rate.limiter实例不能每次请求都new,最常见失效原因是将rate.newlimiter写在handler内导致每个请求独享空桶;正确做法是复用实例——全局限流用包级变量或结构体注入,按ip/用户限流则用sync.map缓存并安全解析真实ip,且限流逻辑必须置于中间件最前端。

rate.Limiter 实例不能每次请求都 new
限流失效最常见原因就是把 rate.NewLimiter 写在 handler 函数里。每次请求都新建一个 limiter,等于每个请求都有自己的“空桶”,完全起不到限制作用。
正确做法是复用实例:
- 全局限流:定义为包级变量或注入到 handler 结构体中
- 按 IP 或用户限流:用
sync.Map[string]*rate.Limiter缓存,key 是解析后的标准 IP(推荐net.ParseIP(ip).To16().String()) - 绝不为每个新请求无条件 new,否则内存缓慢上涨且逻辑失效
HTTP 中间件里必须最早执行限流逻辑
限流判断要放在中间件链最前端,否则前置步骤(如 JWT 解析、日志记录)耗时会导致漏判——请求已消耗资源才被拒绝,失去保护意义。
实操要点:
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 在
gin.HandlerFunc或http.Handler中,第一行就调用limiter.AllowN(time.Now(), 1) - 返回
false立即http.Error(w, "429", http.StatusTooManyRequests)并return - 别用
WaitN(ctx, n)—— 它会阻塞,违背 REST 响应时效性原则 - 若需支持单次扣多 token(如按文件大小计费),传入动态
n比固定Allow()更合理
按 IP 限流必须安全解析真实地址
直接用 r.RemoteAddr 当 key 会失效——所有请求经过 Nginx、Cloudflare 或 ALB 后都显示代理 IP。
安全提取真实客户端 IP 的步骤:
- 先检查请求来源 IP 是否在可信代理网段(如
trustedProxies = []string{"10.0.0.0/8", "192.168.0.0/16"}) - 可信时取
X-Forwarded-For最左非私有地址(如"192.168.1.100, 203.0.113.5"→ 取203.0.113.5) - 不可信则 fallback 到
r.RemoteAddr - key 统一转成
net.ParseIP(ip).To16().String(),避免 IPv4/IPv6 格式不一致
Limit 和 Burst 参数必须协同设置且定期清理缓存
rate.NewLimiter(rate.Every(200*time.Millisecond), 10) 中的两个参数不是独立配置的:
-
rate.Every(200*time.Millisecond)≈ 5 QPS,比直接写浮点数rate.Limit(5)更稳定,避免精度漂移 -
burst必须 ≥limit,否则新速率永远达不到(如 limit=10 但 burst=5,桶最多存 5 个) - burst 过大会削弱限频效果:设成 1000 不是“扛住突发”,而是“放行前 1000 次请求不拦”
-
sync.Map必须配后台 goroutine 定期清理,比如每分钟删掉time.Since(lastAccess) > 30 * time.Minute的条目,否则内存持续上涨
真实业务里最难的不是写对 AllowN,而是让 IP 解析不出错、让 limiter 实例不泄漏、让 burst 不掩盖真实瓶颈。这些点一旦松动,限流就从防护盾变成幻觉。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










