上篇介紹使用 System.Threading.RateLimiting,實現限制執行速度,當有多台機器/服務需要限速,這就需要一台集中式的服務來管理 RateLimiter 的狀態,目前微軟官方還沒有支援 Redis,不過已經被排入 .NET 9 Milestones 再過一陣子,System.Threading.RateLimiting 應該就可以支援 Redis 了,對於現在需要使用 Redis 的夥伴,可以先考慮使用 cristipufu/aspnetcore-redis-rate-limiting
開發環境
- Windows 11
- JetBrains Rider 2023.3.3
- .NET 8
- System.Threading.RateLimiting 8.0.0
- RedisRateLimiting 1.1.0
安裝
若是要限制伺服器的使用量,要安裝的是
dotnet add package RedisRateLimiting.AspNetCore --version 1.1.0
若是要限制 Client 端打出去的量要安裝的是
dotnet add package RedisRateLimiting --version 1.1.0
準備 Redis 環境,請參考 建立 .NET 6 + Redis 本機開發環境 | 余小章 @ 大內殿堂 - 點部落 (dotblogs.com.tw)
docker-compose.yml 如下
version: "3.8"
services:
redis:
image: redis
ports:
- 6379:6379
伺服器端的限速
這跟 Microsoft.AspNetCore.RateLimiting 的使用方式一樣,主要都是透過 Middleware 處理 Request
builder.Services.AddRateLimiter(options =>
{
options.AddRedisConcurrencyLimiter("demo_concurrency", (opt) =>
{
opt.ConnectionMultiplexerFactory = () => connectionMultiplexer;
opt.PermitLimit = 5;
// Queue requests when the limit is reached
//opt.QueueLimit = 5
});
});
Client 端的限速
這次使用的是 RedisSlidingWindowRateLimiter 10 秒內處理 50 筆請求
using Lab.ClientRateLimitAndRedis;
using RedisRateLimiting;
using StackExchange.Redis;
var redisSlidingWindowRateLimiter = new RedisSlidingWindowRateLimiter<string>("demo-redis-sliding-window",
new RedisSlidingWindowRateLimiterOptions
{
ConnectionMultiplexerFactory = () => ConnectionMultiplexer.Connect("localhost"),
Window = TimeSpan.FromSeconds(10),
PermitLimit = 50
}
);
// Create an HTTP client with the client-side rate limited handler.
var limiter = redisSlidingWindowRateLimiter;
using HttpClient client = new(
handler: new ClientSideRateLimitedHandler(limiter: limiter));
Console.WriteLine($"{DateTime.Now.ToString()},Start");
var count = 0;
while (true)
{
var lease = await limiter.AcquireAsync(permitCount: 1, cancellationToken: default);
if (lease.IsAcquired == false)
{
Console.WriteLine("Rate limit exceeded. Pausing requests for 1 sec.");
await Task.Delay(TimeSpan.FromSeconds(1));
continue;
}
var tasks = new List<Task>()
{
Task.Run(async () => { await Task.Delay(TimeSpan.FromMilliseconds(100)); }),
Task.Run(async () => { await Task.Delay(TimeSpan.FromMilliseconds(120)); }),
};
await Task.WhenAll(tasks);
count++;
Console.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss}, Run Count: {count}");
}
static async ValueTask GetAsync(
HttpClient client, string url, CancellationToken cancellationToken)
{
using var response =
await client.GetAsync(url, cancellationToken);
Console.WriteLine(
$"URL: {url}, HTTP status code: {response.StatusCode} ({(int)response.StatusCode})");
}
跟上篇的範例有 87趴像,主要的參數都已經換成來自 Redis。
觀察一下兩隻應用程式執行的筆數,如我所預期
參考
cristipufu/aspnetcore-redis-rate-limiting
範例位置
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET