直接用golang.org/x/time/rate.limiter,别自己写;它基于线程安全的令牌桶,精度高、生产验证充分,自行实现易出现并发漏判、时钟漂移和速率偏差等问题。

令牌桶限流器怎么选:用 golang.org/x/time/rate 还是自己写?
直接用 golang.org/x/time/rate.Limiter,别自己实现令牌桶逻辑。它底层是带滑动窗口的原子计数,精度足够、线程安全、已过大量生产验证。自己写容易在并发场景下漏判(比如两个 goroutine 同时 Allow 成功但只消耗一个令牌),还可能因浮点运算或时间漂移导致速率偏差。
注意:Limiter 默认不阻塞,Allow 返回 bool;若需等待令牌,得用 Wait 或 Reserve + Delay,但 API 计费通常要立即拒绝超限请求,所以优先用 Allow。
如何把用户 ID 和限流策略动态绑定?
不能全局共用一个 Limiter,也不能为每个用户预创建无限个 Limiter 实例——内存和 GC 压力会失控。正确做法是用带驱逐策略的缓存:
-
sync.Map适合读多写少,但不支持自动过期,需配合定时清理或 LRU - 更推荐
github.com/bluele/gcache或github.com/hashicorp/golang-lru,设 TTL(如 1 小时)+ 容量上限(如 10k) - 键用
"user:" + userID,值是*rate.Limiter,初始化时按用户等级查配置(如免费用户 100 QPS,付费用户 1000 QPS)
示例伪代码:
limiter, _ := cache.Get(userID)
if limiter == nil {
rps := getUserRPS(userID) // 查 DB 或配置中心
limiter = rate.NewLimiter(rate.Limit(rps), int(rps)) // burst 设为 rps 是常见安全值
cache.Set(userID, limiter, 60*time.Minute)
}
Allow 返回 false 后,怎么区分是限流还是其他错误?
HTTP 层必须返回明确的状态码和响应头,否则前端无法区分“真的没配额了”和“服务暂时不可用”。关键点:
- 限流必须返回
429 Too Many Requests,不是400或500 - 加响应头:
X-RateLimit-Limit(总配额)、X-RateLimit-Remaining(剩余)、X-RateLimit-Reset(秒级时间戳) - 注意:
Limiter不暴露剩余令牌数,得自己封装:调用Reserve再CancelAt获取RemainingTokens - 不要在每次请求都查 DB 更新限流参数,应通过事件驱动(如用户升级)刷新缓存
为什么 burst 值设成和 RPS 相同?
burst 控制突发流量的“缓冲池大小”,设太小(如 1)会导致抖动请求直接被拒;设太大(如 10×RPS)等于变相放宽限制。经验值是设为 RPS 值本身:
- 对 HTTP API,1 秒内允许最多 RPS 个请求突增,符合真实用户行为(比如页面加载触发多个并行请求)
- 如果业务要求严格平滑(如支付类接口),可降为
int(float64(rps) * 0.5) - 切勿设为 0:
rate.NewLimiter(10, 0)会导致所有请求立刻失败,因为初始令牌数为 0 且无法生成
真正难的是跨实例共享状态——单机 Limiter 无法应对负载均衡场景。这时候得上 Redis + Lua(如 INCR + EXPIRE 实现分布式令牌桶),或者改用服务网格层(如 Envoy 的 rate limit service)。Go 应用层自己搞分布式令牌桶,极易因网络延迟、时钟不同步、Redis 故障引入误判。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











