8.2 KiB
tags, create time
| tags | create time | ||||||
|---|---|---|---|---|---|---|---|
|
2026-06-03 10:05 |
02. 协程池 (Worker Pool)
概述
有界并发协程池,以 NumCPU*4 个 Worker 并行处理任务,配合 per-user 限流和背压保护,支持优雅关闭。
一句话概括:有界并发协程池,
NumCPU*4workers,per-user 限流,背压保护,优雅关闭。
正文
工作流
graph TB
subgraph Submit["Submit入口"]
CHECK_CLOSED{"池已关闭?"}
CHECK_USER{"per-user限流<br/>active >= max?"}
CHECK_FULL{"channel满?"}
end
subgraph Channel["有界任务队列"]
TASK_CHAN["chan Task<br/>capacity = queueSize"]
end
subgraph Workers["Worker协程"]
W1["Worker 0"]
W2["Worker 1"]
W3["Worker ..."]
WN["Worker N-1"]
end
subgraph Execute["任务执行"]
TIMEOUT["context.WithTimeout<br/>10 min"]
FN["task.Fnctx"]
RELEASE["释放用户槽位"]
end
CHECK_CLOSED -->|"ErrPoolClosed"| REJECT["返回错误"]
CHECK_CLOSED -->|否| CHECK_USER
CHECK_USER -->|"ErrUserLimitReached"| REJECT
CHECK_USER -->|通过| CHECK_FULL
CHECK_FULL -->|"ErrPoolFull HTTP 503"| REJECT
CHECK_FULL -->|通过| TASK_CHAN
TASK_CHAN --> W1 & W2 & W3 & WN
W1 & W2 & W3 & WN --> TIMEOUT --> FN --> RELEASE
Pool 结构体
type Pool struct {
workers int // worker 数量(默认 NumCPU*4)
queueSize int // 任务队列容量(默认 100)
maxPerUser int // 单用户最大并发数(默认 2)
taskQueue chan Task // 有界任务队列
wg sync.WaitGroup
ctx context.Context
cancel context.CancelFunc
metrics *Metrics
mu sync.Mutex
userActive map[string]int // userID -> 当前并发数
closed atomic.Bool
closeCh chan struct{}
}
核心字段说明:
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
workers |
int |
runtime.NumCPU() * 4 |
IO 密集型调参 |
queueSize |
int |
100 |
有界缓冲,触发背压 |
maxPerUser |
int |
2 |
防止单用户占满池 |
taskQueue |
chan Task |
buffered channel | 有界队列,固定容量 |
userActive |
map[string]int |
— | 记录每用户活跃任务数 |
Functional Options 模式
协程池采用 Functional Options 模式进行配置,开箱即用、可选覆盖:
pool := workerpool.New(
workerpool.WithWorkers(16), // 覆盖默认的 NumCPU*4
workerpool.WithQueueSize(200), // 覆盖默认的 100
workerpool.WithMaxPerUser(5), // 覆盖默认的 2
)
| Option | 默认值 | 约束 | 说明 |
|---|---|---|---|
WithWorkers(n) |
NumCPU * 4 |
n < 1 时强制为 1 |
IO 密集型场景推荐 4 倍 CPU 核数 |
WithQueueSize(n) |
100 |
n < 1 时强制为 1 |
有界缓冲,满时触发背压 |
WithMaxPerUser(n) |
2 |
n < 1 时置 0(不限制) |
防止单用户占满池 |
[!tip] 为什么选择 Functional Options?
可选参数天然为零值时保持默认,新增配置项无需修改
New()签名,调用方按需指定。
有界并发
协程池的核心是 make(chan Task, queueSize) 创建的 有界缓冲 channel。
工作原理:
Submit()尝试将任务写入 channelN个 Worker 协程从 channel 中取任务执行- channel 满时进入背压逻辑(见下文)
// Submit 核心逻辑
select {
case p.taskQueue <- task: // 成功入队
return nil
case <-p.ctx.Done(): // 池已关闭
return ErrPoolClosed
default: // 队列满,背压
return ErrPoolFull
}
Per-user 限流
每个用户同时执行的任务数受到 maxPerUser 限制,防止单用户占满整个池。
实现机制:
Submit() 调用流程:
1. 加锁检查 userActive[userID]
2. 若 active >= maxPerUser → 返回 ErrUserLimitReached
3. 否则 userActive[userID]++(预留槽位)
4. 任务执行完毕后 defer releaseUserSlot(userID)
槽位生命周期:
| 阶段 | 操作 | 说明 |
|---|---|---|
| Submit | userActive[userID]++ |
预留槽位,不等到 Worker 取出 |
| Execute | — | Worker 从 channel 取出后开始执行 |
| Complete | defer userActive[userID]-- |
任务完成或失败时释放 |
[!warning] 槽位预留时机
在
Submit()而非worker()中预留,确保 channel 满时不会出现"槽位已分配但任务未入队"的不一致状态。
背压保护
当任务队列已满时,协程池通过 select + default 实现非阻塞拒绝:
graph TB
FULL["队列满channel已达capacity"] --> SELECT["select进入default分支"]
SELECT --> ROLLBACK["回滚用户槽位如果有"]
SELECT --> INC["原子递增RejectedTasks"]
SELECT --> RETURN["返回ErrPoolFull"]
RETURN --> HTTP503["Handler层映射为HTTP 503<br/>响应体系统繁忙请稍后重试"]
背压 vs 阻塞:
| 策略 | 行为 | 适用场景 |
|---|---|---|
| 非阻塞拒绝Gen2D | 立即返回错误 | 用户交互型 API,快速失败 |
| 阻塞等待 | 阻塞直到有空位 | 批处理系统,不能丢任务 |
Gen2D 选择非阻塞拒绝,因为用户期望快速得到反馈,而非无限等待。
[!tip] 设计权衡
选择「非阻塞拒绝」意味着可能丢失用户的提交意图。但在 AI 生成场景中,用户可以 重新点击提交按钮——这种短暂的操作成本远低于服务器因连接堆积导致的雪崩风险。这就是经典的 「可用性 > 一致性」 抉择。
优雅关闭
协程池支持优雅关闭,确保正在执行的任务有时间完成:
graph TB
SIG["收到SIGINT / SIGTERM"] --> STOP_CONSUME["consumeCancel<br/>停止消费者不再接收新任务"]
STOP_CONSUME --> SHUTDOWN["pool.Shutdownctx<br/>30秒超时"]
SHUTDOWN --> SWAP["closed.Swaptrue<br/>停止接收新任务"]
SHUTDOWN --> CANCEL["cancel<br/>通知Worker停止取任务"]
SHUTDOWN --> WAIT["wg.Wait<br/>等待所有Worker退出"]
WAIT --> SUCCESS{"成功?"}
SUCCESS -->|是| LOG_OK["日志workerpool shutdown gracefully"]
SUCCESS -->|否| LOG_TIMEOUT["日志workerpool shutdown timeout"]
Shutdown 返回值:
| 返回值 | 含义 |
|---|---|
true |
所有任务正常完成 |
false |
超时,部分任务可能丢失 |
Metrics 指标
协程池内置原子计数器,支持运行时观测:
| 指标 | 类型 | 说明 |
|---|---|---|
ActiveWorkers |
atomic.Int32 |
当前正在执行任务的 Worker 数 |
QueuedTasks |
atomic.Int32 |
当前排队等待的任务数 |
CompletedTasks |
atomic.Int64 |
已完成任务总数 |
FailedTasks |
atomic.Int64 |
失败任务总数 |
SubmittedTasks |
atomic.Int64 |
提交任务总数 |
RejectedTasks |
atomic.Int64 |
被拒绝任务总数(队列满或用户限流) |
所有指标通过 Metrics() 方法返回只读快照,同时上报 Prometheus。
Task 结构体
type Task struct {
ID string // 任务唯一标识
UserID string // 所属用户 ID
Fn func(ctx context.Context) error // 任务执行函数
Priority int // 优先级(预留)
}
每个任务携带超时 context(默认 10 分钟),从池的根 context 派生,确保优雅关闭时能取消正在执行的任务。
与 TaskQueue 的协作
graph LR
TQ["TaskQueue全局排队"] --> CONSUMER["Consumer消费消息"]
CONSUMER --> WP["WorkerPoolSubmit单机并发控制"]
WP --> WORKER["Worker执行Pipeline"]
| 组件 | 职责 | 范围 |
|---|---|---|
| TaskQueue | 持久化排队,解耦提交与执行 | 全局(可跨机器) |
| WorkerPool | 单机并发控制,per-user 限流 | 单机 |
| Consumer | 桥接 TaskQueue 与 WorkerPool | 单机 |
关联文档
- 00-索引 — 文档导航与架构总览图
- 01-系统总览 — 分层架构与依赖注入
- 03-任务队列 — 可插拔队列接口
- 11-Consumer-Producer桥接 — TaskQueue 到 WorkerPool 的解耦