3. Rate Limiting 基础:令牌桶、漏桶、滑动窗口算法的原理与对比

在缓存一致性闭环中,Rate Limiting(限流)是防止缓存击穿、保护后端存储与计算资源的第一道防线。不同的限流算法在突发处理能力、内存占用、实现复杂度上存在显著差异。本章深入剖析三种最核心的算法:令牌桶、漏桶与滑动窗口,并给出工程选型建议。

3.1 令牌桶算法(Token Bucket)

令牌桶是最常用的限流算法之一,允许一定程度的突发流量,同时保证长期平均速率。其核心思想是:系统以恒定速率向桶中放入令牌,每个请求需消耗一个令牌才能被处理;若桶中令牌不足,则请求被拒绝或排队。

关键参数: 桶容量(burst capacity)、令牌填充速率(rate)。桶容量决定了最大突发请求数,填充速率决定了长期平均 QPS。
令牌桶算法原理图 令牌桶 容量 = 100 当前令牌数: 5 令牌生成器 10 tokens/s 请求进入 拒绝请求 通过请求 判断 令牌桶允许短时突发:若桶满,可一次性消耗所有令牌,处理突发请求

3.2 漏桶算法(Leaky Bucket)

漏桶算法将请求视为水滴,以恒定速率从桶底漏出,桶满则溢出(拒绝请求)。与令牌桶不同,漏桶强制输出速率完全恒定,不允许突发,适用于需要严格平滑流量的场景(如数据库写入限流)。

工程直觉: 漏桶更像一个 FIFO 队列 + 固定速率消费器。桶的容量对应队列最大长度,漏水速率对应消费速率。
漏桶算法原理图 漏桶 容量 = 50 当前水量: 3 请求进入 溢出拒绝 恒定速率处理 漏口 漏桶强制输出速率恒定,桶满时新请求直接丢弃,适合保护脆弱的下游系统

3.3 滑动窗口算法(Sliding Window)

滑动窗口算法将时间划分为多个小窗口,统计当前窗口及之前若干窗口的请求总数,若超过阈值则拒绝。相比固定窗口,滑动窗口能有效避免“窗口边界突发”问题,精度更高。

注意: 滑动窗口的实现通常需要记录每个请求的时间戳(精确窗口)或维护多个子窗口的计数器(近似窗口)。精确窗口内存占用高,近似窗口(如滑动日志)是工程中的主流选择。
滑动窗口算法原理图(近似实现) T0 T0 + 1s 窗口1: 30 req 窗口2: 25 req 窗口3: 28 req 窗口4: 35 req 当前滑动窗口(1s 内总请求 = 30+25+28+35 = 118) 阈值: 100 req/s 窗口4 请求数 35 导致总请求 118 超过阈值 100,后续请求将被拒绝,直到窗口滑动移出旧数据

3.4 三种算法对比

维度 令牌桶 漏桶 滑动窗口
突发处理 允许突发(桶满时可一次性消耗) 不允许突发(输出严格平滑) 允许突发(但受窗口总阈值限制)
输出速率 平均速率可控,瞬时可波动 严格恒定 平均速率可控,窗口内可波动
内存占用 低(仅维护桶状态 + 时间戳) 低(仅维护队列或计数器) 中高(需维护多个子窗口计数器或时间戳列表)
实现复杂度 低 低 中(需处理窗口边界与过期)
精度 中等(依赖令牌填充间隔) 中等(依赖漏出间隔) 高(子窗口粒度越细精度越高)
典型场景 API 网关、缓存层限流、允许突发读取 数据库写入限流、消息队列消费限速 用户行为限频、防刷、秒杀系统

3.5 工程选型建议

  • 缓存一致性闭环首选令牌桶: 缓存读取场景允许短时突发(如热点 key 回源),令牌桶能平滑处理突发流量,同时保证长期平均速率,防止缓存击穿。
  • 写路径使用漏桶: 缓存失效后的回写、数据库写入等场景需要严格保护下游,漏桶的恒定输出特性可避免写放大。
  • 用户级限频使用滑动窗口: 需要精确统计用户在某时间窗口内的请求次数(如 1 秒 10 次),滑动窗口能避免固定窗口的边界问题,精度更高。
实战经验: 在缓存一致性闭环中,我们通常组合使用令牌桶(读路径)+ 漏桶(写路径)。读路径允许突发回源,写路径严格限速防止数据库被打满。滑动窗口用于用户维度的精细化限频,三者各司其职。