c# 接口限流的实现 c# 令牌桶算法和漏桶算法

来源:这里教程网 时间:2026-02-21 17:40:39 作者:

为什么直接用
Microsoft.Extensions.RateLimiting
而不是手写令牌桶

ASP.NET Core 7+ 内置的限流中间件已默认采用令牌桶(

TokenBucketRateLimiter
)实现,且做了线程安全、跨请求共享、支持分布式(配合
IDistributedCache
)等关键优化。手写容易漏掉:
Interlocked
竞态处理、滑动窗口时间精度、突发流量下的令牌预分配逻辑。除非你明确需要自定义填充策略(比如按 CPU 使用率动态调速),否则不建议从零实现。

TokenBucketRateLimiter
的核心配置参数怎么设才合理

关键参数不是“桶大小”和“填充速率”两个数字,而是它们与业务响应时间的耦合关系。例如:接口平均耗时 200ms,但允许最多 5 个并发请求瞬间打进来,那么

PermitLimit
至少为 5;若希望每秒最多放行 10 次,则
QueueProcessingOrder
设为
Fifo
QueueLimit
建议不超 5(避免排队过长导致超时),
ReplenishmentPeriod
设为
TimeSpan.FromSeconds(1)
TokensPerPeriod
设为 10。

PermitLimit
:桶容量,决定最大并发请求数,不是 QPS —— 它限制的是“同时能抢到令牌的请求数”
TokensPerPeriod
ReplenishmentPeriod
共同决定平均吞吐,但不保证每秒精确放行——令牌是随时间匀速填充的,不是定时触发
若接口 P99 响应时间达 2s,
QueueLimit
设太高会导致后续请求在队列里等满 30s 才被拒绝,应设为
Math.Min(5, (int)(30 / avgResponseSeconds))

漏桶算法在 C# 里其实很少单独用

漏桶强调恒定输出速率,天然适合做“削峰填谷”,但 .NET 生态中几乎没有开箱即用的漏桶限流器。

Microsoft.Extensions.RateLimiting
不提供漏桶实现,第三方库如
AspNetCoreRateLimit
也只支持滑动窗口或固定窗口。真要模拟漏桶,得自己维护一个按固定间隔出队的
ConcurrentQueue
+
Timer
,但会引入时钟漂移、GC 暂停导致漏速不准等问题。实际项目中,更常见的是用消息队列(如 RabbitMQ 的
x-max-length
+
x-overflow=reject-publish
)在网关层做漏桶语义。

分布式场景下令牌桶怎么保持一致性

单机

TokenBucketRateLimiter
默认用内存存储,集群部署时必须切换为分布式模式。关键不是换存储,而是选对序列化方式:
IDistributedCache
存的是
TokenBucketState
结构体,含
AvailableTokens
LastReplenishedUtc
等字段。Redis 是首选,但要注意:
StackExchange.Redis
StringGetAsync
+
StringSetAsync
必须用 Lua 脚本保证原子性,否则多个实例可能同时读到旧值并各自填充,导致超发。官方限流器已内置该脚本,只需确认你用的是
RedisRateLimiter
(.NET 8+)或配置了
AddDistributedRateLimiter
并注入
RedisCache
实例。

services.AddRateLimiter(options =>
{
    options.AddPolicy("api", context =>
        context.Request.RouteValues["controller"]?.ToString() == "Api"
            ? new TokenBucketRateLimiterOptions
            {
                PermitLimit = 10,
                TokensPerPeriod = 10,
                ReplenishmentPeriod = TimeSpan.FromSeconds(1),
                QueueLimit = 3
            }
            : new FixedWindowRateLimiterOptions { PermitLimit = 100, Window = TimeSpan.FromMinutes(1) });
});

真正难的不是配参数,而是理解“令牌桶”本质是个带状态的时间函数——它把请求速率映射成一个可加减的整数,而这个整数在分布式环境下必须靠强一致存储兜底。多数人卡在 Redis 连接超时没重试、缓存键没加前缀导致多服务冲突、或者误把

QueueLimit
当作总请求数限制。

相关推荐