热路径应直接用 Allow() 无锁判断,禁用 Reserve()+Wait();burst 至少设为预期最大并发突增数;全局复用 Limiter 实例,避免 per-request 新建。

热路径下别用 Reserve() + Wait() 做同步等待
高频请求(比如健康检查、内部服务探针、API 网关前置校验)走 Reserve() 会分配 Reservation 对象,Wait() 还要进 channel select 和定时器调度,GC 和调度开销明显。实测在 50k QPS 下,相比 Allow(),延迟 P99 上浮 15%~20%,对象分配率高 3 倍以上。
真正该做的:直接用 Allow() 做无锁快速判断,失败就立刻返回 429 或降级逻辑。它只读原子字段 + 简单比较,全程无内存分配、无锁、无 goroutine 切换。
-
Allow()返回false不代表“永远不行”,只是“此刻桶空”,下一纳秒可能就补满——所以适合容忍瞬时抖动的场景 - 不要在
Allow() == false后再 fallback 到Reserve():这等于把热路径拖进冷路径,反而放大毛刺 - 若需告知客户端“还需等多久”,可用
Reserve().Delay(),但仅限低频操作类接口(如下单、支付),别塞进每秒几万次的探针里
rate.NewLimiter 的 burst 参数不能设成 1 来“严格控速”
设 rate.NewLimiter(rate.Limit(100), 1) 看似“每秒最多 1 个”,实际是“桶容量 = 1,速率 = 100 QPS”,意味着:第一次请求拿走唯一令牌后,桶要等 10ms 才能补上第二个——但此时如果并发请求打进来,99% 都会立刻被拒。这不是限流,是限活。
burst 至少设为预期最大并发突增数。比如服务平均 100 QPS、允许 3 倍突发,burst 就该 ≥ 300;否则 Allow() 在流量尖峰时大量返回 false,误伤正常请求。
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- burst 小于速率时,桶永远无法填满,长期看等效速率 ≈ burst / (1/rate),而非你期望的 rate
- 重启后 burst 归零,若设得太小(如 1 或 2),刚启动那几毫秒几乎全拒,容易触发客户端重试风暴
- 调试时可临时设大 burst(如 1000)观察真实流量分布,再反推合理值
全局复用 rate.Limiter 实例,别按请求 new
每个请求都 new rate.Limiter 是最常见误用:它内部含 sync.Mutex 和浮点状态,new 开销大,且原子计数器无法共享,等于每请求建一个独立桶,完全失去限流意义。
正确做法是定义包级或服务级变量,复用同一个实例。需要区分维度(如 per-IP、per-user)时,用 sync.Map[string]*rate.Limiter 缓存,配合 TTL 清理(例如 10 分钟无访问自动删)。
- HTTP handler 中直接用全局 limiter 是安全的,
rate.Limiter本身线程安全 - 避免用普通
map+mu sync.RWMutex,高并发下锁争用严重;sync.Map在读多写少场景下性能更稳 - 别把 limiter 存在 request context 里传参——context 是传递取消/超时/值的,不是对象池
纳秒级精度下别信 time.Now(),但你通常不用管
官方 rate.Limiter 内部用 time.Now().UnixNano() 做时间差计算,规避了 time.Now() 浮点转换误差。你自己手写限流逻辑时若用 time.Now().Sub() 再乘速率,可能因浮点舍入导致令牌多算或漏算——尤其在 sub-millisecond 级别调用时。
但绝大多数业务不需要自己重写:只要用 golang.org/x/time/rate,它的 advance() 已处理好单调时钟和浮点截断。唯一要注意的是——别在测试里 mock time.Now 改系统时间,会导致令牌填充逻辑错乱;应改用 clock 接口抽象或直接测 Reserve().Delay() 返回值。
- 自研限流器时,时间差必须用纳秒整型运算,避免 float64 累积误差
- 生产环境禁用
time.Ticker驱动令牌生成:ticker 可能被 GC 暂停或调度延迟,令牌发放不再平滑 - 如果你真需要 per-user 动态配额,优先组合
sync.Map+ 官方Limiter,而不是从头实现令牌逻辑
rate.Limiter 的设计取舍很明确:用浮点 tokens 换取无锁和纳秒精度,用惰性填充换后台 goroutine 免费。踩坑往往发生在试图绕过它、或者没看清 burst 和 rate 的耦合关系。

















