3. 限流算法原理:固定窗口、滑动窗口、令牌桶、漏桶对比
在分布式系统与 gRPC 微服务架构中,限流是保障服务稳定性的第一道防线。不同的限流算法在突发处理能力、内存开销、实现复杂度上各有取舍。本章从原理出发,对比四种经典限流算法,帮助你在 gRPC 拦截器或 API 网关中做出正确选型。
3.1 固定窗口算法(Fixed Window)
原理:将时间划分为固定长度的时间窗口(如 1 秒),每个窗口内维护一个计数器。请求到达时计数器加 1,若计数器超过阈值则拒绝;窗口结束时计数器归零。
核心特征:实现极简,仅需一个计数器 + 窗口起始时间戳;但存在“窗口临界突发”问题——窗口切换瞬间可能涌入 2 倍阈值流量。
| 维度 | 说明 |
|---|---|
| 优点 | 实现简单,内存占用极低(仅 1 个计数器 + 1 个时间戳) |
| 缺点 | 窗口边界突发流量可达阈值的 2 倍;精度受窗口大小限制 |
| 适用场景 | 对突发不敏感、实现成本优先的简单限流 |
3.2 滑动窗口算法(Sliding Window)
原理:将固定窗口进一步细分为多个小格子(如 1 秒窗口分为 10 个 100ms 子窗口)。每个子窗口独立计数,滑动窗口的总请求数为当前时间点前一个完整窗口内所有子窗口计数之和。滑动步长通常为子窗口粒度。
实践建议:滑动窗口是固定窗口的改进版,通过增加子窗口数量来平滑突发。在 gRPC 限流中,推荐使用 10~20 个子窗口,精度与内存开销达到较好平衡。
| 维度 | 说明 |
|---|---|
| 优点 | 流量平滑,无窗口边界突发;精度可通过子窗口数量调节 |
| 缺点 | 需要维护多个子窗口计数器,内存开销随子窗口数线性增长 |
| 适用场景 | 对流量突发敏感、需要较高精度的限流场景 |
3.3 令牌桶算法(Token Bucket)
原理:系统以恒定速率向桶中放入令牌,桶有最大容量。每个请求消耗一个或多个令牌。若桶中有足够令牌则放行,否则拒绝或等待。令牌桶允许一定程度的突发——桶中累积的令牌可被一次性消耗。
gRPC 最佳实践:令牌桶是 gRPC 限流中最常用的算法。配合
gRPC Interceptor 可实现每个方法或每个客户端的独立令牌桶,支持突发流量又不至于击穿后端。
| 维度 | 说明 |
|---|---|
| 优点 | 支持突发流量;令牌放入速率与消耗速率解耦;实现灵活 |
| 缺点 | 需要维护桶状态(令牌数 + 上次填充时间);突发可能仍会冲击后端 |
| 适用场景 | 允许短时突发、需要平滑长期速率的场景(gRPC 首选) |
3.4 漏桶算法(Leaky Bucket)
原理:将请求比作水,桶底部有一个固定速率的漏孔。水以任意速率流入桶中,但以恒定速率从漏孔流出(处理请求)。若桶满则溢出(请求被拒绝)。漏桶强制输出速率完全平滑,不允许任何突发。
注意:漏桶算法与令牌桶的核心区别在于——漏桶强制输出速率恒定,适合需要严格限制出口流量的场景;令牌桶允许输出速率短时超过平均速率。
| 维度 | 说明 |
|---|---|
| 优点 | 输出速率绝对平滑,无任何突发;实现简单 |
| 缺点 | 无法应对突发流量;桶满时直接丢弃请求,不够灵活 |
| 适用场景 | 需要严格限制出口速率、不允许突发的场景(如数据库写入限流) |
3.5 四种算法综合对比
| 算法 | 突发处理 | 内存开销 | 实现复杂度 | 输出平滑度 | gRPC 推荐度 |
|---|---|---|---|---|---|
| 固定窗口 | 差(边界突发 2 倍) | 极低 | 极低 | 低 | ★☆☆☆☆ |
| 滑动窗口 | 中(取决于子窗口数) | 中 | 中 | 中 | ★★★☆☆ |
| 令牌桶 | 好(允许累积突发) | 低 | 中 | 中 | ★★★★★ |
| 漏桶 | 无(强制平滑) | 低 | 低 | 极高 | ★★☆☆☆ |
选型建议:在 gRPC 微服务限流中,令牌桶是最均衡的选择——既能容忍短时突发,又不会让后端持续过载。滑动窗口适合需要精确控制每秒请求数的场景。漏桶适合下游能力严格固定的场景(如单库写入 TPS 限制)。固定窗口仅用于原型或极低精度场景。