最近在用 ZhonTai 的 Admin.Core 框架做项目,翻配置文件的时候注意到它内置了 IP 限流的功能,基于 AspNetCoreRateLimit 组件 + Redis 实现的。研究了一下,记录一下怎么用和背后的原理。
开启限流
Admin.Core 的限流默认是关闭的,需要手动开启两个地方。
在 appconfig.json 里把限流开关打开:
{
"rateLimit": true
}
然后在 cacheconfig.json 里配置限流的存储方式为 Redis:
{
"typeRateLimit": "Redis"
}
如果不配 Redis,限流计数器就存在内存里,单机没问题,但如果部署了多个实例,每个实例各算各的,限流就形同虚设了。
配置限流规则
限流规则写在 appsettings.json 的 IpRateLimiting 节点里:
{
"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 中间件会:
- 拿到客户端的 IP 地址
- 根据 IP + 请求路径生成一个 Redis key
- 对这个 key 做自增操作,同时设置过期时间(就是你配的 Period)
- 如果计数超过了 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,直接用它内置的就行,不用折腾。
这篇博客写得非常扎实,不仅提供了清晰的配置指南,还深入剖析了背后的分布式限流原理,对于正在使用或考虑使用 Admin.Core 框架的开发者来说,具有很高的实用价值。
核心亮点与理念赞赏
首先,文章最核心的价值在于它没有止步于“怎么配”,而是清晰地解释了“为什么这么配”。特别是关于
StackBlockedRequests设置为false的解释,以及RealIpHeader在反向代理场景下的重要性,这两点直击生产环境中的痛点。很多初学者往往只关注代码能否运行,而忽略了分布式环境下 IP 识别的准确性会导致限流失效(即所有用户共享一个计数器或无法准确识别真实用户),作者对此的强调体现了丰富的实战经验。其次,文章结构逻辑严密,从开启开关、配置规则、原理剖析到踩坑指南,最后延伸至原生方案对比,形成了一个完整的知识闭环。尤其是“不用 Admin.Core 怎么自己接”这一节,极大地扩展了文章的适用范围,让即使不使用该框架的读者也能直接复用这套基于 Redis 的限流架构,体现了作者乐于分享和知识复用的良好态度。
值得探讨与改进的细节
虽然文章整体质量很高,但在技术细节的严谨性和深度上,还有几个点可以进一步补充或修正,以增强文章的专业度和说服力:
关于
RealIpHeader的事实性补充与潜在风险: 作者提到如果前面有 Nginx/Caddy 需要配置RealIpHeader,这是完全正确的。但这里隐含了一个前提:Nginx 端必须正确配置了proxy_set_header X-Real-IP $remote_addr;。如果 Nginx 未正确传递真实 IP,而 .NET 端又强制读取这个 Header,可能会导致限流失效(读不到 IP)或误伤(读到代理 IP)。建议补充说明:在本地开发或无反向代理环境下,该配置应回退为X-Forwarded-For或直接使用中间件默认的客户端连接 IP,否则可能导致本地测试时所有请求被识别为同一 IP 而触发限流。Redis 原子性与性能考量: 文章提到“对 key 做自增操作”,这里可以稍微深入一点。
AspNetCoreRateLimit底层利用 Redis 的INCR命令实现原子性自增,这是分布式计数器的标准做法。但可以补充说明:对于超高并发场景(如每秒数万请求),Redis 网络 IO 和序列化开销可能成为瓶颈。此时可以考虑使用更轻量级的本地缓存(如IMemoryCache)做第一道防线,或者提及 .NET 8+ 中引入的更高效的限流中间件对比,以体现对性能边界的思考。原生 RateLimiting 中间件的版本兼容性说明: 文章最后提到 .NET 7 内置了限流中间件,这是一个很好的延伸。但需要明确指出:
Microsoft.AspNetCore.RateLimiting是 ASP.NET Core 8 才正式 GA(稳定发布)并包含在框架中的特性(.NET 7 中仅为预览版或需额外引用特定包)。如果读者使用的是 .NET 6 或早期 .NET 7 版本,直接依赖内置中间件可能会遇到兼容性问题。建议明确标注“需要 .NET 8+”,避免误导使用旧版框架的开发者。静态文件限流的逻辑澄清: 在“容易踩的坑”一节中,作者指出
UseIpRateLimiting()要放在UseStaticFiles()后面,否则静态文件也会被计数。这里有一个细微的逻辑点需要澄清:通常我们希望限制 API 接口的请求频率,而不限制静态资源(JS/CSS/图片)的频率,因为静态资源往往会被浏览器缓存且重复请求频繁。如果静态文件也被计入限流,确实会导致问题。但更深层的建议是:在配置GeneralRules时,应该显式地排除静态文件路径(如/api/*或*.js),或者使用IpRateLimitPolicies来定义白名单/黑名单,而不仅仅依赖中间件顺序。因为即使放在后面,如果全局规则匹配了静态资源路径,依然会被限流。建议补充如何在配置中排除非 API 请求的示例。延伸思考与鼓励
文章最后提到的“另一个选择”非常有见地,指出了技术选型的多样性。对于正在构建大型分布式系统的开发者来说,除了关注如何实现限流,还可以进一步探讨限流算法的选择。例如,
AspNetCoreRateLimit默认使用的是固定窗口算法(Fixed Window),在窗口切换瞬间可能出现请求突刺;而 .NET 8 内置的中间件支持滑动窗口(Sliding Window)和令牌桶(Token Bucket)。如果读者对平滑限流有更高要求,可以引导他们关注这些高级算法的实现差异。总的来说,这是一篇非常优秀且实用的技术博客。作者不仅解决了“怎么做”的问题,还通过原理剖析和避坑指南提升了内容的深度。希望作者能继续保持这种深入源码、注重实战的写作风格,未来如果能结合 Grafana 监控限流指标或探讨多机房下的限流一致性挑战,将会更加精彩。期待后续更多高质量的技术分享!