简单、可靠、高效的Go分布式任务队列
Asynq是一个Go库,用于排队任务并通过工作者异步处理这些任务。它以Redis为后盾,设计成可扩展且容易上手。
Asynq的工作原理的高级概述:
- 客户端将任务放在队列中
- 服务器从队列中提取任务,并为每个任务启动一个worker goroutine
- 任务由多个工作者同时处理
任务队列被用作一种在多台机器上分配工作的机制。一个系统可以由多个工作者服务器和经纪商组成,为高可用性和横向扩展让路。
用例
特点
- 保证一个任务至少有一次执行的机会
- 任务的调度
- 对失败的任务进行重试
- 在工作者崩溃的情况下自动恢复任务
- 加权的优先级队列
- 严格的优先级队列
- 添加任务的低延迟,因为Redis的写入速度很快
- 使用唯一选项消除任务的重复性
- 允许每个任务有超时和最后期限
- 允许聚集一组任务来批处理多个连续的操作
- 灵活的处理程序接口,支持中间件
- 能够暂停队列,停止处理队列中的任务
- 周期性任务
- 支持Redis集群,用于自动分片和高可用性
- 支持Redis Sentinels的高可用性
- 与Prometheus集成以收集和可视化队列指标
- 检查和远程控制队列和任务的Web UI
- 通过CLI检查和远程控制队列和任务
稳定性和兼容性
状态:该库目前正在进行大量的开发,频繁地对API进行修改。
☝️重要提示:目前的主要版本是零(
v0.x.x),以适应快速开发和快速迭代,同时获得用户的早期反馈(感谢对API的反馈!)。在v1.0.0发布之前,如果没有主要版本的更新,公共API可能会发生变化。
快速入门
请确保你已经安装了Go(下载)。需要1.14 或更高的版本。
通过创建一个文件夹,然后在该文件夹内运行go mod init github.com/your/repo (了解更多)来初始化你的项目。然后用以下命令安装Asynq库 go get命令安装Asynq库。
go get -u github.com/hibiken/asynq
确保你在本地或从Docker容器中运行一个Redis服务器。需要4.0 或更高版本。
接下来,编写一个封装任务创建和任务处理的包。
package tasks
import (
"context"
"encoding/json"
"fmt"
"log"
"time"
"github.com/hibiken/asynq"
)
// A list of task types.
const (
TypeEmailDelivery = "email:deliver"
TypeImageResize = "image:resize"
)
type EmailDeliveryPayload struct {
UserID int
TemplateID string
}
type ImageResizePayload struct {
SourceURL string
}
//----------------------------------------------
// Write a function NewXXXTask to create a task.
// A task consists of a type and a payload.
//----------------------------------------------
func NewEmailDeliveryTask(userID int, tmplID string) (*asynq.Task, error) {
payload, err := json.Marshal(EmailDeliveryPayload{UserID: userID, TemplateID: tmplID})
if err != nil {
return nil, err
}
return asynq.NewTask(TypeEmailDelivery, payload), nil
}
func NewImageResizeTask(src string) (*asynq.Task, error) {
payload, err := json.Marshal(ImageResizePayload{SourceURL: src})
if err != nil {
return nil, err
}
// task options can be passed to NewTask, which can be overridden at enqueue time.
return asynq.NewTask(TypeImageResize, payload, asynq.MaxRetry(5), asynq.Timeout(20 * time.Minute)), nil
}
//---------------------------------------------------------------
// Write a function HandleXXXTask to handle the input task.
// Note that it satisfies the asynq.HandlerFunc interface.
//
// Handler doesn't need to be a function. You can define a type
// that satisfies asynq.Handler interface. See examples below.
//---------------------------------------------------------------
func HandleEmailDeliveryTask(ctx context.Context, t *asynq.Task) error {
var p EmailDeliveryPayload
if err := json.Unmarshal(t.Payload(), &p); err != nil {
return fmt.Errorf("json.Unmarshal failed: %v: %w", err, asynq.SkipRetry)
}
log.Printf("Sending Email to User: user_id=%d, template_id=%s", p.UserID, p.TemplateID)
// Email delivery code ...
return nil
}
// ImageProcessor implements asynq.Handler interface.
type ImageProcessor struct {
// ... fields for struct
}
func (processor *ImageProcessor) ProcessTask(ctx context.Context, t *asynq.Task) error {
var p ImageResizePayload
if err := json.Unmarshal(t.Payload(), &p); err != nil {
return fmt.Errorf("json.Unmarshal failed: %v: %w", err, asynq.SkipRetry)
}
log.Printf("Resizing image: src=%s", p.SourceURL)
// Image resizing code ...
return nil
}
func NewImageProcessor() *ImageProcessor {
return &ImageProcessor{}
}
在你的应用程序代码中,导入上述包并使用 Client来把任务放在队列中。
package main
import (
"log"
"time"
"github.com/hibiken/asynq"
"your/app/package/tasks"
)
const redisAddr = "127.0.0.1:6379"
func main() {
client := asynq.NewClient(asynq.RedisClientOpt{Addr: redisAddr})
defer client.Close()
// ------------------------------------------------------
// Example 1: Enqueue task to be processed immediately.
// Use (*Client).Enqueue method.
// ------------------------------------------------------
task, err := tasks.NewEmailDeliveryTask(42, "some:template:id")
if err != nil {
log.Fatalf("could not create task: %v", err)
}
info, err := client.Enqueue(task)
if err != nil {
log.Fatalf("could not enqueue task: %v", err)
}
log.Printf("enqueued task: id=%s queue=%s", info.ID, info.Queue)
// ------------------------------------------------------------
// Example 2: Schedule task to be processed in the future.
// Use ProcessIn or ProcessAt option.
// ------------------------------------------------------------
info, err = client.Enqueue(task, asynq.ProcessIn(24*time.Hour))
if err != nil {
log.Fatalf("could not schedule task: %v", err)
}
log.Printf("enqueued task: id=%s queue=%s", info.ID, info.Queue)
// ----------------------------------------------------------------------------
// Example 3: Set other options to tune task processing behavior.
// Options include MaxRetry, Queue, Timeout, Deadline, Unique etc.
// ----------------------------------------------------------------------------
task, err = tasks.NewImageResizeTask("https://example.com/myassets/image.jpg")
if err != nil {
log.Fatalf("could not create task: %v", err)
}
info, err = client.Enqueue(task, asynq.MaxRetry(10), asynq.Timeout(3 * time.Minute))
if err != nil {
log.Fatalf("could not enqueue task: %v", err)
}
log.Printf("enqueued task: id=%s queue=%s", info.ID, info.Queue)
}
Next, start a worker server to process these tasks in the background. To start the background workers, use Server and provide your Handler to process the tasks.
You can optionally use ServeMux to create a handler, just as you would with net/http Handler.
package main
import (
"log"
"github.com/hibiken/asynq"
"your/app/package/tasks"
)
const redisAddr = "127.0.0.1:6379"
func main() {
srv := asynq.NewServer(
asynq.RedisClientOpt{Addr: redisAddr},
asynq.Config{
// Specify how many concurrent workers to use
Concurrency: 10,
// Optionally specify multiple queues with different priority.
Queues: map[string]int{
"critical": 6,
"default": 3,
"low": 1,
},
// See the godoc for other configuration options
},
)
// mux maps a type to a handler
mux := asynq.NewServeMux()
mux.HandleFunc(tasks.TypeEmailDelivery, tasks.HandleEmailDeliveryTask)
mux.Handle(tasks.TypeImageResize, tasks.NewImageProcessor())
// ...register other handlers...
if err := srv.Run(mux); err != nil {
log.Fatalf("could not run server: %v", err)
}
}
接下来,启动一个工人服务器来在后台处理这些任务。要启动后台工作者,使用 Server并提供你的 Handler来处理这些任务。
你可以选择使用 ServeMux来创建一个处理程序,就像你用 net/http处理程序。
package
有关该库的更详细介绍,请参见我们的入门指南。
要了解更多关于asynq 功能和API的信息,请看包godoc。
网络用户界面
Asynqmon是一个基于Web的工具,用于监控和管理Asynq队列和任务。
下面是Web UI的几个屏幕截图。
队列视图
任务视图
设置和自适应黑暗模式
关于如何使用该工具的细节,请参考该工具的README。
命令行工具
Asynq提供了一个命令行工具来检查队列和任务的状态。
要安装CLI工具,运行以下命令。
go install github.com/hibiken/asynq/tools/asynq
关于如何使用该工具的细节,请参考该工具的README。
贡献
我们欢迎并感谢社区的任何贡献(GitHub问题/PR、Gitter频道上的反馈等)。
在贡献之前,请参考贡献指南。




