最近在用 ZhonTai 的 Admin.Core 框架做项目,翻配置文件的时候注意到它内置了 IP 限流的功能,基于 AspNetCoreRateLimit 组件 + Redis 实现的。研究了一下,记录一下怎么用和背后的原理。


开启限流

Admin.Core 的限流默认是关闭的,需要手动开启两个地方。

appconfig.json 里把限流开关打开:

{
  "rateLimit": true
}

然后在 cacheconfig.json 里配置限流的存储方式为 Redis:

{
  "typeRateLimit": "Redis"
}

如果不配 Redis,限流计数器就存在内存里,单机没问题,但如果部署了多个实例,每个实例各算各的,限流就形同虚设了。

配置限流规则

限流规则写在 appsettings.jsonIpRateLimiting 节点里:

{
  "IpRateLimiting": {
    "EnableEndpointRateLimiting": true,
    "StackBlockedRequests": false,
    "RealIpHeader": "X-Real-IP",
    "HttpStatusCode": 429,
    "GeneralRules": [
      {
        "Endpoint": "*",
        "Period": "1s",
        "Limit": 10
      },
      {
        "Endpoint": "post:/api/admin/auth/login",
        "Period": "1m",
        "Limit": 5
      }
    ]
  }
}

几个关键配置说一下:

EnableEndpointRateLimiting 要设成 true,不然所有规则都是全局生效的,没法针对某个接口单独限流。

StackBlockedRequests 建议保持 false。设成 true 的话,被限流之后的重复请求还会被计入访问次数,可能导致一直被限着出不来。

RealIpHeader 如果你的服务前面有 Nginx 或者 Caddy 之类的反代,需要配成反代传过来的真实 IP 头,不然限流限的是反代服务器的 IP,所有用户共享同一个计数器。

Endpoint 的格式是 HTTP方法:路径* 表示所有接口。上面的配置意思是:全局每秒最多 10 次请求,登录接口每分钟最多 5 次。

它是怎么工作的

原理不复杂。每个请求进来的时候,AspNetCoreRateLimit 中间件会:

  1. 拿到客户端的 IP 地址
  2. 根据 IP + 请求路径生成一个 Redis key
  3. 对这个 key 做自增操作,同时设置过期时间(就是你配的 Period)
  4. 如果计数超过了 Limit,直接返回 429 状态码,请求不会到达 Controller

因为计数器存在 Redis 里,多个实例共享同一份数据,所以不管请求被负载均衡到了哪个实例,限流都是统一生效的。

触发限流后的表现

被限流后,接口会返回 HTTP 429 状态码。Admin.Core 的前端也做了处理,会弹出提示告诉用户请求太频繁了。

如果你想自定义限流后返回的内容,可以在配置里加一个 QuotaExceededResponse

{
  "IpRateLimiting": {
    "QuotaExceededResponse": {
      "Content": "{{ \"message\": \"请求太频繁,请稍后再试\", \"details\": \"限流规则: 每 {1} 最多 {0} 次,请 {2} 秒后重试\" }}",
      "ContentType": "application/json",
      "StatusCode": 429
    }
  }
}

一个容易踩的坑

中间件的注册顺序很重要。UseIpRateLimiting() 要放在 UseStaticFiles() 后面,不然静态文件(js、css、图片)的请求也会被算进访问次数里,几下就被限流了。

不用 Admin.Core 怎么自己接

如果你不是用 Admin.Core,想在自己的 .NET WebApi 项目里接这套限流,核心就三步:

安装依赖:

dotnet add package AspNetCoreRateLimit
dotnet add package AspNetCoreRateLimit.Redis
dotnet add package StackExchange.Redis

注册服务:

builder.Services.AddOptions();
builder.Services.Configure<IpRateLimitOptions>(
    builder.Configuration.GetSection("IpRateLimiting"));

var redisOptions = ConfigurationOptions.Parse(
    builder.Configuration.GetConnectionString("Redis"));
builder.Services.AddSingleton<IConnectionMultiplexer>(
    _ => ConnectionMultiplexer.Connect(redisOptions));

builder.Services.AddRedisRateLimiting();
builder.Services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();

使用中间件:

app.UseStaticFiles();
app.UseIpRateLimiting();  // 放在 UseStaticFiles 后面

配置文件里的规则格式和上面一样。

另一个选择

其实从 .NET 7 开始,微软已经内置了限流中间件 Microsoft.AspNetCore.RateLimiting,支持固定窗口、滑动窗口、令牌桶、并发限制四种算法。如果要配合 Redis 做分布式限流,可以用社区维护的 RedisRateLimiting 这个 NuGet 包。

不过 Admin.Core 用的是 AspNetCoreRateLimit,这个组件出来得更早,配置也比较灵活,在 GitHub 上有接近三千个星。如果你已经在用 Admin.Core,直接用它内置的就行,不用折腾。