4. 令牌桶限流算法原理
令牌桶(Token Bucket)是业界应用最广泛的限流算法之一,其核心思想是通过一个“桶”来平滑突发流量,同时保证长期平均速率可控。本章从数学模型出发,深入剖析令牌桶的工作机制,并与漏桶算法进行对比,帮助你在实际项目中做出正确的技术选型。
4.1 令牌桶算法数学模型
令牌桶算法包含三个核心参数:
- 桶容量(b):桶中最多可存放的令牌数,决定了允许的突发流量上限。
- 令牌生成速率(r):单位时间内向桶中添加的令牌数量,决定了长期平均速率。
- 当前令牌数(c):桶中当前持有的令牌数量,初始值为桶容量 b。
算法执行流程如下:
- 系统以固定速率 r 向桶中添加令牌,若桶已满则丢弃多余令牌。
- 每个请求到达时,尝试从桶中获取 1 个(或多个)令牌。
- 若桶中令牌数 ≥ 请求所需令牌数,则放行请求,并从桶中扣除对应令牌。
- 若桶中令牌数不足,则拒绝请求或等待令牌补充。
关键公式: 在时间间隔 Δt 内,桶中令牌数的变化量为:
其中 consumed 为 Δt 内消耗的令牌总数。
c(t+Δt) = min(b, c(t) + r × Δt - consumed)其中 consumed 为 Δt 内消耗的令牌总数。
实践要点: 令牌桶的“突发”能力由桶容量 b 决定。例如,设置 r=100 req/s、b=200,则系统允许瞬间 200 个请求并发通过,但长期平均速率仍被限制在 100 req/s。
4.2 令牌桶 vs 漏桶算法对比
漏桶(Leaky Bucket)算法与令牌桶常被混淆,但二者在流量整形逻辑上存在本质差异。下表从多个维度进行对比:
| 对比维度 | 令牌桶算法 | 漏桶算法 |
|---|---|---|
| 核心机制 | 以固定速率生成令牌,请求消耗令牌 | 以固定速率漏水,请求注入桶中 |
| 突发处理 | 允许突发(桶容量内) | 严格平滑,不允许突发 |
| 长期速率 | 严格等于令牌生成速率 r | 严格等于漏水速率 |
| 流量整形效果 | 允许短时超速,但平均速率可控 | 完全平滑,输出流量恒定 |
| 实现复杂度 | 中等(需定时器或时间戳计算) | 较低(队列 + 定时器) |
| 典型应用 | API 限流、微服务网关 | 网络流量整形、数据流控 |
避坑提示: 不要将令牌桶与漏桶混用。如果业务需要容忍突发流量(如秒杀场景),请选择令牌桶;如果业务要求绝对平滑的输出速率(如视频流推送),请选择漏桶。
4.3 令牌桶算法工作流程(SVG)
下图展示了令牌桶算法的完整工作流程,包括令牌生成、请求到达、令牌消耗与拒绝等关键环节:
4.4 适用场景分析
不同的限流算法适用于不同的业务场景。以下是令牌桶与漏桶的典型适用场景对比:
| 场景类型 | 推荐算法 | 原因 |
|---|---|---|
| API 网关限流 | 令牌桶 | 允许突发请求,用户体验好;长期速率可控 |
| 秒杀/抢购系统 | 令牌桶 | 瞬间高并发需要突发能力,桶容量可缓冲流量 |
| 视频流推送 | 漏桶 | 需要恒定码率输出,避免网络抖动 |
| 数据库连接池 | 令牌桶 | 允许短时连接峰值,保护数据库不被压垮 |
| 日志采集系统 | 漏桶 | 需要平滑写入,避免后端存储过载 |
| 微服务熔断降级 | 令牌桶 | 配合熔断器实现快速失败与恢复 |
选型建议: 在 Go 微服务架构中,90% 的限流场景推荐使用令牌桶算法。Go 标准库
golang.org/x/time/rate 提供了高效的令牌桶实现,可直接用于生产环境。漏桶算法更适合网络层流量整形,在应用层限流中使用较少。
4.5 令牌桶与漏桶对比(SVG)
下图从流量整形效果的角度,直观对比令牌桶与漏桶在处理突发流量时的差异:
4.6 Go 语言实现示例
Go 标准库 golang.org/x/time/rate 提供了高效的令牌桶实现。以下是一个简单的使用示例:
package main
import (
"fmt"
"time"
"golang.org/x/time/rate"
)
func main() {
// 创建令牌桶:每秒生成 10 个令牌,桶容量为 20
limiter := rate.NewLimiter(10, 20)
// 模拟 30 个请求
for i := 0; i < 30; i++ {
if limiter.Allow() {
fmt.Printf("请求 %d 放行\n", i)
} else {
fmt.Printf("请求 %d 被限流\n", i)
}
time.Sleep(50 * time.Millisecond)
}
}
代码说明:
rate.NewLimiter(r, b) 创建令牌桶,r 为每秒令牌生成数,b 为桶容量。Allow() 方法尝试获取一个令牌,返回 true 表示放行,false 表示限流。如需等待令牌,可使用 Wait() 方法。
4.7 本章小结
- 令牌桶算法通过“桶容量”和“令牌生成速率”两个参数,实现了允许突发且长期速率可控的限流效果。
- 漏桶算法强制输出速率恒定,适合需要严格平滑流量的场景。
- 在 Go 微服务架构中,
golang.org/x/time/rate是首选的令牌桶实现,性能优异且 API 简洁。 - 选型时需根据业务是否允许突发流量来决定使用令牌桶还是漏桶。