Go官方限流器golang.org/x/time/rate基于令牌桶算法,支持速率(RPS)与突发容量(burst)控制,线程安全;其核心参数为每秒令牌生成速率limit和桶容量burst,通过Wait/Allow等方法实现阻塞或非阻塞限流,不依赖定时器而采用lazy计算方式动态更新令牌数。

用 time.Ticker 做简单固定间隔限流不靠谱
很多人第一反应是起个 time.Ticker,每秒发一次请求。但这只控制了“发送节奏”,没控制“并发请求数”,也没处理失败重试导致的漏发或堆积。真实场景中,API 可能要求「每分钟最多 60 次」且「同一时间最多 5 个并发」,光靠 Ticker 无法同时满足这两个约束。
更关键的是,Ticker 不感知请求生命周期:如果某次请求耗时 3 秒,下一次会准时触发,实际并发就可能冲到 3–4 个——尤其在批量调用时容易触发 429。
- 别把
time.Sleep或Ticker当限流器用,它们只是节拍器,不是控制器 - 需要显式跟踪「当前正在跑几个请求」和「最近 60 秒发了多少个」
- 若 API 要求滑动窗口(如「过去 60 秒内 ≤60 次」),
Ticker+ 计数器无法精确实现
用 golang.org/x/time/rate 实现令牌桶限流
rate.Limiter 是 Go 官方扩展包里最轻量、最常用的限流方案,底层基于令牌桶,天然支持速率(RPS)和突发容量(burst)控制,且线程安全,可直接用于 HTTP 客户端封装。
示例:限制每秒最多 10 次请求,允许最多 5 次突发
import "golang.org/x/time/rate"
<p>var limiter = rate.NewLimiter(rate.Every(100 * time.Millisecond), 5)</p><p>// 使用前阻塞等待令牌
if err := limiter.Wait(context.Background()); err != nil {
// 上下文取消或超时
return err
}
resp, err := http.DefaultClient.Do(req)
</p>
-
rate.Every(100 * time.Millisecond)等价于 10 QPS;不要写成rate.Limit(10)再除以秒——单位必须是time.Duration - burst 值设为 5 表示允许短时爆发 5 次,适合应对网络抖动或首次批量请求;设为 1 则完全匀速
-
Wait会阻塞直到拿到令牌,若需非阻塞,用TryConsume(1)并检查返回值 - 注意:它不自动感知响应状态码,429 错误仍需单独处理并临时降级限流速率
组合并发控制与速率限制(semaphore + rate.Limiter)
多数外部 API 同时声明「QPS 上限」和「最大并发连接数」,比如 Stripe 要求「≤100 RPS 且 ≤20 并发」。这时单用 rate.Limiter 不够——它不限制并发数,只限制平均速率。
推荐组合方案:用 golang.org/x/sync/semaphore 控制并发数,外层再套 rate.Limiter 控制节奏。
import (
"golang.org/x/time/rate"
"golang.org/x/sync/semaphore"
)
<p>var (
rateLimiter = rate.NewLimiter(rate.Every(10*time.Millisecond), 10)
sema = semaphore.NewWeighted(20) // 最大并发 20
)</p><p>func callAPI(req *http.Request) error {
// 先抢并发许可
if err := sema.Acquire(context.Background(), 1); err != nil {
return err
}
defer sema.Release(1)</p><pre class="brush:php;toolbar:false;">// 再等速率令牌
if err := rateLimiter.Wait(context.Background()); err != nil {
return err
}
resp, err := http.DefaultClient.Do(req)
// ...}
- 顺序不能反:必须先
Acquire再Wait,否则可能卡在速率限流上,长期占用并发槽位 -
sema.Acquire支持带超时的 context,建议设置context.WithTimeout(ctx, 30*time.Second)防止死等 - 如果 API 返回
Retry-After头,应临时调整rateLimiter.SetLimitAndBurst(),而不是忽略
注意 HTTP 客户端复用与连接池配置
限流逻辑再准,如果底层 http.Client 的连接池失控,照样会打爆目标服务。默认的 http.DefaultClient 对单域名最多只建 2 个空闲连接,但高并发下容易成为瓶颈或引发 TIME_WAIT 暴增。
务必自定义 http.Client 并调优 Transport:
client := &http.Client{
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
IdleConnTimeout: 30 * time.Second,
// 若目标 API 支持 HTTP/2,可省略 TLS 配置;否则确保 TLSClientConfig 正确
},
}
-
MaxIdleConnsPerHost必须 ≥ 你设定的最大并发数,否则连接复用失败,每次都会新建 TCP 连接 - 别忽略
IdleConnTimeout:太长会导致连接僵死,太短会频繁重连;30 秒是较稳妥的起点 - 如果调用的是同一个域名下的多个路径(如
/v1/users和/v1/orders),它们共享同一 Host 连接池,所以按 Host 配置才有效
真正难的不是写限流代码,而是把速率、并发、连接池、错误重试、服务端反馈(429 / Retry-After / RateLimit-Remaining)这几层联动起来——少一层,线上就可能突然被封 IP。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











