Asynq是基于Redis的Go分布式任务队列,支持延迟/周期任务、自动重试、Web UI及状态查询;需配置Redis,任务提交与处理均在Go代码中完成,轻量可靠,适合中小项目快速落地。

用 github.com/hibiken/asynq 快速搭起可靠任务队列
Go 里没有内置任务队列,直接用 goroutine + channel 做简单异步还行,但一涉及重试、延迟、失败持久化、Web 管理界面,就得上专业库。asynq 是目前最成熟的选择:基于 Redis,轻量、稳定、有官方 Web UI,且 API 清晰。
它不依赖复杂部署(只要 Redis 可连),也不强制你改架构——任务定义、提交、处理全在 Go 代码里完成,适合中小项目快速落地。
- 任务结构体必须实现
json.Marshaler(通常加个json:tag 就够) - handler 函数签名固定为
func(ctx context.Context, t *asynq.Task) error - Redis 地址通过
asynq.RedisClientOpt{Addr: "localhost:6379"}传入,密码和 DB 可选配 - 别把大对象(比如原始图片字节流)塞进任务 payload——序列化开销大,Redis 内存压力高;建议只存 ID 或 URL,消费时再拉取
提交任务:支持立即执行、延迟执行与重复调度
任务不是“发出去就完事”,不同场景要不同提交方式。asynq 把这个拆得很清楚:client.Enqueue 是即时的,client.EnqueueIn 和 client.EnqueueAt 控制时间,client.Schedule 配合 cron 表达式做周期任务。
注意:延迟时间单位是 time.Duration,别错写成秒数整型;cron 调度需单独启一个 asynq.Scheduler 实例,不能复用 worker。
立即学习“go语言免费学习笔记(深入)”;
// 提交一个 5 秒后执行的邮件任务
task := asynq.NewTask("send_email", map[string]interface{}{"user_id": 123})
_, _ = client.EnqueueIn(task, 5*time.Second)
<p>// 每天上午 9 点发日报(需另起 scheduler)
sched := asynq.NewScheduler(r, &asynq.SchedulerOpts{
Location: time.UTC,
})
sched.Register("0 0 9 <em> </em> *", task) // 注意 cron 格式是秒分时日月周
消费任务:Worker 启动、中间件与失败重试策略
worker 不是常驻进程,而是你显式调用 srv.Run() 启动的——这意味着你可以按需启停、热更 handler、甚至用 systemd 管理生命周期。
重试逻辑由 asynq 自动处理:默认最多重试 25 次,间隔指数退避(1s → 2s → 4s…)。你可以用 asynq.MaxRetryCount(3) 覆盖,但别设成 0——网络抖动或临时 DB 不可用时,0 次重试等于丢任务。
- 用
asynq.Use加中间件,比如记录耗时、打日志、注入 trace ID - handler 里 panic 会被捕获并计入失败次数;返回
asynq.SkipRetry错误则不再重试(适合业务校验不通过的场景) - 别在 handler 里长时间阻塞(如死循环或无超时 HTTP 调用),会卡住整个 worker 的并发吞吐
查任务状态:从 Redis 直查不如用内置的 asynq.Client 方法
asynq 把任务状态存在 Redis 的不同 key 结构里(processing、retry、completed 等),自己拼 SCAN 或 HGETALL 容易漏或错。直接用 client.GetTaskInfo 查单个任务,或 client.Inspect 查某状态下的批量任务更稳。
注意:completed 和 archived 任务默认只保留 24 小时(可配 asynq.ServerOption{CompletedTaskRetention: 72 * time.Hour}),查历史任务前先确认 retention 设置。
// 查 ID 为 "abc123" 的任务当前状态(pending/running/failed/completed)
info, _ := client.GetTaskInfo("abc123")
fmt.Printf("state: %s, error: %s\n", info.State, info.LastError)
<p>// 查最近 10 个失败任务
failed, <em> := client.Inspect(asynq.Failed, 0, 10)
for </em>, t := range failed {
fmt.Printf("failed task: %s, err: %s\n", t.ID, t.LastError)
}
状态查询不是实时强一致的——比如任务刚被 worker 取出但还没开始执行,GetTaskInfo 可能仍返回 pending。真要精确到毫秒级状态,得结合应用层日志或外部追踪系统。


















