用 chan struct{} 实现令牌桶限流本质是控制并发 goroutine 数量,通过带缓冲 channel 作令牌池,缓冲容量即最大并发数;chan struct{} 零内存开销,但必须 make 时指定容量,无缓冲会导致永久阻塞。
用 chan struct{} 做固定容量的并发令牌桶
限流本质是控制同时运行的 goroutine 数量,最轻量的做法就是用带缓冲的 channel 当“令牌池”:每启动一个任务先从 channel 取一个空结构体,任务结束再塞回去。缓冲区大小即最大并发数。
注意:chan struct{} 不占内存,比 chan int 更干净;缓冲容量必须在 make 时确定,运行时不能扩容。
常见错误是把 channel 声明成无缓冲(make(chan struct{})),导致第一个 goroutine 就阻塞——因为没人往里写,它永远取不到令牌。
- 正确写法:
sem := make(chan struct{}, 5)表示最多 5 个并发 - 获取令牌:
sem (阻塞直到有空位) - 释放令牌:
(从 channel 读出一个,腾出一个位置) - 别忘了用
defer func() { 确保异常时也归还令牌
用 time.Ticker + select 实现速率限流(QPS 控制)
如果要限制的是“单位时间请求数”(比如 100 QPS),就不能只靠计数器或 channel 缓冲,得结合时间维度。典型做法是用 time.Ticker 每秒发一次“重置信号”,配合 select 非阻塞尝试获取令牌。
这种模式适合 API 网关、爬虫请求调度等场景,但要注意:Ticker 的 tick 并不精确,高负载下可能漂移;且它不处理突发流量,需配合漏桶或令牌桶变体。