chan适合单机低频无持久化场景,但易因无缓冲或无消费者导致阻塞;asynq等需注意Redis配置、队列名匹配及并发数合理设置。

用 channel 做简单任务队列,够用但有边界
Go 里最轻量的任务队列就是用 chan,适合单机、低频、无持久化要求的场景。比如定时清理缓存、发几条内部通知。
常见错误是直接把 chan 当成无限缓冲池用——忘了它默认是无缓冲的,或者 make(chan Task, 0) 写成 make(chan Task) 却没配 goroutine 消费,结果一发任务就卡死主流程。
-
make(chan Task, N)的N不是“最多存 N 个”,而是“阻塞前最多缓存 N 个”;超过就会阻塞发送方 - 必须确保至少一个 goroutine 在持续
range或<-ch,否则所有写入都会挂起 - 没有失败重试、超时、进度追踪,出错就丢了
选第三方库前先看清楚要不要持久化和分发
一旦任务不能丢(比如支付回调、消息推送),或者要跨进程/机器调度,channel 就不够用了。这时候得上带存储的方案。
主流选择有 asynq、machinery、goworker,但别急着装。先问自己:任务失败后要不要自动重试?是否需要按优先级分队列?有没有多语言消费者?
立即学习“go语言免费学习笔记(深入)”;
-
asynq依赖 Redis,支持延迟任务、失败重试、Web UI,但不支持 RabbitMQ/Kafka -
machinery支持多种 broker(Redis/RabbitMQ),但默认不存执行日志,排查失败得自己加钩子 - 如果已有 Kafka 集群且团队熟悉,用
segmentio/kafka-go+ 自定义 worker 更可控,但轮子得自己转
asynq 启动后任务不消费?检查 ServerOption 和 Redis 连接
新手常遇到:任务用 client.Enqueue 发出去了,asynq.NewServer 也跑起来了,但 handler 死活不触发。大概率是配置漏项或连接异常。
典型现象是日志里没看到 Started processing,或者报 redis: nil pointer dereference 这类底层错误,但没被显式捕获。
- 必须传
asynq.RedisClientOpt{Addr: "localhost:6379"},不能只传空结构体 -
Server默认只监听default队列,发任务时若指定了其他队列名(如asynq.Queue("critical")),就得在NewServer里显式声明asynq.Queues(map[string]int{"critical": 10}) - Redis 密码、DB 号、TLS 设置不对会导致静默失败——建议启动时加一行
redis.Ping()校验连接
并发数设太高反而拖慢,压测前先看 CPU 和 Redis QPS
asynq 的 Concurrency 参数不是越大越好。它控制的是单个 Server 实例并发执行任务的 goroutine 数,背后共享同一个 Redis 连接池和 CPU 核心。
设成 100 看似吞吐高,实际可能让 Redis 成瓶颈,或者 Go runtime 调度开销反超收益。线上见过设 50 导致 Redis CPU 打满、平均延迟从 5ms 涨到 300ms 的案例。
- 初始值建议从
runtime.NumCPU() * 2开始,比如 8 核机器设 16 - 观察 Redis 的
INFO commandstats里cmdstat_lpop和cmdstat_rpush的调用量和耗时 - 如果任务本身是 CPU 密集型(如压缩、加解密),并发数应远小于 I/O 密集型任务
真正难的不是怎么搭起来,是怎么让队列在流量毛刺、Redis 抖动、任务逻辑偶发 panic 时不雪崩——这些得靠限流、降级、独立监控指标,而不是换一个更“高级”的库。


















