应为每条任务创建独立 context.WithTimeout,而非包裹整个 worker;HTTP 用 NewRequestWithContext、DB 用 QueryContext,轮询需 select 监听 ctx.Done(),并 defer cancel 防泄漏。

直接用 context.WithTimeout 包裹单条任务处理逻辑,而不是整个 worker goroutine —— 后者会粗暴中断所有正在跑的任务,前者才真正实现“单任务超时可退、其余照常消费”。
每个任务必须配独立 context.Context
全局或 worker 级别的 context 一 cancel,整条流水线就停摆。真实场景中,某条消息卡在第三方 API 调用上,不该拖垮后面几百条正常消息。
- 在生产者发送前,为每条
Message创建独立 ctx:ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - 把
ctx和cancel一起塞进消息结构体(或用闭包绑定),确保 worker 拿到后能传给所有下游调用 - worker 内部绝不能用
context.Background()重开,否则超时信号断链 - 务必
defer cancel(),哪怕提前 return 也要执行,否则 timer 泄漏
http.Client 和 db.QueryContext 必须显式接入
只在函数开头检查 ctx.Err() 没用,真正阻塞点(HTTP 请求、DB 查询、time.Sleep)必须原生支持 context,否则超时信号根本进不去。
- HTTP 请求必须用
http.NewRequestWithContext(ctx, ...)+client.Do(req),禁用http.Get - 数据库查询必须用
db.QueryContext(ctx, ...),原生db.Query不响应取消 - 自定义轮询或等待逻辑,每次循环开头都要
select { case ,不能只 check 一次 - 纯 CPU 计算无法被中断,需手动插入检查点,比如每千次迭代加一次
select
worker 启停同步不靠 close(ch),而靠 sync.WaitGroup
只 close channel,主 goroutine 就 exit,正在处理的 task 全被 kill,日志里只剩前几条记录 —— 这不是超时问题,是任务丢失。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 每个 worker 启动时调
wg.Add(1),结尾defer wg.Done() - 主 goroutine 在 close channel 后,必须等
wg.Wait()才退出 -
wg不能传值(会复制),要用指针或闭包捕获 - 若 worker 内部 panic,需
recover()并转发至死信 channel,否则Done()不会被调用
缓冲 channel 容量不是越大越好,要盯住 len(ch) == cap(ch)
设 make(chan Task, 10000) 看似保险,实则掩盖了真实瓶颈:worker 卡在 DB 写入、没加 timeout、或未复用连接。
- 初始缓冲设 100,上线后打日志:当
len(ch) > 0.8*cap(ch)时告警 - 每个 task 处理前后打时间戳,统计耗时分布;若 P99 > 500ms,优先优化单条逻辑,而非扩 buffer
- 避免多个来源共用一个 channel 却不带
Source字段,否则超时策略无法按业务区分
最易被忽略的是:context 超时是协作式的,不是抢占式的。它只发信号,不杀协程 —— 你得在每个可能阻塞的地方主动监听 ctx.Done(),否则再严谨的 timeout 设置也等于没设。

















