閱讀目錄:
- 介紹
- 基於IP全侷限流
- 基於IP的端點限流
- 基於IP和客戶端key的端點限流
- IP和客戶端key的白名單
- IP和客戶端key自定義限制頻率
- 端點自定義限制頻率
- 關於被拒請求的計數器
- 在web.config或app.config中定義限制策略
- 獲取API的客戶端key
- 儲存限流的資料
- 執行期間更新限制頻率
- 限流的請求日誌
- 用ThrottlingFilter、EnableThrottlingAttribute特性配置限制頻率
- 關於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的官方文件,希望對大家有所幫助。