协程限流本质是基于goroutine+channel的并发数控制机制,不统计请求数、不维护时间窗口,仅通过阻塞channel实现请求排队;适用于io密集型场景,但存在goroutine泄漏和channel堵塞风险。

协程限流和标准限流器有啥本质区别?
协程限流不是一种独立算法,而是用 goroutine + channel 实现的「请求排队调度」机制。它不统计请求数、不生成令牌、不维护时间窗口,核心是控制并发执行数——比如限制最多 5 个请求同时处理,超出的自动阻塞在 chan 上。
这种模式适合 IO 密集型场景(如调用外部 API、数据库查询),但不适合 CPU 密集型任务;一旦某个协程卡死或长时间占用,整个 channel 就会被堵住,后续请求无限等待。
- 适用:HTTP 客户端批量调用、文件下载并发控制、资产扫描发包节流
- 不适用:高频写入数据库、实时风控规则计算等需精确 QPS 控制的场景
- 关键风险:
channel容量设太小会丢请求,设太大等于没限流;没配超时极易导致 goroutine 泄漏
怎么用 time.Tick 做最简协程限流?
time.Tick 是 Go 标准库里最轻量的节奏控制器,适合固定速率、无突发容忍需求的场景。但它返回的是 ,不能直接计数,必须配合 channel 缓冲或 select 配合 timeout 使用。
常见错误是直接用 for range time.Tick() 启动无限 goroutine,结果瞬间起几百个协程把内存打爆。
- 正确做法:用一个带缓冲的
chan struct{}当令牌池,每次从time.Tick收到信号就往里塞一个 token - 放行逻辑:接收一次
,成功即放行,失败(超时)则拒绝 - 示例片段:
limiter := make(chan struct{}, 10)<br>go func() {<br> ticker := time.Tick(100 * time.Millisecond)<br> for range ticker {<br> select {<br> case limiter default:<br> }<br> }<br>}()
sync.WaitGroup 能不能替代限流?
不能。很多初学者误以为用 sync.WaitGroup 控制 goroutine 数量就是限流,这是典型概念混淆。WaitGroup 只负责等待完成,不提供任何速率约束、不阻塞新请求、不丢弃超额请求——它只是“等全部做完”,不是“只让做这么多”。
如果你看到代码里用 wg.Add(1) + defer wg.Done() 包裹 handler,那只是避免主 goroutine 提前退出,跟限流完全无关。
- 真限流必须有显式拒绝路径:比如返回
http.StatusTooManyRequests或直接return -
WaitGroup和限流可以共存,但角色分离:前者管生命周期,后者管准入 - 容易踩坑:在限流逻辑外漏掉
wg.Done(),导致 goroutine 永久泄漏
为什么生产环境慎用纯协程限流?
因为协程限流缺乏时间维度的计量能力,无法应对突发流量反弹、无法做滑动窗口统计、无法与分布式限流协同。它本质是本地资源隔离手段,不是服务级流量防护策略。
比如你用 chan 控制并发为 10,在单机压测时表现良好,但一旦部署多实例,总并发就变成 10 × 实例数,毫无全局约束力。
- 真实线上接口必须结合时间窗口(如
CounterLimiter)或令牌桶(如TokenBucket) - 协程限流只适合内部组件间调用节流,比如一个服务调用下游 SDK 的并发控制
- 最容易被忽略的点:没做 panic recover —— 任意一个协程 panic 会导致整个限流 channel 阻塞,后续所有请求 hang 住
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











