Wait() 比 Allow() 更适合第三方 API 调用,因其原子性避免竞态,支持 context 取消,且能严格保证请求间隔;但需配合 WithTimeout 防止 goroutine 积压导致 OOM。

rate.Limiter.Wait() 为什么比 Allow() 更适合第三方调用
第三方 API 的配额通常按「每秒请求数」硬性限制,且超限直接返回 429 或断连。用 Allow() 判断再发请求,存在竞态:判断通过后,另一个 goroutine 可能立刻取走最后一个令牌,导致本次请求实际被拒。而 Wait() 是原子操作,它申请并预留令牌,失败时直接阻塞或返回 error,不会出现“判断通过但执行失败”的情况。
-
Wait(ctx)能响应 context 取消,避免客户端超时后 goroutine 还在等令牌 - 必须传入
r.Context(),不能用 background context,否则无法感知 HTTP 请求生命周期 - 如果第三方要求严格平滑(比如每 200ms 一个请求),
Wait()比Allow()+ sleep 更可靠——后者可能因调度延迟导致间隔抖动
uber-go/ratelimit.Take() 的阻塞行为怎么不拖垮你的 goroutine
Take() 会无条件阻塞,直到轮到它。这在调用第三方 API 时是优点,但也意味着:如果限流器设为 1 QPS,而你并发启动 100 个 goroutine,它们会排队等待,内存和 goroutine 数量线性增长,可能 OOM。
- 务必配合
context.WithTimeout()包裹外部调用逻辑,而不是只包Take()——因为Take()本身不接受 context - 典型写法:
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)<br>defer cancel()<br>select {<br>case <-ctx.Done():<br> return errors.New("timeout before take")<br>default:<br> rl.Take()<br>} - 不要在循环里直接起 goroutine 调
Take(),应先做批量合并或加队列缓冲,否则瞬间压爆限流器内部时钟
按 API Key 动态创建 rate.Limiter 的内存泄漏风险
用 sync.Map 存 map[string]*rate.Limiter 很常见,但没人清理就等于内存永不释放。尤其当 API Key 来自 JWT subject 或短期 token,key 会高频增删。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 别依赖 GC 自动回收——
*rate.Limiter本身不持有大对象,但 map key 会持续占用内存 - 给每个 limiter 绑定 TTL,例如用
time.AfterFunc(24*time.Hour, func(){ limiters.Delete(key) }) - 更稳妥的做法是定期扫描:
limiters.Range(func(k, v interface{}) bool { if shouldExpire(k) { limiters.Delete(k) } return true }) - burst 值要按 key 级别设置:高权限 key 可设 burst=10,普通 key 设 burst=3,避免单个 key 耗尽全局配额
漏桶 vs 令牌桶:选 uber-go/ratelimit 还是 golang.org/x/time/rate
关键不是哪个“更好”,而是第三方接口文档写的限流规则类型。如果文档明确说“每秒最多 N 次,且必须均匀分布”,选 uber-go/ratelimit;如果说“每秒最多 N 次,允许短时突发”,必须用 golang.org/x/time/rate。
立即学习“go语言免费学习笔记(深入)”;
-
uber-go/ratelimit的New(10)表示严格 100ms 间隔,无突发余量;rate.NewLimiter(10, 5)表示平均 10 QPS,但允许最多 5 次瞬时并发 - 有些第三方(如 Stripe、GitHub)的 rate limit header(
X-RateLimit-Remaining)返回的是漏桶式剩余配额,此时用ratelimit更易对齐语义 - 性能差异可忽略,但
rate.Limiter的WaitN()支持一次取多个令牌,适合批量调用场景;ratelimit.Take()只支持单次

















