Asp-Net-Core開發筆記:使用RateLimit中間件實現接口限流
前言
最近一直在忙(2月份沉迷steam,3月開始工作各種忙),好久沒更新博客了,不過也積累了一些,忙里偷閑記錄一下。
這個需求是這樣的,我之前做了個工單系統,現在要對登錄、注冊、發起工單這些功能做限流,不能讓用戶請求太頻繁。
從 .Net7 開始,已經有內置的限流功能了,但目前我們的項目還在使用 .Net6 LTS 版本,下一個 LTS 沒發布之前,暫時不考慮使用 .Net7 這種非 LTS 版本。
然后我找到了這個 AspNetCoreRateLimit 組件,在 Github 上有接近三千個星星,看了一下文檔使用也簡單靈活,于是決定嘗試一下~
AspNetCoreRateLimit 組件
項目主頁: https://github.com/stefanprodan/AspNetCoreRateLimit
這是官方的介紹:
AspNetCoreRateLimit is an ASP.NET Core rate limiting solution designed to control the rate of requests that clients can make to a Web API or MVC app based on IP address or client ID.
The AspNetCoreRateLimit NuGet package contains an IpRateLimitMiddleware and a ClientRateLimitMiddleware, with each middleware you can set multiple limits for different scenarios like allowing an IP or Client to make a maximum number of calls in a time interval like per second, 15 minutes, etc. You can define these limits to address all requests made to an API or you can scope the limits to each API URL or HTTP verb and path.
用最近很厲害的 ChatGPT 翻譯一下:
AspNetCoreRateLimit是一個ASP.NET Core速率限制解決方案,旨在基于IP地址或客戶端ID控制客戶端對Web API或MVC應用程序發出請求的速率。
AspNetCoreRateLimit NuGet包 包含一個IpRateLimitMiddleware和一個ClientRateLimitMiddleware,每個中間件都可以為不同的場景設置多個限制,比如允許IP或客戶端在時間間隔內進行最大數量的調用,比如每秒、15分鐘等。您可以定義這些限制以處理對API發出的所有請求,也可以將限制范圍限定為每個API URL或HTTP動詞和路徑。
這個組件使用起來挺靈活的,直接在 AspNetCore配置 里定義規則,意味著可以不重新編譯程序就修改限流規則,官方給的例子是直接在 appsettings.json
里配置,但使用其他配置源理論上也沒問題(配置中心用起來)。
簡單介紹下這個組件的思路
首先它有兩種模式:
- 根據IP地址限流
- 根據 ClientID 限流
IP地址很容易理解,ClientID 我一開始以為是用戶ID,不過看了說明,是一個放在請求頭里的參數,比如 X-ClientId
,這個要自己實現,可以直接用用戶ID。
為了方便使用,我這個項目里面直接用IP地址模式。
RateLimit 組件可以配置全局的限流,也可以配置對某個IP地址(段)進行限流。
配置服務
為了從 appsettings.json
讀取數據,先在 Program.cs
注冊配置服務
builder.Services.AddOptions();
然后寫個擴展方法來注冊 RateLimit 的相關服務
引入命名空間
using AspNetCoreRateLimit;
using AspNetCoreRateLimit.Redis;
using StackExchange.Redis;
寫個靜態類
public static class ConfigureRateLimit {
public static void AddRateLimit(this IServiceCollection services, IConfiguration conf) {
//load general configuration from appsettings.json
services.Configure<IpRateLimitOptions>(conf.GetSection("IpRateLimiting"));
var redisOptions = ConfigurationOptions.Parse(conf.GetConnectionString("Redis"));
services.AddSingleton<IConnectionMultiplexer>(provider => ConnectionMultiplexer.Connect(redisOptions));
services.AddRedisRateLimiting();
services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
}
public static IApplicationBuilder UseRateLimit(this IApplicationBuilder app) {
app.UseIpRateLimiting();
return app;
}
}
來解析一下配置的代碼。
我暫時不需要對不同的IP地址段應用不同的限流規則
所以直接用 IpRateLimitOptions
services.Configure<IpRateLimitOptions>(conf.GetSection("IpRateLimiting"));
要做根據IP限流,就得記錄每個IP訪問了多少次,RateLimit 組件支持多種存儲方式,最簡單的可以直接存內存里,不過為了穩定我還是選擇 Redis。
這幾行代碼就是配置 Redis 的。
var redisOptions = ConfigurationOptions.Parse(conf.GetConnectionString("Redis"));
services.AddSingleton<IConnectionMultiplexer>(provider => ConnectionMultiplexer.Connect(redisOptions));
services.AddRedisRateLimiting();
最后注入一下 IRateLimitConfiguration
,我猜應該是中間件要用到的。至少我目前在 Controller 代碼里不需要用到任何跟 RateLimit 有關的代碼。
寫完了擴展方法,回到 Program.cs
注冊服務
builder.Services.AddRateLimit(builder.Configuration);
添加中間件
var app = builder.Build();
app.UseExceptionless();
app.UseStaticFiles(new StaticFileOptions {
ServeUnknownFileTypes = true
});
app.UseRateLimit();
// ...
app.Run();
我這里把 UseRateLimit
放在 UseStaticFiles
后面,不然頁面里的靜態文件都被算進去訪問次數,很快就被限流了。
配置
在 appsettings.json
里寫具體的限流規則。
官網提供的配置規則不能照抄,要理解一下他的文檔
EnableEndpointRateLimiting
- 這個選項要設置為 true ,不然設置的限流是全局的,不能根據某個路徑單獨設置限流StackBlockedRequests
- 按照默認的設置為 false 就行,設置成 true 的話,一個接口被限流之后再重復請求還會計算到訪問次數里面,這樣有可能導致限流到天荒地老。
其他的配置顧名思義,懂的都懂。
GeneralRules
是對具體路徑的限流規則
如果全局限流,把 EnableEndpointRateLimiting
設置為 false 的話,那就這樣設置,1分鐘只能訪問5次
{
"Endpoint": "*",
"Period": "1m",
"Limit": 5
}
Endpoint
可以設置 HTTP方法:路徑
的形式,比如 post:/account/login
具體看文檔吧(參考文檔第三條)
附上我的配置文件,對添加工單、登錄、注冊接口進行限流。
{
"IpRateLimiting": {
"EnableEndpointRateLimiting": true,
"StackBlockedRequests": false,
"RealIpHeader": "X-Real-IP",
"ClientIdHeader": "X-ClientId",
"HttpStatusCode": 429,
"IpWhitelist": [],
"EndpointWhitelist": [
"get:/api/license",
"*:/api/status"
],
"ClientWhitelist": [
"dev-id-1",
"dev-id-2"
],
"GeneralRules": [
{
"Endpoint": "*:/ticket/add",
"Period": "1m",
"Limit": 5
},
{
"Endpoint": "post:/account/login",
"Period": "1m",
"Limit": 5
},
{
"Endpoint": "post:/account/SignUp",
"Period": "1m",
"Limit": 5
}
],
"QuotaExceededResponse": {
"Content": "{{ \"message\": \"先別急,你訪問得太快了!\", \"details\": \"已經觸發限流。限流規則: 每 {1} 只能訪問 {0} 次。請 {2} 秒后再重試。\" }}",
"ContentType": "application/json",
"StatusCode": 429
}
}
}
同時自定義了被限流時的提示。
效果如下
{
"message": "先別急,你訪問得太快了!",
"details": "已經觸發限流。限流規則: 每 1m 只能訪問 5 次。請 16 秒后再重試。"
}