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。硬编码全局变量会导致测试难、热重载卡死、多租户场景冲突。
在 Go 中使用 google/wire 实现编译时依赖注入——wire.NewSet、wire.Build、wire.Bind(接口→实现)、wire.Struct、wire.Value、wire.Interface
- 用闭包封装依赖:写一个工厂函数,比如
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 就失效。
- 所有 long-running 操作必须带 context:用
http.NewRequestWithContext(ctx, ...)、db.QueryContext(ctx, ...) - 定期检查
if ctx.Err() != nil { return ctx.Err() },别只在开头 check 一次 - Cancel 后任务仍可能出现在
completed列表里——Asynq 认为“执行完并返回 error”也算完成,不是真取消了执行过程
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










