用chan做任务队列一上线就卡死,因无缓冲通道导致生产者阻塞;需设合理缓冲(100~1000),配合context.Context控制启停,Task宜存数据+统一Handler,生产环境必须换Redis实现持久化与多实例分发。

用 chan 做任务队列,为什么一上线就卡死?
直接用 make(chan Task) 无缓冲通道当队列,90% 的新手会在高并发时被阻塞住——生产者一发任务就 hang 住,因为没消费者在读。必须设缓冲:make(chan Task, 100),否则连基本可用都谈不上。
- 缓冲大小不是越大越好:设成 10000 可能导致内存暴涨或 OOM,建议从 100~1000 起步,按压测结果调整
- 别用
range配合close()做退出控制:worker 一旦开始range,就无法响应外部停止信号,容易 goroutine 泄漏 - 真正安全的启停靠
context.Context+select,不是靠关 channel
Task 结构体里该放函数还是放数据?
放函数(如 func())最简单,但会导致闭包捕获变量生命周期混乱,调试困难;放数据 + 统一 Handler 更可控,也方便加日志、重试、超时。
- 推荐结构:
type Task struct { ID string; Payload map[string]interface{}; Handler func(map[string]interface{}) error } - Handler 不要直接操作数据库或发 HTTP 请求:先包装成可测试的纯函数,再由 worker 调用
- 避免在 Task 里存大对象(比如原始图片字节),应只存路径或 ID,由 Handler 按需加载
本地开发用 chan,上生产为什么必须换 Redis?
因为 chan 是进程内通信,重启服务 = 所有未消费任务丢失;多实例部署时,每个实例都有自己的 channel,任务无法分发到空闲节点。
- Redis 的
LPUSH+BLPOP是最轻量的跨进程队列方案,go-redis 库封装友好 - 别手写 JSON 序列化:用
json.Marshal前检查Payload是否含不可序列化字段(如func、chan、map[interface{}]interface{}) - Redis worker 必须用
BLPOP(阻塞式),不用LPOP+time.Sleep,否则 CPU 白耗、延迟高
异步任务失败了,怎么不丢也不炸?
没有重试机制的异步队列,等于裸奔。但盲目重试又可能把下游打挂——关键在“可控重试”。
立即学习“go语言免费学习笔记(深入)”;
- 在
Task中加字段:Retries int和MaxRetry int,每次失败自增,超限才进死信逻辑 - 重试不要立刻塞回原队列:用
time.AfterFunc或 Redis 的 delay queue(如zset+ 定时轮询)做退避 - 所有失败必须打日志,且日志里带
Task.ID和error.Error(),否则排查时根本找不到上下文
真正难的不是写完一个能跑的队列,而是让任务在崩溃、重启、网络抖动、下游超时这些真实场景下,依然可追溯、可重放、不重复。channel 是起点,不是终点。










