直接用golang.org/x/time/rate即可,rate.newlimiter(10,10)表示长期10qps、瞬时容许10次请求;应按ip或用户id分桶复用实例,优先wait(ctx)而非allow(),reserven需显式fulfill或cancelat。

Go 里做速率限流,直接用 golang.org/x/time/rate 就行,别自己手写或套第三方轮子——它已通过高并发、时钟漂移、context 取消等真实压测验证,精度和并发安全都有保障。
rate.NewLimiter 参数怎么设才不翻车
常见错误是把 rate.NewLimiter(10, 1) 当成“每秒放行 10 次”,实际是“桶最多存 1 个令牌,每秒补 10 个”。第一秒只放过 1 次,其余全拒。
-
rate.Limit是每秒补充令牌数(float64类型),可设0.1表示“每 10 秒 1 次”,但低于0.01会因纳秒级精度丢失导致行为异常 -
burst是桶最大容量(int),必须 ≥rate.Limit值,否则构造会 panic;设太小(如 1)等于放弃突发容忍,用户连点两次刷新就 429;设太大(如 1000)等于形同虚设 - 合理配比示例:
rate.NewLimiter(10, 10)表示长期均值 10 QPS、允许瞬时 10 次请求打进来
HTTP 中间件该用 Wait 还是 Allow
绝大多数 API 限流场景该用 limiter.Wait(ctx),不是 Allow()。后者只返回 bool,没法透出等待时间、剩余令牌数,也难加标准响应头。
-
Wait(ctx)阻塞直到拿到令牌或 context 超时/取消,天然匹配 HTTP handler 的串行模型 - 务必传带 deadline 的 context,例如
ctx, cancel := context.WithTimeout(r.Context(), 200*time.Millisecond);否则客户端断连后 goroutine 卡死,连接池缓慢耗尽 -
Allow()仅适合极低延迟、允许快速失败的场景,比如健康检查探针或日志上报这类可降级路径
按 IP 或用户 ID 限流为什么不能共用一个 Limiter 实例
*rate.Limiter 本身并发安全,但它不绑定任何标识——同一个实例无法区分用户 A 和用户 B 的请求。共用实例会导致恶意用户刷爆配额,把其他用户全堵死。
- 必须为每个 key(如
r.RemoteAddr、userID)创建独立*rate.Limiter - 用
sync.Map或带 TTL 的 LRU cache 管理生命周期;长期存活的实例不会自动 GC,需定期清理(比如 5 分钟无访问就删),否则内存泄漏 - 注意
X-Forwarded-For可伪造,生产环境提取真实 IP 要结合反向代理配置(如 Nginx 的real_ip_header)
一次扣多个令牌(上传/导出)必须用 ReserveN
Allow() 和 Wait() 都固定扣 1 个令牌,上传大文件、批量导出等操作必须用 ReserveN()。
- 调用前必须缓存一次
now := time.Now(),后续所有ReserveN(now, n)都复用它;否则单请求内多次time.Now()会导致时间跳跃,限流行为飘忽 -
n必须 ≤burst容量,否则ReserveN直接返回不可用的*rate.Reservation - 拿到
res后,必须显式调res.Fulfill()(成功执行)或res.CancelAt(now)(放弃),否则令牌被预占却不扣除,桶迅速变空
最常被忽略的是:ReserveN 返回的 reservation 不调 Fulfill 或 CancelAt,桶就永远少那几个令牌;还有 burst 设成 1 却指望扛住用户连续点击——这些不是配置问题,是语义理解偏差。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











