
本文介绍如何在 go 中结合漏桶算法与信号量机制,精确限制 http 请求的每秒发送数量(rps)并控制最大并发数,避免服务端过载或触发限流。
本文介绍如何在 go 中结合漏桶算法与信号量机制,精确限制 http 请求的每秒发送数量(rps)并控制最大并发数,避免服务端过载或触发限流。
在高并发 HTTP 客户端场景中,仅靠 sync.WaitGroup 或无节制启动 goroutine 很容易导致瞬时流量洪峰,既可能被目标服务限流/拒绝,也易耗尽本地资源(如文件描述符、内存)。真正稳健的方案需同时满足两个约束:
✅ 速率限制(Rate Limiting):例如「最多 10 次请求/秒」;
✅ 并发限制(Concurrency Limiting):例如「最多 5 个请求同时进行」。
Go 标准库未直接提供 RPS 级限流器,但可通过组合 time.Ticker 与带缓冲 channel 实现轻量级漏桶式限速器:
// 初始化速率通道:每 100ms 注入 1 个令牌 → 等效于 10 QPS
rate := make(chan struct{}, 10) // 缓冲区支持突发(burst)
go func() {
ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop()
for range ticker.C {
select {
case rate <- struct{}{}:
// 成功注入令牌
default:
// 缓冲已满,丢弃本次令牌(可选:日志告警)
}
}
}()每次发起请求前,必须先从 rate 通道获取令牌(阻塞等待):
<-rate // 阻塞直到获得令牌,自动实现平滑限速
⚠️ 注意:单纯限速无法防止长尾请求堆积导致并发失控。例如,若某请求耗时 2 秒,而限速器持续放行新请求,10 秒内可能积压 100 个 goroutine。因此需叠加并发控制——使用带缓冲 channel 作为信号量(Semaphore):
semaphore := make(chan struct{}, 5) // 最大并发数 = 5
// 在请求函数中:
func doRequest(url string) error {
semaphore <- struct{}{} // 获取并发许可(阻塞)
defer func() { <-semaphore }() // 请求结束释放许可
<-rate // 获取速率许可(阻塞)
resp, err := http.Get(url)
// ... 处理响应
return err
}完整工作流程为:
- 先抢
semaphore(控制并发数上限); - 再抢
rate(控制单位时间请求数); - 执行 HTTP 请求;
- 请求完成自动释放
semaphore(defer保证)。
? 进阶提示:
- 若需更精确的「每秒 N 次」而非「平均 N QPS」,可改用
golang.org/x/time/rate包中的Limiter(基于令牌桶,支持AllowN()和ReserveN()); - 生产环境建议添加超时控制(
http.Client.Timeout)、重试策略及错误熔断; - 对
rate通道的select{default:}分支可用于实现非阻塞限速(失败则跳过或降级)。
通过双 channel 协同,你能在零依赖前提下构建出兼具速率精度与资源安全性的 HTTP 客户端限流体系。

















