3. Rate Limiting 基础:令牌桶、漏桶、滑动窗口算法的原理与对比
在缓存一致性闭环中,Rate Limiting(限流)是防止缓存击穿、保护后端存储与计算资源的第一道防线。不同的限流算法在突发处理能力、内存占用、实现复杂度上存在显著差异。本章深入剖析三种最核心的算法:令牌桶、漏桶与滑动窗口,并给出工程选型建议。
3.1 令牌桶算法(Token Bucket)
令牌桶是最常用的限流算法之一,允许一定程度的突发流量,同时保证长期平均速率。其核心思想是:系统以恒定速率向桶中放入令牌,每个请求需消耗一个令牌才能被处理;若桶中令牌不足,则请求被拒绝或排队。
关键参数: 桶容量(burst capacity)、令牌填充速率(rate)。桶容量决定了最大突发请求数,填充速率决定了长期平均 QPS。
3.2 漏桶算法(Leaky Bucket)
漏桶算法将请求视为水滴,以恒定速率从桶底漏出,桶满则溢出(拒绝请求)。与令牌桶不同,漏桶强制输出速率完全恒定,不允许突发,适用于需要严格平滑流量的场景(如数据库写入限流)。
工程直觉: 漏桶更像一个 FIFO 队列 + 固定速率消费器。桶的容量对应队列最大长度,漏水速率对应消费速率。
3.3 滑动窗口算法(Sliding Window)
滑动窗口算法将时间划分为多个小窗口,统计当前窗口及之前若干窗口的请求总数,若超过阈值则拒绝。相比固定窗口,滑动窗口能有效避免“窗口边界突发”问题,精度更高。
注意: 滑动窗口的实现通常需要记录每个请求的时间戳(精确窗口)或维护多个子窗口的计数器(近似窗口)。精确窗口内存占用高,近似窗口(如滑动日志)是工程中的主流选择。
3.4 三种算法对比
| 维度 | 令牌桶 | 漏桶 | 滑动窗口 |
|---|---|---|---|
| 突发处理 | 允许突发(桶满时可一次性消耗) | 不允许突发(输出严格平滑) | 允许突发(但受窗口总阈值限制) |
| 输出速率 | 平均速率可控,瞬时可波动 | 严格恒定 | 平均速率可控,窗口内可波动 |
| 内存占用 | 低(仅维护桶状态 + 时间戳) | 低(仅维护队列或计数器) | 中高(需维护多个子窗口计数器或时间戳列表) |
| 实现复杂度 | 低 | 低 | 中(需处理窗口边界与过期) |
| 精度 | 中等(依赖令牌填充间隔) | 中等(依赖漏出间隔) | 高(子窗口粒度越细精度越高) |
| 典型场景 | API 网关、缓存层限流、允许突发读取 | 数据库写入限流、消息队列消费限速 | 用户行为限频、防刷、秒杀系统 |
3.5 工程选型建议
- 缓存一致性闭环首选令牌桶: 缓存读取场景允许短时突发(如热点 key 回源),令牌桶能平滑处理突发流量,同时保证长期平均速率,防止缓存击穿。
- 写路径使用漏桶: 缓存失效后的回写、数据库写入等场景需要严格保护下游,漏桶的恒定输出特性可避免写放大。
- 用户级限频使用滑动窗口: 需要精确统计用户在某时间窗口内的请求次数(如 1 秒 10 次),滑动窗口能避免固定窗口的边界问题,精度更高。
实战经验: 在缓存一致性闭环中,我们通常组合使用令牌桶(读路径)+ 漏桶(写路径)。读路径允许突发回源,写路径严格限速防止数据库被打满。滑动窗口用于用户维度的精细化限频,三者各司其职。