固定窗口限流用 RateLimitingMiddleware
配合 FixedWindowRateLimiter
ASP.NET Core 8+ 内置的限流中间件默认不启用任何策略,必须显式注册并配置。固定窗口的核心是“每 N 秒重置计数”,比如每 60 秒最多 100 次请求。
常见错误是只调用
AddRateLimiter却没指定具体策略类型,导致运行时报
InvalidOperationException: No rate limiter policy is configured。 在
Program.cs中注册时,必须用
AddFixedWindowLimiter并传入策略名(如
"fixed")和配置委托
Window单位是
TimeSpan,写成
TimeSpan.FromMinutes(1)比硬写
60000毫秒更安全 注意
PermitLimit是窗口内总允许请求数,不是并发数;超限后默认返回 429,且不排队
builder.Services.AddRateLimiter(options =>
{
options.AddFixedWindowLimiter("fixed", options =>
{
options.Window = TimeSpan.FromMinutes(1);
options.PermitLimit = 100;
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = 0; // 不排队,直接拒绝
});
});
滑动窗口限流需手动实现或换用第三方库
ASP.NET Core 原生
RateLimiter目前(.NET 8 / 9 Preview)**不提供内置滑动窗口限流器**。官方只实现了
FixedWindowRateLimiter和
SlidingWindowRateLimiter的抽象基类,但后者未公开具体实现。
这意味着你不能像固定窗口那样直接调用
AddSlidingWindowLimiter—— 它根本不存在于
RateLimiterOptions扩展方法中。 若坚持用原生方案,只能基于
IServerRateLimiter自定义实现,需维护时间分片(如 10 个 6 秒桶),处理跨桶聚合,逻辑复杂且易出错 更现实的做法是引入
AspNetCoreRateLimit(已归档)或
Microsoft.Extensions.Resilience中的
RateLimiter(.NET 8+ 实验性 API,仍不稳定) 生产环境推荐用
StackExchange.Redis+ Lua 脚本实现分布式滑动窗口,避免内存泄漏和节点间不同步
中间件启用时必须指定策略名,且顺序不能错
注册完策略只是第一步,中间件本身需要明确告诉它“对哪些请求应用哪个策略”。漏掉
UseRateLimiter或策略名拼错,限流完全不生效,连日志都不会打。
UseRateLimiter()必须放在
UseRouting()之后、
UseEndpoints()之前 策略名(如
"fixed")要和注册时完全一致,大小写敏感 若想按路由或 Header 区分策略,得用
ConfigureRateLimiter+
EndpointRateLimiter,而不是全局统一策略
app.UseRouting();
app.UseRateLimiter(); // 必须在这里
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers().RequireRateLimiting("fixed");
});
滑动窗口的“平滑”特性容易被误读为“更宽松”
滑动窗口不是简单地把固定窗口切碎再叠加,它的计数是动态滚动的:例如“每 60 秒最多 100 次”,第 59 秒发起的请求会同时计入第 1 秒和第 60 秒两个窗口片段,实际允许短时突发更高——但这不是 bug,是设计使然。
如果你发现滑动窗口比预期放行更多请求,先确认是否混淆了“窗口长度”和“滑动粒度”。比如 Redis 实现中用 6 秒分片 × 10 片 = 60 秒窗口,但每个请求只更新当前分片,查询时 sum 最近 10 个分片值。这个 sum 过程如果没原子执行,就会出现竞态漏计。
单机滑动窗口用ConcurrentDictionary<datetime int></datetime>加锁维护,性能差、GC 压力大,不建议 真正可靠的滑动窗口几乎都依赖外部存储(Redis、SQL Server 的行级锁 + TTL) 别为了“听起来高级”强行上滑动窗口——固定窗口在多数 API 场景下更稳定、更容易监控和调试
