为什么会积压
很多服务写了 worker pool,但上线后仍然:
- 高峰时队列暴涨
- 平均延迟还行,P99 很差
- CPU 没打满却开始超时
这通常是“入队速率 > 出队速率”的背压问题。
基础模型
type Job struct {
ID string
}
func startPool(ctx context.Context, n int, jobs <-chan Job) {
for i := 0; i < n; i++ {
go func() {
for {
select {
case <-ctx.Done():
return
case job, ok := <-jobs:
if !ok {
return
}
handle(job)
}
}
}()
}
}
结构不复杂,难点在参数选择。
三个观测指标
- 吞吐:每秒处理多少请求
- 延迟:P50/P95/P99
- 队列深度:channel 长度趋势
如果队列长期接近上限,说明处理能力不足或外部依赖抖动。
调优顺序
- 固定业务流量,先找单 worker 处理能力
- 逐步增加 worker,观察 P99 与 CPU 变化
- 达到拐点后停止扩容,避免锁竞争和上下文切换过量
- 设置拒绝策略,不让队列无限增长
一个实用策略
- 快速失败:队列满时直接返回可重试错误
- 分级队列:高优先级任务单独通道
- 限时执行:每个任务绑定 context 超时
总结
worker pool 不是“越大越好”。并发数本质是资源预算,必须和外部依赖能力、延迟目标一起设计。