Asynq 不能直接用 asynq.NewClient 连 Redis 集群,因其默认使用单节点配置(asynq.RedisClientOpt),底层虽基于 redis.UniversalClient,但未自动适配集群拓扑,需显式构造 redis.ClusterClient 或 redis.FailoverClient 并包装为 asynq.RedisConnOpt,否则会报 ERR This instance is not a master 或连接超时。

Asynq 为什么不能直接用 asynq.NewClient 连 Redis 集群?
因为 asynq.NewClient 底层用的是 redis.UniversalClient,但 Asynq 默认只传了单节点配置(asynq.RedisClientOpt),遇到 Redis Cluster 或 Sentinel 就会报 ERR This instance is not a master 或连接超时。它不自动适配集群拓扑,得手动绕过。
- 必须显式构造
redis.ClusterClient或redis.FailoverClient,再包装成asynq.RedisConnOpt - 别直接填
Addr: "cluster-node:6379"——Asynq 会把它当单机连,集群里没 master 角色就失败 - 如果用哨兵,得传
MasterName和多个SentinelAddrs,且确保哨兵能返回当前 master 地址
任务注册时怎么让 handler 支持依赖注入?
Asynq 默认 handler 是函数类型 func(*asynq.Context, *asynq.Task) error,没法直接塞入 DB 实例或 config。硬编码全局变量会导致测试难、热重载卡死、多租户场景冲突。
- 用闭包封装依赖:写一个工厂函数,比如
MakeEmailHandler(db *sql.DB, smtp *smtp.Client),返回实际 handler 函数 - 避免在 handler 里 new 数据库连接——每次任务都建连接会打爆连接池,应在 factory 外部初始化好再传入
- 如果用 Wire 或 fx,把 handler 构建逻辑放在 injector 初始化阶段,而不是每次
ProcessTask时调用
如何安全地更新正在运行的 Asynq Server?
直接 kill -9 或滚动发布会导致正在处理的任务被中断,Redis 里状态变成 processing 卡住,重试机制也救不回来。
- 发
SIGTERM后,Asynq 会等待ShutdownTimeout(默认 5s)让当前 task 完成,但不会接受新任务;务必设足够长的 timeout,尤其对耗时操作 - 加健康检查端点(比如
/healthz),K8s 的 readiness probe 要等 AsynqIsRunning()返回 true 才导流 - 上线前用
asynqmon看 pending/running 数量,确认降到 0 再切流量
为什么 asynq.CancelTask 有时不生效?
Cancel 不是强制终止 goroutine,只是把任务状态从 processing 改成 cancelled,真正起作用靠 handler 主动轮询 ctx.Err() 并退出。如果 handler 里有阻塞 IO(比如没设 timeout 的 HTTP 请求)或死循环,cancel 就失效。
立即学习“go语言免费学习笔记(深入)”;
- 所有 long-running 操作必须带 context:用
http.NewRequestWithContext(ctx, ...)、db.QueryContext(ctx, ...) - 定期检查
if ctx.Err() != nil { return ctx.Err() },别只在开头 check 一次 - Cancel 后任务仍可能出现在
completed列表里——Asynq 认为“执行完并返回 error”也算完成,不是真取消了执行过程


















