通過 RateLimiter 限速器 + Redis,限制執行速度

上篇介紹使用 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

範例位置

sample.dotblog/Rate Limit/Lab.ClientRateLimitAndRedis at 0d109fca68f6a776816a5be181476e5267eaedcb · yaochangyu/sample.dotblog (github.com)

若有謬誤,煩請告知,新手發帖請多包涵


Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET

Image result for microsoft+mvp+logo