必须用sync.waitgroup等待worker退出,仅close(channel)会导致任务丢失;需在worker中add/defer done、主goroutine调用wait,同时为每条消息配独立context超时、加panic recover并转发至死信通道。

直接用 chan 做工作队列是可行的,但生产环境里“高并发消费”真正卡点不在 channel 本身,而在 worker 启停同步、缓冲区水位控制、错误隔离和资源复用这四件事上。
worker 必须用 sync.WaitGroup 等待退出,不能只靠 close(ch)
现象:程序启动后几秒就退出,log 只打印了前几个 task,后面全丢了。
原因:for task := range ch 确实会在 close(ch) 后退出循环,但它不等 worker 里的 process() 执行完——比如你有 time.Sleep(2 * time.Second),主 goroutine 已经 exit,进程直接 kill 掉所有 goroutine。
必须做:
- 在每个 worker goroutine 开头 wg.Add(1),结尾 defer wg.Done()
- 主程序调用 wg.Wait() 再 return
- 别把 wg 当参数传值(会复制),要用指针或闭包捕获
缓冲 channel 容量不是越大越好,要盯住 len(ch) == cap(ch) 日志
设 make(chan Task, 1000) 看似保险,实际掩盖了消费瓶颈。
常见误判:
- 生产者发得快,len(ch) 长期接近 cap(ch) → 以为是队列太小,盲目扩到 10000
- 真实问题:worker 卡在 DB 写入、HTTP 调用或没加 context timeout
建议:
- 初始设 100,上线后打日志:当 len(ch) > 0.8*cap(ch) 时告警
- 每个 worker 处理前打 start timestamp,结束后打 end,算出耗时分布
- 若 P99 > 500ms,优先优化单条处理逻辑,而不是加 buffer 或 worker 数量
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
多个队列共用一个任务 channel 时,来源标识必须打在消息结构体里
当你同时消费 Kafka topic A、RabbitMQ queue B、Redis Stream C,它们的消息格式可能一样,但业务逻辑不同。
别这么做:
- 把所有消息 unmarshal 成同一个 struct{Data string},再靠外部变量判断来源
应该:
- 定义统一消息结构:type Message struct { Source string; Payload []byte; Timestamp time.Time }
- 每个消费者 goroutine 在 send 前填 msg.Source = "kafka-topic-a"
- worker 里用 switch msg.Source 分支处理,避免 if-else 嵌套过深
- Source 字符串别硬编码,从配置读,方便灰度切换
worker 内部必须带 context 控制单条超时,不能依赖全局 cancel
全局 ctx 一 cancel,所有正在跑的 worker 全中断,哪怕只剩 100ms 就处理完了。
正确做法:
- 每个消息配独立 ctx:ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
- 把 cancel() 放在 defer 里,确保无论成功失败都释放
- DB 查询、HTTP 调用、序列化全部传这个子 ctx,不是主 ctx
- 如果业务允许重试,失败时检查 errors.Is(ctx.Err(), context.DeadlineExceeded),区分是超时还是其他错误
最易被忽略的点:worker panic 后不会自动重启,也不会通知上游。你得在 worker 循环里加 recover,且 recover 后要记录 panic stack 并重新 send 到 dead-letter channel,否则故障会静默丢失。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










