Go 1.22 的 HTTP 服务里每个请求进来都执行 rate.NewLimiter(rate.Limit(10), 10),压测 200 QPS 全部放行,下游 Redis 的 QPS 直接被打到 2000,连接数报警。
一开始怀疑是「多副本部署时每个实例各算一份,总量被放大」,查了一圈,方向不对。
原因不止一个,得分开看。Limiter 在 handler 里新建,桶一开始就是满的,等于没有限流。少数情况下是应该做成包级单例或按用户维度缓存。
不容易想到的是,burst 设成和 limit 一样大,瞬时允许的突发偏高。
上来先确认 NewLimiter 出现的位置
go doc golang.org/x/time/rate.NewLimiter
随后把它提到包级变量只初始化一次
go vet ./...
然后用 Wait 代替 Allow,让超出的请求排队
go test ./... -run TestLimiter -v
压测对比单例前后的下游 QPS
go test -bench BenchmarkHandler -benchtime 30s
当时还开着另一个排查窗口,两边日志对照着看才定位到。
恢复的判定其实很直接。改成包级单例后,200 QPS 压测下下游只收到约 10 QPS,超出的请求在 Wait 里排队,P99 从 40ms 涨到 300ms 但下游不再报警。
限流器是有状态的,new 在请求路径上等于每次重新装满一桶水,这类 bug 压测时最容易暴露。监控比日志更早给出信号,事后回看经常能提前半小时发现异常。
