• WebApiThrottle限流框架使用手册


    阅读目录:

    1. 介绍
    2. 基于IP全局限流
    3. 基于IP的端点限流
    4. 基于IP和客户端key的端点限流
    5. IP和客户端key的白名单
    6. IP和客户端key自定义限制频率
    7. 端点自定义限制频率
    8. 关于被拒请求的计数器
    9. 在web.config或app.config中定义限制策略
    10. 获取API的客户端key
    11. 存储限流的数据
    12. 运行期间更新限制频率
    13. 限流的请求日志
    14. 用ThrottlingFilter、EnableThrottlingAttribute特性配置限制频率
    15. 关于ThrottlingMiddleware限制频率

    介绍

    为了防止网站意外暴增的流量比如活动、秒杀、攻击等,导致整个系统瘫痪,在前后端接口服务处进行流量限制是非常有必要的。本篇主要介绍下Net限流框架WebApiThrottle的使用。

    WebApiThrottle是一个专门为webApi限制请求频率而设计的,支持寄宿OWIN上的中间件的限制过滤。服务端接口可以基于客户端请求IP地址、客户端请求key、及请求路由去限制webapi接口的访问频率。

    使用nuget命令安装WebApiThrottle:

    PM> Install-Package WebApiThrottle

    Nuget地址:

    https://www.nuget.org/packages/WebApiThrottle/

    WebApiThrottle支持自定义配置各种限流策略。可以根据不同场景配置多个不同的限制,比如授权某个IP每秒、每分钟、每小时、每天、每周的最大调用次数。 这些限制策略可以配置在所有请求上,也可以单独给每个API接口去配置。

    基于IP全局限流

    下面的代码是限制来自同IP请求的最大次数。如果在一分钟内,同样IP的客户端分别调用api/values和api/values/1两个接口, 那么调用api/values/1的请求会被拒绝掉。

    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            config.MessageHandlers.Add(new ThrottlingHandler()
            {
                Policy = new ThrottlePolicy(perSecond: 1, perMinute: 20, perHour: 200, perDay: 1500, perWeek: 3000)
                {
                    IpThrottling = true
                },
                Repository = new CacheRepository()
            });
        }
    }

    如果是自寄宿在Owin上的WebApi,上面的CacheRepository必须替换成运行时的MemoryCacheRepository类,因为CacheRepository使用的是Asp.net版本的缓存。

    public class Startup
    {
        public void Configuration(IAppBuilder appBuilder)
        {
            // Configure Web API for self-host. 
            HttpConfiguration config = new HttpConfiguration();
    
            //Register throttling handler
            config.MessageHandlers.Add(new ThrottlingHandler()
            {
                Policy = new ThrottlePolicy(perSecond: 1, perMinute: 20, perHour: 200, perDay: 1500, perWeek: 3000)
                {
                    IpThrottling = true
                },
                Repository = new MemoryCacheRepository()
            });
    
            appBuilder.UseWebApi(config);
        }
    }

    基于IP的端点限流

    上面的api/values限流配置会对整个api/values开头的API限流,同一秒内、同一ip访问api/values后,所有后续访问api/values/xxx的请求都会被拒绝掉。 如果配置了端点限流,同一秒内你也访问api/values/1了,请求将不会被拒绝,因为它们走的是不同的路由。

    config.MessageHandlers.Add(new ThrottlingHandler()
    {
        Policy = new ThrottlePolicy(perSecond: 1, perMinute: 30)
        {
            IpThrottling = true,
            EndpointThrottling = true
        },
        Repository = new CacheRepository()
    });

    基于IP和客户端key的端点限流

    如果同一个ip的客户端,在同一秒内,调用了2次api/values,其最后一次的调用将会被拒绝掉。

    如果想接口通过唯一key去识别限制客户端,忽略客户端的ip地址限制,应该配置IpThrottling为false。

    config.MessageHandlers.Add(new ThrottlingHandler()
    {
        Policy = new ThrottlePolicy(perSecond: 1, perMinute: 30)
        {
            IpThrottling = true,
            ClientThrottling = true,
            EndpointThrottling = true
        },
        Repository = new CacheRepository()
    });

    IP和客户端key的白名单

    如果请求是从一个白名单中的IP或客户端key发起的,那么限流策略将不会生效,这个请求的所有信息也不会被存储。 其IP白名单列表支持IP v4和v6的范围配置,比如"192.168.0.0/24", "fe80::/10" 和 "192.168.0.0-192.168.0.255",关于IP范围的更多信息请查看https://github.com/jsakamoto/ipaddressrange

    config.MessageHandlers.Add(new ThrottlingHandler()
    {
        Policy = new ThrottlePolicy(perSecond: 2, perMinute: 60)
        {
            IpThrottling = true,
            IpWhitelist = new List<string> { "::1", "192.168.0.0/24" },
    
            ClientThrottling = true,
            ClientWhitelist = new List<string> { "admin-key" }
        },
        Repository = new CacheRepository()
    });

    IP和客户端key自定义限制频率

    你可以自定义基于ip或客户端key的请求频率限制,这些限制会重写WebApiThrottle的默认配置。

    需要注意的是,这些自定义策略需要写到全局配置里才会生效,策略里可以单独给某个ip或某个key配置限流策略。

    config.MessageHandlers.Add(new ThrottlingHandler()
    {
        Policy = new ThrottlePolicy(perSecond: 1, perMinute: 20, perHour: 200, perDay: 1500)
        {
            IpThrottling = true,
            IpRules = new Dictionary<string, RateLimits>
            { 
                { "192.168.1.1", new RateLimits { PerSecond = 2 } },
                { "192.168.2.0/24", new RateLimits { PerMinute = 30, PerHour = 30*60, PerDay = 30*60*24 } }
            },
    
            ClientThrottling = true,
            ClientRules = new Dictionary<string, RateLimits>
            { 
                { "api-client-key-1", new RateLimits { PerMinute = 40, PerHour = 400 } },
                { "api-client-key-9", new RateLimits { PerDay = 2000 } }
            }
        },
        Repository = new CacheRepository()
    });

    端点自定义限制频率

    你也可以为明确的路由地址去自定义限制频率,这些限制配置会重写WebApiThrottle的默认配置。也可以通过相关联的路由地址去定义端点的限制规则,比如api/entry/1端点的请求仅仅是/entry/整个路由地址请求的一部分。 配置后,端点限制引擎会在请求的绝对URI中去搜索这个表达式(api/entry/1),如果这个表达式在请求路由策略中被找到,那么这个限制规则将会被应用。如果有两个或更多的限制规则匹配到同一个URL,更近一级的限制策略将会被应用。

    config.MessageHandlers.Add(new ThrottlingHandler()
    {
        Policy = new ThrottlePolicy(perSecond: 1, perMinute: 20, perHour: 200)
        {
            IpThrottling = true,
            ClientThrottling = true,
            EndpointThrottling = true,
            EndpointRules = new Dictionary<string, RateLimits>
            { 
                { "api/search", new RateLimits { PerSecond = 10, PerMinute = 100, PerHour = 1000 } }
            }
        },
        Repository = new CacheRepository()
    });

    关于被拒请求的计数器

    默认情况下,被拒绝的请求不会累加到WebApiThrottle的计数器里。 比如一个客户端在同一秒中请求了3次,而你配置的限制策略是每秒1次,那么分钟、小时、天的计数器只会记录第一次调用,因为第一次请求不会被拒绝。如果你想把被拒绝的请求也计算到其他的计数器里(分钟、小时、天),你可以设置StackBlockedRequests为true。

    config.MessageHandlers.Add(new ThrottlingHandler()
    {
        Policy = new ThrottlePolicy(perSecond: 1, perMinute: 30)
        {
            IpThrottling = true,
            ClientThrottling = true,
            EndpointThrottling = true,
            StackBlockedRequests = true
        },
        Repository = new CacheRepository()
    });

    在web.config或app.config中定义限制策略

    在web.config或app.config中配置限制策略,通过ThrottlePolicy.FromStore加装配置项。

    config.MessageHandlers.Add(new ThrottlingHandler()
    {
        Policy = ThrottlePolicy.FromStore(new PolicyConfigurationProvider()),
        Repository = new CacheRepository()
    });

    配置示例(policyType的值1代表ip、2代表clientkey、3代表端点)

    <configuration>
    
      <configSections>
        <section name="throttlePolicy" 
                 type="WebApiThrottle.ThrottlePolicyConfiguration, WebApiThrottle" />
      </configSections>
    
      <throttlePolicy limitPerSecond="1"
                      limitPerMinute="10"
                      limitPerHour="30"
                      limitPerDay="300"
                      limitPerWeek ="1500"
                      ipThrottling="true"
                      clientThrottling="true"
                      endpointThrottling="true">
        <rules>
          <!--Ip 规则-->
          <add policyType="1" entry="::1/10"
               limitPerSecond="2"
               limitPerMinute="15"/>
          <add policyType="1" entry="192.168.2.1"
               limitPerMinute="12" />
          <!--Client 规则-->
          <add policyType="2" entry="api-client-key-1"
               limitPerHour="60" />
          <!--Endpoint 规则-->
          <add policyType="3" entry="api/values"
               limitPerDay="120" />
        </rules>
        <whitelists>
          <!--Ip 白名单-->
          <add policyType="1" entry="127.0.0.1" />
          <add policyType="1" entry="192.168.0.0/24" />
          <!--Client 白名单-->
          <add policyType="2" entry="api-admin-key" />
        </whitelists>
      </throttlePolicy>
    </configuration>

    获取API的客户端key

    默认情况下,WebApiThrottle的ThrottlingHandler(限流处理器)会从客户端请求head里通过Authorization-Token key取值。如果你的API key存储在不同的地方,你可以重写ThrottlingHandler.SetIndentity方法,指定你自己的取值策略。

    public class CustomThrottlingHandler : ThrottlingHandler
    {
        protected override RequestIdentity SetIndentity(HttpRequestMessage request)
        {
            return new RequestIdentity()
            {
                ClientKey = request.Headers.Contains("Authorization-Key") ? request.Headers.GetValues("Authorization-Key").First() : "anon",
                ClientIp = base.GetClientIp(request).ToString(),
                Endpoint = request.RequestUri.AbsolutePath.ToLowerInvariant()
            };
        }
    }

    存储限流的数据

    WebApiThrottle会在内存中存储所有的请求数据,寄宿在IIS里使用ASP.NET版本的cache、自寄宿在Owin上使用运行时版本的缓存MemoryCache。如果你想改变请求数据存储的策略,框架是支持redis、nosql、数据库存储的,这种情况下必须创建自己的存储引擎,可以通过实现IThrottleRepository接口完成。

    public interface IThrottleRepository { bool Any(string id);
    
    ThrottleCounter? FirstOrDefault(string id);
    
    void Save(string id, ThrottleCounter throttleCounter, TimeSpan expirationTime);
    
    void Remove(string id);
    
    void Clear();
    }

    自从1.2版本后有IPolicyRepository的接口可以实现存储、获取限制策略对象,意味着可以持久化限流策略,同时也可以被用于在运行期间动态更新限制策略对象。

    public interface IPolicyRepository
    {
        ThrottlePolicy FirstOrDefault(string id);
    
        void Remove(string id);
    
        void Save(string id, ThrottlePolicy policy);
    }

    运行期间更新限制频率

    为了更新限制策略对象,并在运行时使用新的ThrottlingHandler对象,需要引入WebApiThrottle 1.2版本后支持的ThrottleManager.UpdatePolicy函数。

    在启动时注册ThrottlingHandler对象,并在构造函数中传入PolicyCacheRepository ,如果你是通过Owin自寄宿的webapi,需要使用PolicyMemoryCacheRepository对象。

    public static void Register(HttpConfiguration config)
    {
        //trace provider
        var traceWriter = new SystemDiagnosticsTraceWriter()
        {
            IsVerbose = true
        };
        config.Services.Replace(typeof(ITraceWriter), traceWriter);
        config.EnableSystemDiagnosticsTracing();
    
        //添加限流处理者到Web API消息处理集合里
        config.MessageHandlers.Add(new ThrottlingHandler(
            policy: new ThrottlePolicy(perMinute: 20, perHour: 30, perDay: 35, perWeek: 3000)
            {
                //启用ip限制策略
                IpThrottling = true,
    
                //启用客户端key限制策略
                ClientThrottling = true,
                ClientRules = new Dictionary<string, RateLimits>
                { 
                    { "api-client-key-1", new RateLimits { PerMinute = 60, PerHour = 600 } },
                    { "api-client-key-2", new RateLimits { PerDay = 5000 } }
                },
    
                //启用端点限制策略
                EndpointThrottling = true
            },
    
            //如果是owin寄宿,替换成PolicyMemoryCacheRepository
            policyRepository: new PolicyCacheRepository(),
    
            //如果是owin寄宿,替换成MemoryCacheRepository 
            repository: new CacheRepository(),
    
            logger: new TracingThrottleLogger(traceWriter)));
    }

    当你想更新限制策略对象时,可以在任何你的代码里调用静态方法ThrottleManager.UpdatePolicy去刷新内存中的策略数据。

    public void UpdateRateLimits()
    {
        //初始化策略仓库
        var policyRepository = new PolicyCacheRepository();
    
        //从缓存中获取策略对象
        var policy = policyRepository.FirstOrDefault(ThrottleManager.GetPolicyKey());
    
        //更新客户端限制频率
        policy.ClientRules["api-client-key-1"] =
            new RateLimits { PerMinute = 80, PerHour = 800 };
    
        //添加新的客户端限制频率
        policy.ClientRules.Add("api-client-key-3",
            new RateLimits { PerMinute = 60, PerHour = 600 });
    
        //应用策略更新
        ThrottleManager.UpdatePolicy(policy, policyRepository);
    
    }

    限流的请求日志

    如果你想记录限流后的请求日志,可以实现IThrottleLogger接口,添加到ThrottlingHandler里。

    public interface IThrottleLogger
    {
        void Log(ThrottleLogEntry entry);
    }

    实现ITraceWriter日志记录接口的例子

    public class TracingThrottleLogger : IThrottleLogger
    {
        private readonly ITraceWriter traceWriter;
    
        public TracingThrottleLogger(ITraceWriter traceWriter)
        {
            this.traceWriter = traceWriter;
        }
    
        public void Log(ThrottleLogEntry entry)
        {
            if (null != traceWriter)
            {
                traceWriter.Info(entry.Request, "WebApiThrottle",
                    "{0} Request {1} from {2} has been throttled (blocked), quota {3}/{4} exceeded by {5}",
                    entry.LogDate, entry.RequestId, entry.ClientIp, entry.RateLimit, entry.RateLimitPeriod, entry.TotalRequests);
            }
        }
    }

    用SystemDiagnosticsTraceWriter和ThrottlingHandler记录日志的使用例子:

    var traceWriter = new SystemDiagnosticsTraceWriter()
    {
        IsVerbose = true
    };
    config.Services.Replace(typeof(ITraceWriter), traceWriter);
    config.EnableSystemDiagnosticsTracing();
    
    config.MessageHandlers.Add(new ThrottlingHandler()
    {
        Policy = new ThrottlePolicy(perSecond: 1, perMinute: 30)
        {
            IpThrottling = true,
            ClientThrottling = true,
            EndpointThrottling = true
        },
        Repository = new CacheRepository(),
        Logger = new TracingThrottleLogger(traceWriter)
    });

    用ThrottlingFilter、EnableThrottlingAttribute特性配置限制频率

    EnableThrottling与ThrottlingHandler是一个二选一的策略配置方案,二者会做同样的事情,但ThrottlingHandler可以通过EnableThrottlingAttribute特性指定某个webapi的controllers和actions去自定义频率限制。需要注意的是,在webapi请求管道中,ThrottlingHandler是在controller前面执行,因此在你不需要ThrottlingFilter提供的功能时,可以用ThrottlingHandler去直接替代它。

    设置ThrottlingFilter过滤器的步骤,跟ThrottlingHandler类似:

    config.Filters.Add(new ThrottlingFilter()
    {
        Policy = new ThrottlePolicy(perSecond: 1, perMinute: 20, 
        perHour: 200, perDay: 2000, perWeek: 10000)
        {
            //ip配置区域
            IpThrottling = true,
            IpRules = new Dictionary<string, RateLimits>
            { 
                { "::1/10", new RateLimits { PerSecond = 2 } },
                { "192.168.2.1", new RateLimits { PerMinute = 30, PerHour = 30*60, PerDay = 30*60*24 } }
            },
            //添加127.0.0.1到白名单,本地地址不启用限流策略
            IpWhitelist = new List<string> { "127.0.0.1", "192.168.0.0/24" },
    
            //客户端配置区域,如果ip限制也是启动的,那么客户端限制策略会与ip限制策略组合使用。
            ClientRules = new Dictionary<string, RateLimits>
            { 
                { "api-client-key-demo", new RateLimits { PerDay = 5000 } }
            },
            //白名单中的客户端key不会进行限流。
            ClientWhitelist = new List<string> { "admin-key" },
    
            //端点限制策略配置会从EnableThrottling特性中获取。
            EndpointThrottling = true
        }
    });

    使用特性开启限流并配置限制频率:

    [EnableThrottling(PerSecond = 2)]
    public class ValuesController : ApiController
    {
        [EnableThrottling(PerSecond = 1, PerMinute = 30, PerHour = 100)]
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }
    
        [DisableThrotting]
        public string Get(int id)
        {
            return "value";
        }
    }

    关于ThrottlingMiddleware限制频率

    ThrottlingMiddleware是一个OWIN中间件部分,它的作用跟ThrottlingHandler一样。使用ThrottlingMiddleware 你可以在webapi作用域范围外配置限制策略,跟使用OAuth中间件或SignalR端点类似。

    自寄宿配置例子:

    public class Startup
    {
        public void Configuration(IAppBuilder appBuilder)
        {
            ...
    
            //从app.config加载限流策略
            appBuilder.Use(typeof(ThrottlingMiddleware),
                ThrottlePolicy.FromStore(new PolicyConfigurationProvider()),
                new PolicyMemoryCacheRepository(),
                new MemoryCacheRepository(),
                null);
    
            ...
        }
    }

    IIS寄宿配置例子:

    public class Startup
    {
        public void Configuration(IAppBuilder appBuilder)
        {
            ...
    
        //从web.config加载限流策略
        appBuilder.Use(typeof(ThrottlingMiddleware),
            ThrottlePolicy.FromStore(new PolicyConfigurationProvider()),
            new PolicyCacheRepository(),
            new CacheRepository(),
            null);
    
            ...
        }
    }

    翻译了https://github.com/stefanprodan/WebApiThrottle的官方文档,希望对大家有所帮助。

  • 相关阅读:
    爬虫笔记:使用python生成词云(八)
    31丨2内核剖析
    六飞翔篇(4讲)30 丨 2特性概览
    29 丨 我应该迁移到HTTPS吗?
    28 丨 连接太慢该怎么办:HTTPS的优化
    27丨更好更快的握手:TLS1.3特性解析
    26丨信任始于握手:TLS1.2连接过程解析
    Python全栈工程师 (exercises)
    Python全栈工程师(每周总结:2)
    Python全栈工程师(函数嵌套、变量作用域)
  • 原文地址:https://www.cnblogs.com/mushroom/p/4659200.html
Copyright © 2020-2023  润新知