令牌桶算法适合控制平均速率和突发流量,Go可用rate.Limiter实现;支持按用户/IP精细化限流;分布式场景推荐Redis+Lua脚本;需增强可观测性与降级能力。

基于令牌桶的限流实现
令牌桶算法适合控制请求的平均速率和突发流量。Golang标准库没有直接提供,但可用 golang.org/x/time/rate 包轻松实现。
核心是 rate.Limiter,它内部维护一个按固定速率填充令牌的桶。每次请求调用 Allow() 或 Wait() 判断是否可被处理:
- Allow() 立即返回 true/false,不阻塞
- Wait() 阻塞等待令牌(可设超时),适合需强控延迟的场景
示例:每秒最多允许 10 个请求,最大突发 5 个:
limiter := rate.NewLimiter(10, 5)
http.HandleFunc("/api/data", func(w http.ResponseWriter, r *http.Request) {
if !limiter.Allow() {
http.Error(w, "Too many requests", http.StatusTooManyRequests)
return
}
// 处理业务逻辑
})
按用户或IP维度做精细化限流
全局限流不够灵活,常需区分来源。可在中间件中提取标识(如 X-Forwarded-For、Authorization 或自定义 header),为每个标识维护独立限流器。
立即学习“go语言免费学习笔记(深入)”;
使用 sync.Map 缓存限流器,避免高频新建对象:
- 键为用户ID或IP字符串
- 值为 *rate.Limiter,带过期机制(如用 time.AfterFunc 清理闲置项)
- 注意并发安全:sync.Map 的 LoadOrStore 可原子完成“查+建”逻辑
简单示意:
var limiters sync.Map
func getLimiter(key string) *rate.Limiter {
if lim, ok := limiters.Load(key); ok {
return lim.(*rate.Limiter)
}
lim := rate.NewLimiter(5, 2) // 每用户每秒5次,突发2次
limiters.Store(key, lim)
return lim
}
结合Redis实现分布式限流
单机限流在多实例部署下失效,需共享状态。Redis 的 INCR + EXPIRE 原子组合是常用方案,或用 Redis Lua 脚本保证操作完整性。
推荐使用 github.com/redis/go-redis/v9,配合 Lua 脚本一次完成计数、设置过期、判断阈值:
- 脚本接收 key、窗口时间(秒)、最大请求数作为参数
- 返回 1 表示放行,0 表示拒绝
- Golang 中用 rdb.Eval(ctx, script, []string{key}, windowSec, maxReq) 调用
优势是状态集中、各节点行为一致,且支持按用户/IP/接口路径等任意维度组合限流键。
限流结果的可观测与降级响应
限流不是终点,而是服务治理一环。建议在拦截时记录日志或上报指标(如 Prometheus Counter),便于分析攻击特征或容量瓶颈。
- HTTP 响应头中添加 X-RateLimit-Limit、X-RateLimit-Remaining、X-RateLimit-Reset,提升客户端友好度
- 对关键接口,可配置“熔断+限流”双策略:当错误率持续超标时,自动降级为更宽松限流或返回兜底数据
- 避免限流器本身成为性能瓶颈——缓存限流器、复用 Redis 连接、限制 Lua 脚本复杂度
不复杂但容易忽略。










