这是 Beta 探索课程,内容结构、实验步骤和示例可能会继续调整。
限流算法
算法 1:固定窗口
思路:把时间切成等长的「桶」(常见是按整秒),每个桶里单独计数;进入新桶时计数清零重新算。
固定窗口:按「整秒」切块计数
一次请求在系统中的判断路径
新请求到达
对齐到当前秒的窗口起点
例如整秒边界:…59、…00
读取该窗口内的已用次数
键通常包含:用户 + 窗口标识
未达上限 计数 +1,放行
已达上限 拒绝或排队
边界问题:相邻两个窗口各放行 1 次,真实间隔可以很短
规则示例:每「整秒窗口」最多 1 次请求
窗口 A 上一秒
0.9s 请求
计数未满 → 允许窗口 B 下一秒(新窗口)
1.0s 请求
新窗口计数从零开始 → 与 A 内最后一次可紧挨着放行从 0.9s 到 1.0s 只相隔 0.1 秒,却跨了两个窗口,各算「本秒第一次」—— 对「平滑到任意 1 秒滑动区间」来说会偏松。
小结:实现和存储都简单,但桶与桶交界处容易出现「两桶各放行一点」的突刺,对需要严格平滑的场景不够理想。
算法 2:滑动窗口
思路:不依赖对齐到整秒的边界,而是始终统计「当前时刻往前一段固定长度」内的请求数量;每次判断前丢掉这段以外的旧记录。
滑动窗口:始终看「过去固定长度」这一段时间
时间轴上的窗口在「滑动」
下图灰色条表示长度为 1 秒的观察区间,随当前时刻一起向右平移。
较早请求
当前时刻
过去
未来
落在窗口左边界之外的旧记录会被丢弃,只统计窗口内的次数。
典型实现思路(有序集合)
删掉窗口之外的旧时间戳
只保留「当前时刻 − 窗口长度」之后的记录
统计窗口内已有多少次请求
与上限比较
仍有余量 写入本次时间戳,放行
已满 限流
相比固定窗口,任意连续相同长度的时间段内,请求分布更「均匀」,边界突刺更小。
小结:在任意同样长度的连续时间区间里,流量更均匀,更接近「这段时间内最多 N 次」的直觉。
算法 3:令牌桶
思路:桶有容量上限;令牌按恒定速率进入桶(满了就停);请求到达时尝试取走一枚令牌,取到才放行。
令牌桶:按固定速率「补水」,桶满则存住突发额度
结构示意
容量有上限
令牌堆积在桶内
令牌堆积在桶内
恒定速率向桶内投放令牌(至满为止)
请求处理流程
读取桶内令牌数与上次更新时间
按经过的时间补充令牌
补充量 = 时间差 × 发放速率,且不超过桶容量
令牌 ≥ 1 消耗 1 枚,放行
令牌不足 限流或等待
突发为何被允许?
阶段 1
空闲时令牌在桶内堆满
阶段 2
短时间大量请求依次「取票」通过
阶段 3
桶空后,只能按发放速率慢慢恢复
长期平均流量仍由「发放速率」约束;短期峰值由「桶容量」吸收。
小结:长期平均速率由「投放速率」管住,短期可以吃掉桶里攒下的令牌,因此允许可控突发,很适合对外 API 与网关类场景。
算法对比
三种算法怎么选
固定窗口
- 实现难度
- 低
- 平滑度
- 差(边界易突刺)
- 突发
- 基本不支持
- 内存
- 低
- 常见用途
- 原型、极低流量、容忍粗糙
滑动窗口
- 实现难度
- 中
- 平滑度
- 好
- 突发
- 不强调突发
- 内存
- 中(需保留窗口内事件)
- 常见用途
- 需要更均匀的任意区间限流
令牌桶
- 实现难度
- 中高
- 平滑度
- 好(长期平滑)
- 突发
- 支持(受桶容量约束)
- 内存
- 低(常存少量状态)
- 常见用途
- 生产环境、API 网关常见默认思路