.Net Core限流的實現(xiàn)示例
一、環(huán)境
1.vs2019
2..Net Core 3.1
3.引用 AspNetCoreRateLimit 4.0.1
二、基礎(chǔ)使用
1.設(shè)置
在Startup文件中配置如下,把配置項都放在前面:
public void ConfigureServices(IServiceCollection services)
{
// 從appsettings.json中加載ip限流配置通用規(guī)則
services.Configure<IpRateLimitOptions>(Configuration.GetSection("IpRateLimiting"));
// 從appsettings.json中加載ip限流規(guī)則
services.Configure<IpRateLimitPolicies>(Configuration.GetSection("IpRateLimiting:IpRateLimitPolicies"));
// 從appsettings.json中加載客戶端限流配置通用規(guī)則
services.Configure<ClientRateLimitOptions>(Configuration.GetSection("IpRateLimiting"));
// 從appsettings.json中加載客戶端限流規(guī)則
services.Configure<ClientRateLimitPolicies>(Configuration.GetSection("IpRateLimiting:ClientRateLimitPolicies"));
// 注入計數(shù)器和規(guī)則存儲
services.AddInMemoryRateLimiting();
// 配置(解析器、計數(shù)器密鑰生成器)
services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
//解析clientid和ip的使用有用,如果默認(rèn)沒有啟用,則此處啟用
//services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//調(diào)用ip限流方式和客戶端限流方式
//只能選用一個,后一個調(diào)用的生效,也就是說ip規(guī)則限流和客戶端限流的特殊規(guī)則不能同時使用,但是通用規(guī)則不影響
app.UseIpRateLimiting();
app.UseClientRateLimiting();
}
2.規(guī)則設(shè)置
規(guī)則的設(shè)置分為兩個大類:通過IP限流和通過客戶端限流。都通過配置文件來配置參數(shù),在appsettings.json中配置如下(也可以另起配置文件):
"IpRateLimiting": {
"EnableEndpointRateLimiting": false,
"StackBlockedRequests": false,
"RealIpHeader": "X-Real-IP",
"ClientIdHeader": "X-ClientId",
"HttpStatusCode": 429,
//"IpWhitelist": [ "198.0.0.1", "::1/10", "192.168.0.13/24" ],
"EndpointWhitelist": [ "get:/api/license", "*:/api/status" ],
"ClientWhitelist": [ "dev-id-1", "dev-id-2" ],
"QuotaExceededResponse": {
"Content": "{{\"code\":429,\"msg\":\"Visit too frequently, please try again later\",\"data\":null}}",
"ContentType": "application/json;utf-8",
"StatusCode": 429
},
"GeneralRules": [
{
"Endpoint": "*",
"Period": "1s",
"Limit": 2
}
],
"ClientRateLimitPolicies": {
"ClientRules": [
{
"ClientId": "client-id-1",
"Rules": [
{
"Endpoint": "*",
"Period": "1s",
"Limit": 10
},
{
"Endpoint": "*",
"Period": "15m",
"Limit": 200
}
]
}
]
},
"IpRateLimitPolicies": {
"IpRules": [
{
"Ip": "84.247.85.224",
"Rules": [
{
"Endpoint": "*",
"Period": "1s",
"Limit": 10
},
{
"Endpoint": "*",
"Period": "15m",
"Limit": 200
}
]
}
]
}
}
各配置項的說明如下:
EnableEndpointRateLimiting:設(shè)置為true,則端點規(guī)則為 * 的時候所有的謂詞如GET、POST等分別享有限制次數(shù)。例如,如果您為*:/api/values客戶端設(shè)置每秒GET /api/values5 次調(diào)用的限制,則每秒可以調(diào)用5 次,但也可以調(diào)用5 次PUT /api/values。
如果設(shè)置為false,則上述例子中GET、POST等請求共享次數(shù)限制。是否共享限制次數(shù)的設(shè)置。這里有個注意的地方,就是當(dāng)該參數(shù)設(shè)置為false的時候,只有端點設(shè)置為星號*的規(guī)則有效,其他規(guī)則無效,設(shè)置為true時所有規(guī)則有效。
StackBlockedRequests:設(shè)為false的情況下,被拒絕的請求不會加入到計數(shù)器中,如一秒內(nèi)有三個請求,限流規(guī)則分別為一秒一次和一分鐘三次,則被拒絕的兩個請求是不會記錄在一分鐘三次的規(guī)則中的,也就是說這一分鐘還能調(diào)用兩次該接口。設(shè)置為true的話,則被拒絕的請求也會加入計數(shù)器,像上述例子中的情況,一分鐘內(nèi)就不能調(diào)用了,三次全部記錄了。
RealIpHeader:與配置項IP白名單IpWhitelist組合使用,如果該參數(shù)定義的請求頭名稱存在于一個請求中,并且該參數(shù)內(nèi)容為IP白名單中的IP,則不受限流規(guī)則限制。
ClientIdHeader:與配置項客戶端白名單ClientIdHeader組合使用,如果該參數(shù)定義的請求頭名稱存在于一個請求中,并且該參數(shù)內(nèi)容為客戶端白名單中的名稱,則不受限流規(guī)則限制。
HttpStatusCode:http請求限流后的返回碼。
IpWhitelist:IP白名單,字段支持支持Ip v4和v6如 "198.0.0.1", "::1/10", "192.168.0.13/24"等??梢耘浜蟁ealIpHeader參數(shù)使用,也單獨使用,請求的ip符合該白名單規(guī)則任意一條,則不受限流規(guī)則限制。
EndpointWhitelist:終端白名單,符合該終端規(guī)則的請求都將不受限流規(guī)則影響,如"get:/api/values"表示GET請求的api/values接口不受影響,*表示所有類型的請求。
ClientWhitelist:客戶端白名單,配合ClientIdHeader參數(shù)使用,配置客戶端的名稱。
QuotaExceededResponse:限流后的返回值設(shè)置,返回內(nèi)容、狀態(tài)碼等。
GeneralRules:通用規(guī)則設(shè)置,有三個參數(shù)為Endpoint、Period和Limit。
Endpoint端點格式為{HTTP_Verb}:{PATH},可以使用星號來定位任何 HTTP 動詞,如get:/api/values。
Period期間格式為{INT}{PERIOD_TYPE},可以使用以下期間類型之一:s、m、h、d,分別為秒分時天。
Limit限制格式為{LONG},訪問次數(shù)。
ClientRateLimitPolicies:客戶端限流的特殊配置,規(guī)則和通用規(guī)則一樣設(shè)置,只不過需要配合ClientIdHeader在請求頭中來使用,需要使用app.UseClientRateLimiting();啟用,否則無效。這個參數(shù)名稱是可以更改的噢。通用規(guī)則和特殊規(guī)則是同優(yōu)先級的。
IpRateLimitPolicies:IP限流的特殊配置,規(guī)則和通用規(guī)則一樣設(shè)置,只不過需要配合RealIpHeader在請求頭中來使用,需要使用app.UseIpRateLimiting();啟用,否則無效。這個參數(shù)名稱是可以更改的噢。通用規(guī)則和特殊規(guī)則是同優(yōu)先級的。
3.特殊規(guī)則的啟用
IP和客戶端特殊規(guī)則的啟用需要改造Program文件中的程序入口如下,分別發(fā)送各自的特殊規(guī)則:
public static async Task Main(string[] args)
{
IWebHost webHost = CreateWebHostBuilder(args).Build();
using (var scope = webHost.Services.CreateScope())
{
var clientPolicyStore = scope.ServiceProvider.GetRequiredService<IClientPolicyStore>();
await clientPolicyStore.SeedAsync();
var ipPolicyStore = scope.ServiceProvider.GetRequiredService<IIpPolicyStore>();
await ipPolicyStore.SeedAsync();
}
await webHost.RunAsync();
}
在ConfigureServices中讀取配置參數(shù),之后是在Startup文件中的Configure方法選擇app.UseIpRateLimiting()或app.UseClientRateLimiting()啟動IP特殊規(guī)則或者客戶端特殊規(guī)則,都存在的情況下,先執(zhí)行的先生效。
三、請求返回頭
限流啟動后,執(zhí)行限流規(guī)則的返回頭會有三個參數(shù)分別為:
X-Rate-Limit-Limit:現(xiàn)在時間,如1d。
X-Rate-Limit-Remaining:剩余可請求次數(shù)。
X-Rate-Limit-Reset:下次請求次數(shù)重置時間。
多個限制規(guī)則會采用最長的周期的規(guī)則顯示。
在配置文件中配置返回信息,除了返回提示信息外,還可以返回限制規(guī)則提醒,如下
"Content": "{{\"code\":429,\"msg\":\"訪問太頻繁了,每{1}{0}次,請在{2}秒后重試\",\"data\":null}}",
{0}可以替換當(dāng)前阻止規(guī)則規(guī)定的次數(shù),{1}可以替換時間區(qū)間帶單位s、h等,{2}替換幾秒后嘗試當(dāng)單位為天或者小時等都會換算成秒。
四、使用Redis存儲
限流規(guī)則等目前都是通過內(nèi)存存儲的,我們結(jié)合實際會使用redis存儲。使用Microsoft.Extensions.Caching.Redis可以達(dá)到這么目的。
但是好像會存在性能問題,所以我們自己替換,使用的是用CSRedis封裝的方法,不過這里不做闡述。
我們緩存三類數(shù)據(jù)1、訪問計數(shù)2、ip特殊規(guī)則3、客戶端特殊規(guī)則
1、訪問計數(shù)
public class RedisRateLimitCounterStore : IRateLimitCounterStore
{
private readonly ILogger _logger;
private readonly IRateLimitCounterStore _memoryCacheStore;
private readonly RedisCache _redisCache;
public RedisRateLimitCounterStore(
IMemoryCache memoryCache,
ILogger<RedisRateLimitCounterStore> logger)
{
_logger = logger;
_memoryCacheStore = new MemoryCacheRateLimitCounterStore(memoryCache);
_redisCache = new RedisCache();
}
public async Task<bool> ExistsAsync(string id, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
return await TryRedisCommandAsync(
() =>
{
return _redisCache.KeyExistsAsync(id, 0);
},
() =>
{
return _memoryCacheStore.ExistsAsync(id, cancellationToken);
});
}
public async Task<RateLimitCounter?> GetAsync(string id, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
return await TryRedisCommandAsync(
async () =>
{
var value = await _redisCache.GetStringAsync(id, 0);
if (!string.IsNullOrEmpty(value))
{
return JsonConvert.DeserializeObject<RateLimitCounter?>(value);
}
return null;
},
() =>
{
return _memoryCacheStore.GetAsync(id, cancellationToken);
});
}
public async Task RemoveAsync(string id, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
_ = await TryRedisCommandAsync(
async () =>
{
await _redisCache.KeyDeleteAsync(id, 0);
return true;
},
async () =>
{
await _memoryCacheStore.RemoveAsync(id, cancellationToken);
return true;
});
}
public async Task SetAsync(string id, RateLimitCounter? entry, TimeSpan? expirationTime = null, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
_ = await TryRedisCommandAsync(
async () =>
{
var exprie = expirationTime.HasValue ? Convert.ToInt32(expirationTime.Value.TotalSeconds) : -1;
await _redisCache.SetStringAsync(id, JsonConvert.SerializeObject(entry.Value), exprie);
return true;
},
async () =>
{
await _memoryCacheStore.SetAsync(id, entry, expirationTime, cancellationToken);
return true;
});
}
private async Task<T> TryRedisCommandAsync<T>(Func<Task<T>> command, Func<Task<T>> fallbackCommand)
{
if (_redisCache != null)
{
try
{
return await command();
}
catch (Exception ex)
{
_logger.LogError($"Redis command failed: {ex}");
}
}
return await fallbackCommand();
}
}
2、ip特殊規(guī)則
public class RedisIpPolicyStore : IIpPolicyStore
{
private readonly IpRateLimitOptions _options;
private readonly IpRateLimitPolicies _policies;
private readonly RedisCache _redisCache;
public RedisIpPolicyStore(
IOptions<IpRateLimitOptions> options = null,
IOptions<IpRateLimitPolicies> policies = null)
{
_options = options?.Value;
_policies = policies?.Value;
_redisCache = new RedisCache();
}
public async Task<bool> ExistsAsync(string id, CancellationToken cancellationToken = default)
{
return await _redisCache.KeyExistsAsync($"{_options.IpPolicyPrefix}", 0);
}
public async Task<IpRateLimitPolicies> GetAsync(string id, CancellationToken cancellationToken = default)
{
string stored = await _redisCache.GetStringAsync($"{_options.IpPolicyPrefix}", 0);
if (!string.IsNullOrEmpty(stored))
{
return JsonConvert.DeserializeObject<IpRateLimitPolicies>(stored);
}
return default;
}
public async Task RemoveAsync(string id, CancellationToken cancellationToken = default)
{
await _redisCache.DelStringAsync($"{_options.IpPolicyPrefix}", 0);
}
public async Task SeedAsync()
{
// on startup, save the IP rules defined in appsettings
if (_options != null && _policies != null)
{
await _redisCache.SetStringAsync($"{_options.IpPolicyPrefix}", JsonConvert.SerializeObject(_policies), 0).ConfigureAwait(false);
}
}
public async Task SetAsync(string id, IpRateLimitPolicies entry, TimeSpan? expirationTime = null, CancellationToken cancellationToken = default)
{
var exprie = expirationTime.HasValue ? Convert.ToInt32(expirationTime.Value.TotalSeconds) : -1;
await _redisCache.SetStringAsync($"{_options.IpPolicyPrefix}", JsonConvert.SerializeObject(_policies), 0, exprie);
}
}
3、客戶端特殊規(guī)則
public class RedisClientPolicyStore : IClientPolicyStore
{
private readonly ClientRateLimitOptions _options;
private readonly ClientRateLimitPolicies _policies;
private readonly RedisCache _redisCache;
public RedisClientPolicyStore(
IOptions<ClientRateLimitOptions> options = null,
IOptions<ClientRateLimitPolicies> policies = null)
{
_options = options?.Value;
_policies = policies?.Value;
_redisCache = new RedisCache();
}
public async Task<bool> ExistsAsync(string id, CancellationToken cancellationToken = default)
{
return await _redisCache.KeyExistsAsync($"{_options.ClientPolicyPrefix}", 0);
}
public async Task<ClientRateLimitPolicy> GetAsync(string id, CancellationToken cancellationToken = default)
{
string stored = await _redisCache.GetStringAsync($"{_options.ClientPolicyPrefix}", 0);
if (!string.IsNullOrEmpty(stored))
{
return JsonConvert.DeserializeObject<ClientRateLimitPolicy>(stored);
}
return default;
}
public async Task RemoveAsync(string id, CancellationToken cancellationToken = default)
{
await _redisCache.DelStringAsync($"{_options.ClientPolicyPrefix}", 0);
}
public async Task SeedAsync()
{
// on startup, save the IP rules defined in appsettings
if (_options != null && _policies != null)
{
await _redisCache.SetStringAsync($"{_options.ClientPolicyPrefix}", JsonConvert.SerializeObject(_policies), 0).ConfigureAwait(false);
}
}
public async Task SetAsync(string id, ClientRateLimitPolicy entry, TimeSpan? expirationTime = null, CancellationToken cancellationToken = default)
{
var exprie = expirationTime.HasValue ? Convert.ToInt32(expirationTime.Value.TotalSeconds) : -1;
await _redisCache.SetStringAsync($"{_options.ClientPolicyPrefix}", JsonConvert.SerializeObject(_policies), 0, exprie);
}
}
之后在Startup文件中增加對應(yīng)的注入
services.AddSingleton<IRateLimitCounterStore, RedisRateLimitCounterStore>(); services.AddSingleton<IIpPolicyStore, RedisIpPolicyStore>(); services.AddSingleton<IClientPolicyStore, RedisClientPolicyStore>();
之后運行就可以在redis中看到啦

五、修改規(guī)則
規(guī)則只能修改IP和客戶端的特殊規(guī)則,因為上一部分已經(jīng)注入了改規(guī)則的對應(yīng)redis增刪查改的功能,所以我們可以利用這些方法重寫規(guī)則,如下:
public class ClientRateLimitController : Controller
{
private readonly ClientRateLimitOptions _options;
private readonly IClientPolicyStore _clientPolicyStore;
public ClientRateLimitController(IOptions<ClientRateLimitOptions> optionsAccessor, IClientPolicyStore clientPolicyStore)
{
_options = optionsAccessor.Value;
_clientPolicyStore = clientPolicyStore;
}
[HttpGet]
public ClientRateLimitPolicy Get()
{
return _clientPolicyStore.Get($"{_options.ClientPolicyPrefix}_cl-key-1");
}
[HttpPost]
public void Post()
{
var id = $"{_options.ClientPolicyPrefix}_cl-key-1";
var clPolicy = _clientPolicyStore.Get(id);
clPolicy.Rules.Add(new RateLimitRule
{
Endpoint = "*/api/testpolicyupdate",
Period = "1h",
Limit = 100
});
_clientPolicyStore.Set(id, clPolicy);
}
}
到此這篇關(guān)于.Net Core限流的實現(xiàn)示例的文章就介紹到這了,更多相關(guān).Net Core限流內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
ASP.NET MVC5+EF6+EasyUI后臺管理系統(tǒng) 微信公眾平臺開發(fā)之資源環(huán)境準(zhǔn)備
這篇文章主要介紹了ASP.NET MVC5+EF6+EasyUI后臺管理系統(tǒng),微信公眾平臺開發(fā)之資源環(huán)境準(zhǔn)備,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-09-09
asp net core2.1如何使用jwt從原理到精通(二)
這篇文章主要給大家介紹了關(guān)于asp net core2.1如何使用jwt從原理到精通的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-11-11
asp.net(c#)實現(xiàn)從sqlserver存取二進(jìn)制圖片的代碼
有一個員工表Employee,需要保存員工照片(Photo)到數(shù)據(jù)庫(sql server)上。員工照片對應(yīng)的字段是varbinary(max),也就是要存成二進(jìn)制文件類型(這和以前討巧地存圖片文件路徑就不相同了),默認(rèn)可以為空。2011-09-09
.Net通過TaskFactory.FromAsync簡化APM
這篇文章介紹了.Net通過TaskFactory.FromAsync簡化APM的方法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-06-06
Asp.net SignalR應(yīng)用并實現(xiàn)群聊功能
這篇文章主要為大家分享了Asp.net SignalR應(yīng)用并實現(xiàn)群聊功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-04-04

