令牌桶限流器¶
30 秒版(开场)¶
令牌桶:以速率 rate 补充令牌,上限 burst;请求消耗 1 令牌,无令牌则拒绝或等待。它限制长期平均速率,同时允许短时 burst,不保证输出流量平滑;严格整形更接近漏桶。Go 手写:每次
Allow先按 elapsed 补令牌再扣减。
3 分钟版(精讲深度)¶
- 是什么:相对 漏桶(固定流出速率),令牌桶允许积累 burst,更适合 API 网关。
- 为什么:保护下游、防刷、公平配额;与 S-ARCH-08 限流 系统设计题呼应。
- 怎么做:
tokens += elapsed * rate,cap 到burst;tokens >= 1则--并 return true;全程Mutex保护。
10 分钟版(原理 + 图示)¶
flowchart LR
R[Rate 补充] --> B[(Tokens ≤ burst)]
B -->|Allow 消耗 1| OK[通过]
B -->|不足| REJ[拒绝/等待]
与漏桶对比
| 令牌桶 | 漏桶 | |
|---|---|---|
| burst | 允许 | 不允许(平滑输出) |
| 实现 | 时间差补令牌 | 固定间隔漏水 |
| 典型 | API 限流 | 网络流量整形 |
核心公式
tokens = min(burst, tokens + (now - lastRefill).Seconds() * rate)
生产场景¶
- 单机网关:
Allow()快速判断 - 分布式:Redis + Lua / 中心化服务(本题为单机手写)
- 生产推荐:
golang.org/x/time/rate.Limiter(已实现且经 battle test)
排查与工具¶
- 压测验证 burst 后是否按 rate 恢复
go test ./ratelimit/...
架构取舍¶
| 方案 | 适用 |
|---|---|
| 手写 Mutex 桶 | 编码练习 / 中等负载 |
| x/time/rate | 生产默认 |
| 滑动窗口 | 更严的「固定窗口 QPS」统计 |
深挖问答¶
- Wait 怎么实现? → 在锁内计算缺少令牌所需时间,锁外用
time.Timer等待,并同时监听ctx.Done();唤醒后重新计算,避免 busy loop。 - AllowN(n) 呢? → 补令牌后若
tokens >= n扣 n。 - 多 goroutine 并发? → Mutex;高 QPS 可分片或 atomic(实现复杂)。
- 和 semaphore 区别? → 信号量控制并发数;令牌桶控制 速率。
反模式与事故¶
- 不 cap burst → 令牌无限涨,失去限流意义
- 用 int 令牌不用 float → 低速率(0.5 QPS)精度差
- 分布式各机独立桶 → 总 QPS = N × limit,需 Redis 协调
- 构造时接受
rate<=0或burst<=0→ 除零、永久拒绝或错误容量;必须校验参数
代码示例¶
见 examples/senior/ratelimit/token_bucket.go:
func (tb *TokenBucket) Allow() bool {
tb.mu.Lock()
defer tb.mu.Unlock()
tb.refill()
if tb.tokens < 1 {
return false
}
tb.tokens -= 1
return true
}
func (tb *TokenBucket) Wait(ctx context.Context) error {
// 实现见示例:计算等待时间,Timer + ctx,醒来后循环复核。
}
cd examples/senior && go test ./ratelimit/...