burst设为0会导致所有请求立即被拒,因桶初始无令牌;burst至少为1,推荐设为QPS的2–5倍以平衡突发容忍与限流效果。

rate.Limiter 初始化时 burst 参数设为 0 会直接拒所有请求
burst 是令牌桶的“初始容量”和“最大积压数”,不是并发数。设成 0 意味着桶里永远没有可用令牌,Wait() 和 Allow() 全部立刻返回 false 或阻塞超时。生产环境常见误配是 rate.NewLimiter(10, 0),结果所有请求 429。
-
burst至少设为1,推荐值为 QPS 的 2–5 倍(如 10 QPS → burst=20) - burst 太小(如 1)会导致正常重试、客户端抖动也被拦截;太大(如 1000)等于弱化限流效果
- 它影响的是“短时突发容忍度”,不是长期速率——长期仍受
rate.Limit约束
按路径或用户 ID 分 key 时别直接用 r.RemoteAddr 当 IP
r.RemoteAddr 在有反向代理(Nginx、Cloudflare)时只是代理地址,不是真实客户端 IP。直接拿它做限流 key,会导致整个代理后所有用户共享一个桶,形同虚设。
- 优先读
r.Header.Get("X-Real-IP")或可信的X-Forwarded-For首段(需确认代理链可信) - 若用 JWT 认证,从 token payload 提取
sub字段作 user_id,比 header 更可靠 - key 字符串拼接建议加前缀,如
"ip:" + realIP或"user:" + userID,避免不同维度 key 冲突
sync.Map 存 limiter 实例但不清理会导致内存持续增长
每个新 IP 或路径首次访问都会新建 *rate.Limiter 并存进 sync.Map,若无清理机制,爬虫扫一遍 /api/v1/user/{id} 就能生成上万 key,内存只增不减。
- 不要依赖 GC 自动回收——
rate.Limiter本身不占大内存,但 map 键值对和 goroutine 引用会累积 - 用
time.AfterFunc在创建后 10 分钟尝试删除,删除前调用lim.Reserve().OK()判断是否空闲 - 更稳妥的做法是换
github.com/hashicorp/golang-lru,设 maxEntries=10000,自动淘汰冷 key - 如果用 Redis 分布式限流,key 必须带 TTL,例如
SET api:limit:ip:1.2.3.4 1 EX 60
中间件里用 Wait() 而不是 Allow() 容易卡住 goroutine
Allow() 是非阻塞判断,适合快速失败;Wait() 会阻塞当前 goroutine 直到拿到令牌。在高并发下,若客户端断连或网关超时,而 Wait() 还在等,就会堆积大量僵尸 goroutine,最终 OOM。
立即学习“go语言免费学习笔记(深入)”;
- 必须用带 context 的
WaitN(ctx, n),且 ctx 来自r.Context() - 别在中间件里写
limiter.Wait()—— 缺少 ctx 会导致无法中断等待 - 如果业务允许“秒级延迟容忍”,用
TryWait()更安全;否则确保上游网关设置了合理的 timeout(如 3s) - 错误示例:
limiter.Wait();正确写法:limiter.WaitN(r.Context(), 1)
rate.Limiter + 合理 burst 就够用——前提是 key 不乱、ctx 不丢、burst 不设 0。


















