前言
之前介紹過使用 AspNetCoreRateLimit 元件來實現介面限流
從 .Net7 開始,AspNetCore 開始內建限流元件,當時我們的專案還在 .Net6 所以只能用第三方的
現在都升級到 .Net8 了,當然是得來試試這個原生元件
體驗後:配置使用都比較簡單,不過功能也沒有 AspNetCoreRateLimit 那麼靈活
註冊服務
為了保持 Program.cs
的程式碼簡潔,依然是使用擴充套件方法來實現服務註冊和配置
在 src/IdsLite.Api/Extensions/CfgRateLimit.cs
檔案中
public static class RateLimitPolicies {
public const string Fixed = "fixed";
public const string Sliding = "sliding";
}
public static class CfgRateLimit {
public static IServiceCollection AddRateLimit(this IServiceCollection services) {
services.AddRateLimiter(options => {
options.AddFixedWindowLimiter(RateLimitPolicies.Fixed, opt => {
opt.Window = TimeSpan.FromMinutes(1); // 時間視窗
opt.PermitLimit = 3; // 在時間視窗內允許的最大請求數
opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; // 請求處理順序
opt.QueueLimit = 2; // 佇列中允許的最大請求數
});
});
return services;
}
public static IApplicationBuilder UseRateLimit(this IApplicationBuilder app) {
app.UseRateLimiter();
return app;
}
}
這裡使用了 Fixed Window (固定視窗)的策略來進行限流,具體配置程式碼裡面有註釋。
PS: 關於四種常用的限流策略(固定/滑動視窗、令牌桶、併發),上一篇文章已經介紹過了
接著在 Program.cs 檔案中新增
builder.Services.AddRateLimit();
配置中介軟體
var app = builder.Build();
app.MapHealthChecks("/healthz");
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseExceptionless();
app.UseHttpsRedirection();
app.UseRouting(); // Routing should be defined before applying rate limiting
app.UseRateLimiter(); // Rate limiting should be applied after routing
app.UseCors(); // CORS should be applied after rate limiting
app.UseAuthentication(); // Authentication should be applied before authorization
app.UseAuthorization(); // Authorization should be applied after authentication
app.UseSwaggerWithAuthorize();
app.MapControllers();
app.Run();
注意這裡一定要把限流中介軟體放在 UseRouting
之後的第一個
因為我們要在具體的介面上新增限流策略,所以要在請求已經被路由到指定的介面之後再進行限流
為介面新增限流配置
在需要限流的 Controller 或者介面方法上新增 [EnableRateLimiting(RateLimitPolicies.Fixed)]
就可以了
比如這個登入介面
[HttpPost("login/password")]
[EnableRateLimiting(RateLimitPolicies.Fixed)]
[ValidateClient(ClientIdSource.Body, ParameterName = "ClientId")]
public async Task<IActionResult> LoginByPassword(PwdLoginDto dto) {}
測試效果
在 shell 中使用 curl 進行介面測試
for i in {1..11}; do curl http://localhost:5000/api/auth/login/password; done
按照上述的配置的話,在第11個請求的時候,觸發了限流策略,應該會卡住,等到1分鐘後才能返回
因為我們配置了 opt.QueueLimit = 2
即觸發限流時,可以有 2 個請求在佇列裡排隊,這 2 個請求會一直等到系統可以重新生成響應為止。
如果有第 3 個請求進來,則立刻返回 503 (Service Unavailable) ,這個元件預設就是 503 ,也可以自己配置成 429 (Too many requests)
配置
配置等待佇列
把 opt.QueueLimit
設定為 0 ,可以實現觸發限流立刻拒絕響應
自定義拒絕響應
前面說觸發限流返回503,我們也可以自己配置成 429
services.AddRateLimiter(options => {
options.OnRejected = (context, token) => {
// 檢查 context.Lease 中是否包含 RetryAfter 後設資料
// RetryAfter 後設資料通常指示客戶端應該在多少秒後重試請求
if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter)) {
// 如果 RetryAfter 後設資料存在,將 RetryAfter 值(以秒為單位)新增到響應頭中
context.HttpContext.Response.Headers.RetryAfter =
((int)retryAfter.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo);
}
context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
context.HttpContext.Response.WriteAsync("Too many requests. Please try again later.",
cancellationToken: token);
return new ValueTask();
};
});
其實這個是官方文件裡的寫法,最關鍵的就是 context.HttpContext.Response.StatusCode
這一行,其他的都是錦上添花,可以省略。
擴充套件
根據IP地址進行限流
之前我使用 AspNetCoreRateLimit 元件的時候,直接使用了它提供的 IP 地址限流功能
在原生的 Microsoft.AspNetCore.RateLimiting
中,沒有內建這個功能,需要自己來實現
我看了下文件裡介紹的,思路是為每一個 IP 地址建立一個分割槽 (PartitionedRateLimiter.Create
),在每個分割槽裡再配置限流策略
詳見官方文件的 limiter-with-onrejected-retryafter-and-globallimiter 部分
這種實現方式還要考慮一下,應用容易受到採用 IP 源地址欺騙的拒絕服務攻擊,詳見參考資料
PS: 有點複雜,我還是用回 AspNetCoreRateLimit 得了,真是折騰
根據使用者進行限流
透過自定義策略,可以實現根據登入使用者進行限流,比如每個使用者每分鐘只能請求 10 次這樣子
詳見官方文件的 limiter-with-authorization 部分
對了,官方文件裡還提到了 dotnet user-jwts 這個工具,第一次聽到,先 mark 一下,後面來看看咋樣
小結
就這樣吧,試用了一下,感覺還是太折騰,用回原來的 AspNetCoreRateLimit 元件得了
參考資料
-
https://learn.microsoft.com/zh-cn/aspnet/core/performance/rate-limit
-
https://www.rfc-editor.org/info/bcp38
-
https://learn.microsoft.com/zh-cn/aspnet/core/security/authentication/jwt-authn?view=aspnetcore-8.0