rate.limiter是go中api限流最直接可靠的选择,需全局复用、按ip/用户id缓存、合理设burst、区分allow/wait语义、单机有效且分布式需额外方案。

rate.Limiter 是 Go 实现 API 限流最直接、最可靠的选择,别自己手写计数器或用第三方轮子——它精度高、线程安全、支持突发控制,且已被大规模生产验证。
为什么不能每次请求都 new 一个 rate.Limiter
限流失效的头号原因:在 handler 里写 rate.NewLimiter(10, 5)。每个请求拿到的是全新实例,桶永远是满的,等于没限。
- 全局限流:定义为包级变量,复用单个实例
- 按 IP 或用户 ID 限流:用
sync.Map缓存,key 是清洗后的 IP(优先取X-Forwarded-For并校验可信跳数),value 是*rate.Limiter - 别直接用
r.RemoteAddr当 key——代理环境下它只是内网地址,会把所有请求归到同一个桶里
burst 设太小会导致合法请求被拒
burst 不是“最多允许多少次”,而是令牌桶的初始容量和最大积压量。设为 0,第一次请求就失败;设为 1,相当于强制匀速,扛不住前端重试、CDN 预热、用户双击等真实毛刺。
- 典型值:设为期望 QPS 的 2–3 倍,比如 QPS=10,
burst=20更稳妥 - 用
rate.Every(100 * time.Millisecond)初始化时,burst含义不变,仍是桶容量 - burst 过大会让限流形同虚设;过小则误伤率高,需结合监控调优
Allow() 和 Wait() 别混用,语义完全不同
前者是非阻塞试探,后者是阻塞等待——选错就等于放弃对延迟或成功率的控制。
-
limiter.Allow()立即返回 bool,适合低延迟敏感接口(如搜索建议),失败就该立刻返回429 -
limiter.Wait(ctx)会阻塞直到拿到令牌,适合强保序场景(如支付回调),但必须传带超时的 context,例如context.WithTimeout(r.Context(), 500*time.Millisecond),否则 goroutine 可能卡死 - 需要预占多个令牌(比如按文件大小计费上传)?用
limiter.ReserveN(time.Now(), n),失败记得调reserve.Cancel()
分布式部署下 rate.Limiter 失效是设计使然,不是 bug
它纯内存实现,单机有效。3 个 Pod 部署时,用户发 30 QPS 就能绕过 10 QPS 限制——这不是缺陷,而是明确的设计边界。
- 别急着上 Redis,先评估是否真需要分布式限流:很多业务用本地限流 + 自动扩缩容就能扛住峰值
- 若必须跨节点共享状态,核心是“原子操作 + 归一化 key”,推荐用 Redis + Lua 脚本,而不是改算法
- key 设计要归一化(比如统一小写、去端口、校验可信代理链),失败时可降级到本地
rate.Limiter,避免雪崩
真正难的不是写几行 rate.NewLimiter,而是决定谁该被限、按什么粒度限、burst 怎么调、超时怎么设、分布式要不要做——这些决策比代码本身更影响线上稳定性。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











