golang.org/x/time/rate 是最稳妥的限流方案,基于改良令牌桶,支持预热与突发,避免手写计数器的并发和时钟问题;应正确配置 burst、使用 Wait() 配合超时 context,并按需隔离不同路由的限流实例。

Go 的 golang.org/x/time/rate 是最稳妥的选择
标准库不提供限流,但官方维护的 rate 包就是为此设计的,底层用的是改良令牌桶(支持预热、允许突发),不是简单计数器。别自己手写带锁的计数器——容易漏掉并发边界、时钟漂移、重置逻辑错乱。
常见错误现象:rate.Limit(0) 导致无限速;rate.NewLimiter(rate.Inf, 1) 看似“不限速”,实则每次 Allow() 都会消耗 1 个令牌,且初始桶空,第一次必拒;没处理 context.WithTimeout 就直接 Wait(),可能永久阻塞。
-
rate.Every(time.Second / 10)比硬写rate.Limit(10)更清晰,避免除零或浮点误差 - 突发容量(burst)设为 0 意味着完全不允许任何瞬时并发,通常设为和 QPS 相同或略高(比如 QPS=10,burst=15)更实用
- 如果请求处理时间远大于间隔(如平均耗时 200ms,但限 10qps),burst 过小会导致大量
Wait()等待,拖慢整体吞吐
HTTP 中间件里用 limiter.Wait() 而不是 Allow()
Allow() 只是试探性扣令牌,返回 bool,适合“尽力而为”场景(比如日志采样);真实 API 限流必须用 Wait(),它会自动阻塞直到拿到令牌,语义明确,且内置了对 context 取消的支持。
使用场景:API 网关、微服务入口、第三方回调接收端。注意 Wait() 会修改传入的 context,所以别复用同一个 ctx 给多个限流调用。
立即学习“go语言免费学习笔记(深入)”;
- 务必传带超时的
context,例如ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second),否则客户端断连后 goroutine 可能卡住 - 不要在
Wait()前做 heavy work(如 DB 查询),否则限流失去意义——令牌是按请求发放的,不是按处理完成发放的 - 如果想返回 429,用
limiter.TryConsume()+ 手动写响应,但要注意它不阻塞,也不更新内部状态(需配Reserve()才准)
全局共享 *rate.Limiter 时注意初始化时机和作用域
一个 *rate.Limiter 实例可安全被多 goroutine 并发调用,但它代表的是**同一速率策略下的统一桶**。错把每个 handler 都 new 一个 limiter,等于没限流;错把 limiter 声明成包级变量但 init 里没赋值,运行时报 nil panic。
参数差异:QPS 相同但 burst 不同,会导致相同流量下失败率差异极大;rate.Every(100*time.Millisecond) 和 rate.Limit(10) 理论等价,但前者更易读、不易算错。
- Web 服务中,limiter 应作为 handler 字段或依赖注入,而不是函数局部变量
- 不同路由需要不同速率?别共用一个实例——建 map[string]*rate.Limiter 或用中间件参数化构造
- 测试时用
rate.NewLimiter(1000, 1)配大 QPS 小 burst,快速暴露竞态;用rate.NewLimiter(1, 1)模拟严苛限流
别忽略时钟精度和测试中的 time.Now() 替换
rate.Limiter 内部靠 time.Now() 计算令牌生成时间,生产环境没问题,但单元测试里如果依赖真实时间,会导致 flaky test(尤其当测试机器负载高、时钟跳变)。
性能影响:单次 Wait() 调用开销约几十纳秒,远低于一次 HTTP 解析或 JSON decode,瓶颈从来不在这里;真正拖慢的是阻塞等待本身,也就是你的下游处理太慢。
- 测试时用
rate.NewLimiterAt()+ 自定义clock(需自己实现rate.Clock接口),或直接用github.com/benbjohnson/clockmock - 线上观察限流效果,看
limiter.Limit()返回值是否稳定,以及Wait()的平均等待时长(可用 Prometheus 暴露rate_limiter_wait_seconds_sum) - 令牌桶不是万能的——它压不住突发连接数(TCP 层)、也管不了内存暴涨(如大文件上传),得配合连接池、body size 限制一起用
真正难的不是选算法,而是确定 burst 值该设多少:设太小,正常毛刺就打满;设太大,又起不到保护作用。这得靠线上真实流量分布+错误率反馈来调,没法靠理论算出来。










