.net 7+ 内置 microsoft.aspnetcore.ratelimiting 是最稳、最轻、最易维护的限流方案,需正确注册服务、启用中间件(useratelimiter 必须在 useauthentication/authorization 之后、useendpoints 之前),并精准配置策略名、滑动窗口分段、限流键生成器及 onrejected 响应。

直接用 Microsoft.AspNetCore.RateLimiting(.NET 7+ 内置)是当前最稳、最轻、最易维护的方案。第三方库如 AspNetCoreRateLimit 虽仍可用,但已非首选——它依赖额外注册、易因中间件顺序或缓存服务缺失而静默失效。
必须先注册服务再启用中间件
很多人加了 AddRateLimiter() 却没效果,根本原因是漏掉了 UseRateLimiter(),或把它放在了错误位置。
-
UseRateLimiter()必须在UseAuthentication()和UseAuthorization()之后、UseEndpoints()或MapControllers()之前 - 若用了
[Authorize]且想按用户限流,UseAuthentication()还得在UseRateLimiter()前面,否则HttpContext.User为空 - 策略名(如
"sliding")在AddRateLimiter()和[EnableRateLimiting("sliding")]中必须完全一致,大小写敏感
滑动窗口限流(SlidingWindowLimiter)最常用但参数易误解
它比固定窗口更抗突发流量,但配置项含义和实际行为常被搞错。
-
Window = TimeSpan.FromMinutes(1)不代表“每分钟重置”,而是滚动统计最近 60 秒内的请求数 -
SegmentsPerWindow = 6表示把这 1 分钟切成 6 段(每段 10 秒),用于更平滑计数;不设默认为 1,退化为固定窗口 -
QueueLimit = 0是关键:设为 0 则超限请求立刻返回 429;设为正数会排队,但可能掩盖真实过载,生产环境建议关掉 -
ReplenishmentPeriod对滑动窗口无效,只对TokenBucketLimiter生效
按用户而非 IP 限流必须自定义 KeyGenerator
默认限流键只取 RemoteIpAddress,所有请求都算同一个桶。要按 JWT 用户 ID 或 API Key 区分,就得自己写提取逻辑。
- 在
AddSlidingWindowLimiter()配置里传入keyGenerator函数,例如:key => key.User.FindFirst("sub")?.Value ?? key.Connection.RemoteIpAddress.ToString() - 未认证用户 fallback 到 IP 是合理做法,避免匿名用户共享额度
- 别在
KeyGenerator里做耗时操作(如查 DB),它在每次请求时同步执行 - 如果用
X-Api-Key头,记得检查Request.Headers.TryGetValue("X-Api-Key", out var key)是否成功,空值会导致 fallback 到 IP
429 响应体为空?检查是否被异常中间件吞了
限流触发后本该返回 429,但你看到的是空响应或 500,大概率是全局异常处理中间件(如 UseExceptionHandler())捕获了 RateLimitingFailureException 并静默吞掉。
- 不要在
try/catch里包裹限流逻辑,它由中间件自动抛出 - 若需自定义 429 响应体,用
OnRejected回调,例如设置响应头或写 JSON:options.OnRejected = async (ctx, _) => { ctx.Response.StatusCode = 429; await ctx.Response.WriteAsJsonAsync(new { error = "Too many requests" }); }; - 注意:这个回调里不能再 throw 异常,否则又进异常中间件循环
真正难的不是配策略,而是判断「谁该被限」和「限多狠」——IP 维度太粗,用户维度要考虑未登录场景,API Key 维度得防伪造。这些逻辑一旦写进 KeyGenerator,就很难动态调整,上线前务必用压测工具验证桶隔离是否生效。











