rate.Limiter必须复用,不能每次请求新建;应定义为包级变量或注入结构体共享实例,避免限流失效,且需配合WaitN(ctx,1)与context超时控制。

rate.Limiter 初始化必须复用,不能每次请求 new
限流失效最常见的原因是把 rate.NewLimiter 放在 handler 里调用,导致每个请求都拿到一个全新、空桶的限流器。它永远有令牌,等于没限流。
- 正确做法:定义为包级变量或注入到结构体中,确保所有请求共享同一个实例
- 错误写法示例:
func handler(w http.ResponseWriter, r *http.Request) { limiter := rate.NewLimiter(10, 5) ... } - 并发安全无需额外加锁——
rate.Limiter内部用 atomic 操作,本身就是线程安全的
WaitN(r.Context(), 1) 比 Allow() 更适合 HTTP 接口
Allow() 是非阻塞判断,返回 true 后立刻执行业务逻辑,但此时令牌可能已被其他 goroutine 抢走,导致超限;而 WaitN() 会真正预留令牌,并天然支持 context 超时控制。
- 推荐写法:
err := limiter.WaitN(r.Context(), 1),配合http.Request.Context()可自动感知客户端断连或网关超时 - 避免直接用
time.AfterFunc或独立 goroutine 做限流判断——脱离请求生命周期,容易泄漏 goroutine - 若需快速失败(不等),可用
limiter.TryWaitN(ctx, 1),它带超时且不阻塞
Burst 不是“最大并发数”,而是冷启动/突发缓冲能力
设 rate.NewLimiter(10, 5),不代表“最多允许 5 个请求同时处理”。它的实际含义是:桶初始有 5 个令牌,系统刚启动或空闲后,前 5 个请求可立即通过;之后严格按每 100ms 补 1 个令牌(≈10 QPS)平滑放行。
- Burst 过小(如 1)→ 突发流量全被拒,体验差
- Burst 过大(如 1000)→ 相当于放开前 N 秒的流量,失去限流意义
- 经验取值:一般设为期望 QPS 的 2–5 倍,比如限 100 QPS,Burst 取 200~500
按用户或路径分级限流,别用全局单例硬扛
全站统一限流只适用于极简场景。真实业务中,登录接口可以宽松(如 50 QPS),支付接口必须严格(如 5 QPS),管理后台甚至更低(如 1 QPS)。
立即学习“go语言免费学习笔记(深入)”;
- 路径前缀限流:用
map[string]*rate.Limiter+sync.RWMutex,key 为r.URL.Path截断前缀(如/api/pay) - 用户级限流:从 header 提取
X-User-ID或解析 JWT,key 为用户 ID;注意用sync.Map或加锁,避免 map 并发写 panic - 健康检查接口(如
/healthz)必须显式放行,否则监控会误报
c.Abort() 就直接 http.Error 返回,后续 handler 仍会执行。这个细节一漏,业务逻辑就绕过限流了。


















