不能只用一个全局rate.Limiter实例,否则所有请求共用同一令牌桶,导致IP A耗尽配额后IP B被连坐限流;必须按ClientIP等维度隔离实例,用sync.Map缓存并正确解析X-Forwarded-For获取真实IP。

直接用 golang.org/x/time/rate 的 rate.Limiter,3 行核心逻辑就能跑通,但不处理维度隔离和并发安全就等于没做。
为什么不能只 new 一个全局 limiter
所有请求共用同一个 rate.Limiter 实例,会导致 IP A 刷爆额度后,IP B 立刻被拦——这不是限流,是连坐。真实场景中必须按请求来源(如 c.ClientIP() 或 c.GetHeader("X-User-ID"))各自维护独立实例。
- 用
sync.Map存储map[string]*rate.Limiter,避免全局sync.RWMutex锁竞争 -
sync.Map.LoadOrStore返回interface{},记得强制断言为*rate.Limiter - 别从
r.RemoteAddr直接取 IP;Nginx 反向代理下会全是127.0.0.1,得解析X-Forwarded-For并做可信跳数校验
rate.NewLimiter 参数怎么设才不翻车
burst 不是“最大并发数”,而是令牌桶容量;limit 也不是“每秒固定放行 N 个”,而是长期平均速率。设错会导致秒杀时大面积误拒,或压测时形同虚设。
-
rate.Every(200 * time.Millisecond)≡ 每 200ms 放 1 个令牌 ≡ 长期均值 5 QPS -
burst = 5表示允许瞬间打满 5 个请求,之后严格按 5 QPS 走;若业务峰值常达 30 QPS,burst至少设到 15~20 - 初值建议:burst ≈ avg_qps × 2,上线后盯
X-RateLimit-Remaining日志滚动调优
选 Allow() 还是 Wait()?看你的 HTTP 语义
Allow() 是非阻塞判断,适合「立刻拒绝」场景;Wait() 会挂起 goroutine 等令牌,适合「宁可等也不丢」的后台任务。混用会破坏超时控制。
- Web API 接口一律用
limiter.Allow(),配合c.AbortWithStatusJSON(429, ...) - 别在 HTTP handler 里调
Wait(),否则用户连接可能卡住,且无法响应context.DeadlineExceeded - 需要预留令牌做预占(比如先检查再执行耗时操作),用
ReserveN()+Cancel(),不是AllowN()
真正难的不是写那几行 Allow() 调用,而是想清楚「谁和谁不该共享桶」,以及「burst 值在凌晨三点的流量低谷里是否还合理」——这些没法靠代码自动推导,得看监控、看日志、看真实请求分布。











