Asynq 是一个 Go 编写的异步任务队列,基于 Redis 实现,支持任务重试、延迟任务、定时调度等特性。
Asynq 简介
Asynq 是一个简单但强大的异步任务队列系统,常用于:
- 邮件通知
- 订单处理
- 图像/音频转码
- 后台批量任务
- 定时调度任务
官网和文档: 🔗 github.com/hibiken/asy...
安装
安装 Redis
bash
brew install redis
brew services start redis
或使用 Docker:
bash
docker run -d -p 6379:6379 redis
安装 Asynq 库
bash
go get github.com/hibiken/asynq
核心概念
概念 | 说明 |
---|---|
Task | 一个任务,由类型和载荷组成。 |
Client | 创建任务、加入队列的客户端。 |
Server | 任务执行者(Worker)。 |
Queue | 任务队列,支持多队列 |
Retry | 支持失败自动重试。 |
Schedule | 定时/延迟任务执行。 |
快速上手
定义任务类型
go
// tasks/types.go
const TypeEmailWelcome = "email:welcome"
提交任务(Client)
go
client := asynq.NewClient(asynq.RedisClientOpt{Addr: "localhost:6379"})
defer client.Close()
payload, _ := json.Marshal(map[string]interface{}{"user_id": 42})
task := asynq.NewTask(TypeEmailWelcome, payload)
info, err := client.Enqueue(task) // 立即执行
Worker 执行任务(Consumer)
go
func main(){
srv := asynq.NewServer(
asynq.RedisClientOpt{Addr: "localhost:6379"},
asynq.Config{Concurrency: 10},
)
mux := asynq.NewServeMux()
mux.HandleFunc(TypeEmailWelcome,HandleEmailWelcome)
srv.Run(mux)
}
func HandleEmailWelcome(ctx context.Context, t *asynq.Task)error{
var p map[string]interface{}
json.Unmarshal(t.Payload(), &p)
fmt.Println("发送欢迎邮件给用户:", p["user_id"])
return nil
}
任务调度与重试机制
延迟任务
go
client.Enqueue(task, asynq.ProcessIn(10*time.Second))
设置过期时间(TTL)
go
client.Enqueue(task, asynq.Retention(1*time.Hour))
设置最大重试次数
go
client.Enqueue(task, asynq.MaxRetry(3))
Handler 深入探讨
使用 ServeMux
go
mux := asynq.NewServeMux()
mux.Handle("email:welcome", welcomeEmailHandler)
mux.Handle("email:reminder", reminderEmailHandler)
mux.Handle("email:", defaultEmailHandler) // catchall for all other task types with a prefix "email:"
使用 ServeMux,您可以注册多个 Handlers。 它将每个任务的类型与已注册模式的列表进行匹配,并为与任务的类型名称最匹配的模式调用处理程序。
使用 Middleware
如果你需要在任务之前或者任务之后执行一些代码,就可以用middleware来实现。middleware接收一个Handler,并返回 一个Handler的函数。
go
func loggingMiddleware(h asynq.Handler) asynq.Handler {
return asynq.HandlerFunc(func(ctx context.Context, t *asynq.Task) error {
start := time.Now()
log.Printf("Start processing %q", t.Type())
err := h.ProcessTask(ctx, t)
if err != nil {
return err
}
log.Printf("Finished processing %q: Elapsed Time = %v", t.Type(), time.Since(start))
return nil
})
}
// 使用 middleware
myHandler = loggingMiddleware(myHandler)
// 或
ux := NewServeMux()
mux.Use(loggingMiddleware)
Grouping middlewares
如果想要把某一个中间件用于一组任务,就可以通过组合多个ServeMux来实现。需要注意,每个组中的任务其类型名称中需要有相同的前缀
go
productHandlers := asynq.NewServeMux()
productHandlers.Use(productMiddleware) // 应用给product相关任务
productHandlers.HandleFunc("product:update", productUpdateTaskHandler)
orderHandlers := asynq.NewServeMux()
orderHandler.Use(orderMiddleware) // 应用给order相关任务
orderHandlers.HandleFunc("order:refund", orderRefundTaskHandler)
mux := asynq.NewServeMux()
mux.Use(someGlobalMiddleware) // 给所有任务应用
mux.Handle("product:", productHandlers)
mux.Handle("order:", orderHandlers)
if err := srv.Run(mux); err != nil {
log.Fatal(err)
}
任务的生命周期
将任务放入队列的时候,asynq会在内部对任务进行管理,以确保在指定的时间去使用任务调用处理程序。在次过程中任务就会经历不同的生命周期状态
- Scheduled :任务正在等待将来处理( 仅适用于具有 ProcessAt 或 ProcessIn 选项的任务 )
ProcessIn: 延迟多久后再执行任务(相对时间)
延迟10秒执行 : client.Enqueue(task, asynq.ProcessIn(10*time.Second)))
ProcessAt: 在某个具体的时间点执行任务(绝对时间)
设定在2小时后执行 :client.Enqueue(task, asynq.ProcessAt(time.Now().Add(2 * time.Hour))))
- Pending :任务已准备好进行处理,并将由空闲工作程序选取
- Active : 任务正在由工作程序处理(即 handler 与任务一起调用)。
- Retry : 无法处理任务,并且任务正在等待将来重试。
- Archived :任务已达到其最大重试次数,并存储在存档中以供手动检查
- Completed : 任务已成功处理并保留,直到保留 TTL 过期( 仅适用于具有 Retention 选项的任务 )。
Retention : 用于设置已完成任务在 Redis 中保留多久,之后会自动被删除。
client.Enqueue(task, asynq.Retention(24 * time.Hour))
任务执行成功后,结果在 Redis 中保存 24 小时,然后自动清除
默认情况下,任务执行成功后,Asynq 会立即从 Redis 中删除任务记录
状态转换图
