rate.NewLimiter(10, 10) 的陷阱在于冷启动后第11个请求必须严格等待100ms,易放大延迟毛刺;burst应设为limit×3以匹配3秒业务波峰,而非与limit相等;Allow()适合网关快速拦截,Wait()需配context.WithTimeout防goroutine卡死;分布式场景下单机限流必然失效。

golang.org/x/time/rate 是你当前最该用、也最不该乱用的限流工具——它开箱即用,但参数配错一毫秒,系统就可能在流量高峰时既没拦住洪峰,又误杀合法请求。
为什么 rate.NewLimiter(10, 10) 很可能不是你想要的配置
这个看似对称的配置(每秒10令牌,桶容量10)隐含一个关键陷阱:冷启动时桶满,前10个请求瞬间通过;但第11个请求开始,就必须严格等100ms才能拿到下一个令牌。如果下游是数据库或第三方API,这种“前快后卡”的节奏反而放大了延迟毛刺。
更现实的配置需拆解两个目标:
- 平均速率(
limit)由后端资源吞吐能力决定,比如MySQL连接池最大支持50 QPS,那limit就不该超过50 - 突发容量(
burst)要匹配真实业务波峰,比如营销活动期间用户点击集中在3秒内爆发,那就需要burst >= limit × 3,而不是拍脑袋填个和limit相同的值 - 注意:
burst不是越大越好——过大的值会让限流器在初期形同虚设,直到系统已被打垮才开始生效
Allow() 和 Wait() 的选择本质是「丢弃策略」的取舍
这两个方法不是“功能差异”,而是服务契约的声明:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
-
Allow()立即返回bool,适合网关层快速拦截——HTTP状态码直接返回429 Too Many Requests,不占goroutine,也不阻塞调用方 -
Wait(ctx)会阻塞当前goroutine直到拿到令牌,适合后台任务队列——你愿意让任务多等200ms,但不能让它失败;但必须配context.WithTimeout,否则超时请求会永久卡住 - 别混用:在HTTP handler里用
Wait()而不设超时,等于给每个慢请求预留一个goroutine,OOM风险陡增
单机限流失效的三个典型信号
当你观察到这些现象,说明 rate.Limiter 已经不够用了:
立即学习“go语言免费学习笔记(深入)”;
- 同一用户从不同节点发起请求,总频次远超配置上限——这是分布式场景下本地桶各自为政的必然结果
- 重启服务后出现大量
rate: Wait returned an error: context canceled——因为Wait()在上下文取消时返回错误,但你没检查就继续执行了业务逻辑 - 监控显示QPS稳定在限流阈值,但下游DB的CPU却持续90%+——说明限流点位置错了,应该卡在更靠近DB的中间件,而不是API入口
limiter := rate.NewLimiter(...),而是搞清你的 burst 值到底是为应对3秒脉冲,还是为兜底5分钟缓存击穿;以及当 Wait() 返回 error 时,你是该重试、降级,还是直接记录并告警。

















