asynq | go-zero学习 第五章 集成asynq
1 前提
asynq
是基于redis
实现的,且需要高版本的redis
,本文使用的是Redis-x64-5.0.14.1
。
redis下载地址:
- linux/mac系统:https://redis.io/download
- windows系统:https://github.com/tporadowski/redis/releases
2 源代码
2.1 官方介绍
- Asynq是Go语言中简单、可靠、高效的分布式任务队列。
- Asynq 是一个 Go 库,用于对任务进行排队并与工作线程异步处理它们。它由Redis支持,旨在可扩展且易于入门。
- Asynq支持高可用性和水平扩展。
Asynq 工作原理的高级概述:
- 客户端将任务放入队列
- 服务器从队列中取出任务并为每个任务启动一个工作协程
- 任务由多个worker同时处理
Asynq的特性:
- 保证至少执行一次任务
- 支持任务调度
- 失败任务的重试
- 在工作进程崩溃时自动恢复任务
- 权重优先级队列
- 严格的优先级队列
- 由于 Redis 中的写入速度很快,因此添加任务的延迟较低
- 使用唯一选项对任务进行去重
- 允许为每个任务设置超时和截止时间
- 允许聚合任务组来批处理多个连续操作
- 灵活的处理程序接口,支持中间件
- 能够暂停队列以停止正在处理队列中的任务
- 定时任务
- 支持Redis Cluster进行自动分片和高可用性
- 支持Redis Sentinels实现高可用性
- 与Prometheus集成,以收集和可视化队列指标
- Web界面,用于检查和远程控制队列和任务(有Web页面)
- CLI命令行界面,用于检查和远程控制队列和任务(有CLI页面)
※2.2 官方Wiki文档(强烈推荐阅读)
在github
的asynq
代码仓库里,可以看到Wiki
,这里就是asynq
的官方文档,里面有更详细的使用demo
。
2.3 官方示例
源代码README.md
中的代码示例
/asynq/official/task/task.go
package tasks
import (
"context"
"encoding/json"
"fmt"
"github.com/hibiken/asynq"
"log"
"time"
)
// A list of task types.
const (
//TypeEmailDelivery :任务类型为邮件
TypeEmailDelivery = "email:deliver"
//TypeImageResize :任务类型为邮件
TypeImageResize = "image:resize"
)
// EmailDeliveryPayload 队列里的任务消息体
type EmailDeliveryPayload struct {
UserID int
TemplateID string
}
// ImageResizePayload 队列里的任务消息体
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.(编写一个函数 HandleXXXTask 来处理输入的任务)
// Note that it satisfies the asynq.HandlerFunc interface.(请注意它满足 asynq.HandlerFunc 接口。)
//
// Handler doesn't need to be a function. You can define a type
// that satisfies asynq.Handler interface. See examples below.(处理程序不一定需要是一个函数。你可以定义一个满足 asynq.Handler 接口的类型。请参考下面的示例。)
//---------------------------------------------------------------
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{}
}
/asynq/official/client/client.go
package main
import (
tasks "go-zero-micro/asynq/official/task"
"log"
"time"
"github.com/hibiken/asynq"
)
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)
}
/asynq/official/server/server.go
package main
import (
tasks "go-zero-micro/asynq/official/task"
"log"
"github.com/hibiken/asynq"
)
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)
}
}
3 asynq介绍
3.1 简单介绍
asynq 是一个基于 Go 的开源异步任务处理框架,它提供了一种简单的方式来处理异步任务,如任务队列、定时任务等。asynq 可以轻松地与现有的应用程序集成,并且非常易于使用。
3.2 常用API
NewServer(opts ...Option) *Server
:创建一个新的异步任务处理器。Server.Enqueue(task *Task) error
:将任务添加到任务队列中。Server.EnqueueAt(task *Task, t time.Time) error
:将任务添加到定时任务队列中,指定任务的执行时间。Server.EnqueueIn(task *Task, d time.Duration) error
:将任务添加到延迟任务队列中,指定任务的延迟时间。Server.ProcessTask(fn func(ctx context.Context, task *Task) error) error
:自定义任务处理器,可以编写自己的任务处理逻辑。Server.Start() error
:启动异步任务处理器。Server.Shutdown(ctx context.Context) error
:停止异步任务处理器。NewTask(typ string, payload Payload) *Task
:创建一个新的任务。Task.Meta()
:获取任务的元数据。Task.Payload() Payload
:获取任务的负载数据。Task.Type() string
:获取任务的类型。Task.ID() string
:获取任务的唯一标识符。
此外,asynq
还提供了许多其他的 API
,如任务重试、任务优先级、任务过期等。具体的 API
可以参考 asynq
的文档。
3.3 应用场景
- 异步任务处理:asynq 可以将异步任务添加到任务队列中,并在后台异步处理这些任务。这对于需要进行耗时操作的应用程序非常有用,如发送电子邮件、生成 PDF 文件、处理图像等。
- 定时任务处理:asynq 可以在指定的时间执行任务,这对于需要在特定时间执行操作的应用程序非常有用,如发送定期报告、执行备份操作等。
- 任务重试:asynq 可以自动重试失败的任务,这对于需要处理不稳定网络或外部系统的应用程序非常有用。
- 任务优先级管理:asynq 可以根据任务的优先级来处理任务,这对于需要处理紧急任务或高优先级任务的应用程序非常有用。
- 延迟任务处理:asynq 可以在指定的时间后执行任务,这对于需要处理延迟操作的应用程序非常有用,如处理付款、取消订单等。
※4 asynq具体讲解及示例
通过官方Wiki文档可以选择合适的解决方案。(官方文档
和源代码
为主要参考)
4.1 asynq入门
入门:有异步任务处理的代码示例。
4.2 server端自定义结构体类型实现处理任务
server端自定义结构体类型实现处理任务:有代码示例。
4.3 server端使用中间件
server端使用中间件:有代码示例。
4.4 任务的生命周期
任务的生命周期):纯讲解。
4.4 信号
信号:纯讲解,注意目前Windows
不支持TSTP信号。
4.5 队列优先级
队列优先级:有代码示例。
4.6 任务重试
任务重试:有代码示例。
4.7 任务超时和取消
任务超时和取消:有代码示例。
4.8 周期性任务
周期性任务:有代码示例。
4.9 周期性任务(动态)
周期性任务(动态):有代码示例。
4.10 速率限制
速率限制:有代码示例。
4.11 任务唯一性
任务唯一性:有代码示例。
4.12 任务保留和结果
任务保留和结果:有代码示例。
4.13 任务聚合
任务聚合:有代码示例。
4.14 Redis集群
Redis集群:有代码示例。
4.15 自动故障转移
自动故障转移:有代码示例。
4.16 监控和警报
监控和警报:纯讲解。
5 亲自实践
5.1 参考文档
通过官方文档可以选择合适的解决方案。(官方文档
和源代码
为主要参考)
这里仅展示 异步任务处理
、定时任务处理
、任务重试处理
、任务优先级管理
、延迟任务处理
这五个类型以及asynq
的Web UI
工具asynqmon
的使用。
5.2 异步任务处理
以异步发送邮件为例,这个示例其实与官方示例是大同小异,这里是拆分成单个示例了。
5.2.1 具体代码
/asynq/others_demo/async_task_demo/async_task_task/task.go
package async_task_task
import (
"encoding/json"
"fmt"
"github.com/hibiken/asynq"
"golang.org/x/net/context"
)
// 创建一个新任务类型
const AsyncEmailTask = "async_email_task"
// AsyncEmailPayload 定义发送邮件的负载数据结构
type AsyncEmailPayload struct {
To string `json:"to"`
Subject string `json:"subject"`
Body string `json:"body"`
}
// NewAsyncEmailTask 创建异步电子邮件任务的函数
func NewAsyncEmailTask(asyncEmail AsyncEmailPayload) (*asynq.Task, error) {
payload, err := json.Marshal(asyncEmail)
if err != nil {
return nil, err
}
task := asynq.NewTask(AsyncEmailTask, payload)
return task, err
}
// HandleAsyncEmailTask 处理异步电子邮件任务的函数
func HandleAsyncEmailTask(ctx context.Context, task *asynq.Task) error {
payload := AsyncEmailPayload{}
if err := json.Unmarshal(task.Payload(), &payload); err != nil {
return err
}
// TODO: 模拟发送邮件
fmt.Printf("\nAsync Server:Start handle AsyncTask!")
fmt.Printf("Sending email to %s, subject: %s, body: %s\n", payload.To, payload.Subject, payload.Body)
fmt.Println("Async Server:End handle AsyncTask!")
return nil
}
/asynq/others_demo/async_task_demo/async_task_client/client.go
package main
import (
"fmt"
"github.com/hibiken/asynq"
"go-zero-micro/asynq/others_demo/async_task_demo/async_task_task"
"go-zero-micro/common/utils"
"log"
"time"
)
const redisAddr = "127.0.0.1:6379"
func main() {
client := asynq.NewClient(asynq.RedisClientOpt{Addr: redisAddr})
defer client.Close()
//emailBody := fmt.Sprintf("异步任务邮件已发送,发送时间:%s", time.Now().Format(utils.DateTimeFormat))
//asyncEmail := async_task_task.AsyncEmailPayload{
// To: "user@example.com",
// Subject: "异步任务邮件",
// Body: emailBody,
//}
//task, err := async_task_task.NewAsyncEmailTask(asyncEmail)
//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)
for i := 0; i < 5; i++ {
emailBody := fmt.Sprintf("异步任务邮件已发送,发送时间:%s", time.Now().Format(utils.DateTimeFormat))
asyncEmail := async_task_task.AsyncEmailPayload{
To: "user@example.com",
Subject: "异步任务邮件",
Body: emailBody,
}
task, err := async_task_task.NewAsyncEmailTask(asyncEmail)
time.Sleep(time.Second)
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)
}
}
/asynq/others_demo/async_task_demo/async_task_task_server/server.go
package main
import (
"go-zero-micro/asynq/others_demo/async_task_demo/async_task_task"
"log"
"github.com/hibiken/asynq"
)
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(async_task_task.AsyncEmailTask, async_task_task.HandleAsyncEmailTask)
// ...register other handlers...
if err := srv.Run(mux); err != nil {
log.Fatalf("could not run server: %v", err)
}
}
启动演示:
先启动服务端,再启动客户端。
※5.2.2 自定义server端处理任务方法
asynq的服务端除了直接调用处理任务的方法外,也可以通过声明结构体来实现asynq.Handler
里的ProcessTask(context.Context, *Task) error
接口来处理任务,这样会更加灵活,但是要注意直接调用和通过实现接口只能二选一。
参考:server端自定义结构体类型实现处理任务:有代码示例。
具体代码(核心代码):
/asynq/others_demo/async_task_demo/async_task_task/task.go
// AsyncEmailProcessor implements asynq.Handler interface.
type AsyncEmailProcessor struct {
// ... fields for struct
}
func NewAsyncEmailProcessor() *AsyncEmailProcessor {
return &AsyncEmailProcessor{}
}
func (processor *AsyncEmailProcessor) ProcessTask(ctx context.Context, t *asynq.Task) error {
var payload AsyncEmailPayload
if err := json.Unmarshal(t.Payload(), &payload); err != nil {
//return fmt.Errorf("json.Unmarshal failed: %v: %w", err, asynq.SkipRetry)
return err
}
// TODO: 模拟发送邮件
fmt.Printf("\nAsync Server:ProcessTask:Start handle AsyncTask!\n")
fmt.Printf("Sending email to %s, subject: %s, body: %s\n", payload.To, payload.Subject, payload.Body)
fmt.Println("Async Server:ProcessTask:End handle AsyncTask!")
return nil
}
/asynq/others_demo/async_task_demo/async_task_task_server/server.go
//mux.HandleFunc(async_task_task.AsyncEmailTask, async_task_task.HandleAsyncEmailTask)
mux.Handle(async_task_task.AsyncEmailTask, async_task_task.NewAsyncEmailProcessor())
5.3 定时任务处理
5.3.1 使用定时器(Ticker)实现
定时任务示例的代码与异步任务比较类似,所以复用了异步任务的task.go
和server.go
的代码,这里只展示定时任务client.go
的代码。
package main
import (
"fmt"
"github.com/hibiken/asynq"
"go-zero-micro/asynq/others_demo/async_task_demo/async_task_task"
"go-zero-micro/common/utils"
"log"
"time"
)
const redisAddr = "127.0.0.1:6379"
func main() {
client := asynq.NewClient(asynq.RedisClientOpt{Addr: redisAddr})
defer client.Close()
StartUpScheduledTask(client, 10*time.Second) // 每隔 10 秒执行一次发送数据任务)
}
func StartUpScheduledTask(client *asynq.Client, interval time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for range ticker.C {
emailBody := fmt.Sprintf("定时任务邮件已发送,发送时间:%s", time.Now().Format(utils.DateTimeFormat))
scheduledEmail := async_task_task.AsyncEmailPayload{
To: "user@example.com",
Subject: "定时任务邮件",
Body: emailBody,
}
task, err := async_task_task.NewAsyncEmailTask(scheduledEmail)
//info, err := client.Enqueue(task)
//※ asynq.ProcessAt(time.Now().Add(interval))是让服务端延迟指定的时间执行
info, err := client.Enqueue(task, asynq.ProcessAt(time.Now().Add(interval)))
if err != nil {
log.Fatalf("could not enqueue task: %v", err)
}
log.Printf("enqueued task: id=%s queue=%s", info.ID, info.Queue)
}
}
启动步骤也是先服务端,后客户端。
注意:
- 客户端定时添加任务到队列中时,还加入参数
asynq.ProcessAt(time.Now().Add(interval))
是让服务端延迟指定的时间执行。 - 可以通过对比
服务端处理时间
和客户端添加时间
发现服务端在处理时确实延迟了指定时间间隔。
※5.3.2 使用cron实现
使用Golang
的周期性定时器(Ticker
)实现定时器不够灵活,比如设定具体的执行时间等,这时可以使用cron
来实现。
参考:周期性任务:有代码示例。
client.go
代码:
package main
import (
"fmt"
"github.com/hibiken/asynq"
"go-zero-micro/asynq/others_demo/async_task_demo/async_task_task"
"log"
"time"
)
const redisAddr = "127.0.0.1:6379"
func main() {
loc, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
panic(err)
}
scheduler := asynq.NewScheduler(
&asynq.RedisClientOpt{
Addr: redisAddr,
},
&asynq.SchedulerOpts{
Location: loc,
},
)
//emailBody := fmt.Sprintf("定时任务邮件已发送,发送时间:%s", time.Now().Format(utils.DateTimeFormat))
emailBody := fmt.Sprintf("定时任务邮件已发送!")
scheduledEmail := async_task_task.AsyncEmailPayload{
To: "user@example.com",
Subject: "定时任务邮件",
Body: emailBody,
}
task, err := async_task_task.NewAsyncEmailTask(scheduledEmail)
if err != nil {
log.Fatal(err)
}
//entryID1, err := scheduler.Register("* * * * *", task) //每分钟执行一次任务
//entryID1, err := scheduler.Register("*/1 * * * *", task) //每分钟执行一次任务
entryID1, err := scheduler.Register("*/2 * * * *", task) //每2分钟执行一次任务
//entryID1, err := scheduler.Register("@every 10s", task) //每隔10秒执行1次
//entryID1, err := scheduler.Register("@every 1m", task) //每隔1分钟执行1次
//entryID1, err := scheduler.Register("@every 1h", task) //每隔1小时执行1次
if err != nil {
log.Fatal(err)
}
log.Printf("registered an entry: %q\n", entryID1)
// 运行
if err := scheduler.Run(); err != nil {
log.Fatal(err)
}
}
5.4 任务重试处理
任务重试处理有两个地方可以配置,分别是在创建任务时,任务入队时。
参考:任务重试:有代码示例。
5.4.1 创建任务时设置
创建任务时设置重试次数、超时时间。
package main
import (
"encoding/json"
"fmt"
"github.com/hibiken/asynq"
"go-zero-micro/asynq/others_demo/async_task_demo/async_task_task"
"go-zero-micro/common/utils"
"log"
"time"
)
const redisAddr = "127.0.0.1:6379"
func main() {
emailBody := fmt.Sprintf("定时任务邮件已发送,发送时间:%s", time.Now().Format(utils.DateTimeFormat))
retryEmail := async_task_task.AsyncEmailPayload{
To: "user@example.com",
Subject: "定时任务邮件",
Body: emailBody,
}
task, err := NewRetryEmailTask(retryEmail)
if err != nil {
log.Fatal(err)
}
client := asynq.NewClient(asynq.RedisClientOpt{Addr: redisAddr})
info, err := client.Enqueue(task)
if err != nil {
log.Fatal(err)
}
log.Printf("enqueued task: id=%s queue=%s", info.ID, info.Queue)
}
// NewRetryEmailTask 创建重试电子邮件任务的函数
func NewRetryEmailTask(asyncEmail async_task_task.AsyncEmailPayload) (*asynq.Task, error) {
payload, err := json.Marshal(asyncEmail)
if err != nil {
return nil, err
}
//任务级别:创建任务时设置重试次数、超时时间
task := asynq.NewTask(async_task_task.AsyncEmailTask, payload, asynq.MaxRetry(5), asynq.Timeout(1*time.Minute))
return task, err
}
5.4.2 任务入队时设置
任务入队时设置重试次数、超时时间。
package main
import (
"encoding/json"
"fmt"
"github.com/hibiken/asynq"
"go-zero-micro/asynq/others_demo/async_task_demo/async_task_task"
"go-zero-micro/common/utils"
"log"
"time"
)
const redisAddr = "127.0.0.1:6379"
func main() {
emailBody := fmt.Sprintf("定时任务邮件已发送,发送时间:%s", time.Now().Format(utils.DateTimeFormat))
retryEmail := async_task_task.AsyncEmailPayload{
To: "user@example.com",
Subject: "定时任务邮件",
Body: emailBody,
}
task, err := NewRetryEmailTask(retryEmail)
if err != nil {
log.Fatal(err)
}
client := asynq.NewClient(asynq.RedisClientOpt{Addr: redisAddr})
info, err := client.Enqueue(task, asynq.MaxRetry(5), asynq.Timeout(1*time.Minute))
if err != nil {
log.Fatal(err)
}
log.Printf("enqueued task: id=%s queue=%s", info.ID, info.Queue)
}
// NewRetryEmailTask 创建重试电子邮件任务的函数
func NewRetryEmailTask(asyncEmail async_task_task.AsyncEmailPayload) (*asynq.Task, error) {
payload, err := json.Marshal(asyncEmail)
if err != nil {
return nil, err
}
//任务级别:创建任务时设置重试次数、超时时间
task := asynq.NewTask(async_task_task.AsyncEmailTask, payload, asynq.MaxRetry(5), asynq.Timeout(1*time.Minute))
return task, err
}
5.5 任务优先级管理
参考:队列优先级:有代码示例。
5.6 延迟任务处理
参考:入门。
主要是客户端在将任务入队时使用ProcessIn
或ProcessAt
选项来安排将来要处理的任务。
ProcessIn
:
info, err = client.Enqueue(task, asynq.ProcessIn(24*time.Hour))
ProcessAt
:
package main
import (
"fmt"
"github.com/hibiken/asynq"
"go-zero-micro/asynq/others_demo/async_task_demo/async_task_task"
"go-zero-micro/common/utils"
"log"
"time"
)
const redisAddr = "127.0.0.1:6379"
func main() {
client := asynq.NewClient(asynq.RedisClientOpt{Addr: redisAddr})
defer client.Close()
StartUpScheduledTask(client, 10*time.Second) // 每隔 10 秒执行一次发送数据任务)
}
func StartUpScheduledTask(client *asynq.Client, interval time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for range ticker.C {
emailBody := fmt.Sprintf("定时任务邮件已发送,发送时间:%s", time.Now().Format(utils.DateTimeFormat))
scheduledEmail := async_task_task.AsyncEmailPayload{
To: "user@example.com",
Subject: "定时任务邮件",
Body: emailBody,
}
task, err := async_task_task.NewAsyncEmailTask(scheduledEmail)
//info, err := client.Enqueue(task)
//※ asynq.ProcessAt(time.Now().Add(interval))是让服务端延迟指定的时间执行
info, err := client.Enqueue(task, asynq.ProcessAt(time.Now().Add(interval)))
if err != nil {
log.Fatalf("could not enqueue task: %v", err)
}
log.Printf("enqueued task: id=%s queue=%s", info.ID, info.Queue)
}
}
5.7 Web UI工具asynqmon
asynqmon
源代码:https://github.com/hibiken/asynqmon
6 go-zero中使用asynq
本次代码示例
参考文档:
在go-zero
使用asynq
前,如果对asynq
服务端、客户端的代码结构有更清楚的了解,在go-zero
集成asynq
时会更加轻松,很快能够明白各处的作用。
本次示例是将asynq服务端
和asynq客户端
放到同一个服务里,按照先服务端后客户端的顺序启动,也可以拆分成两个独立的服务,根据自己的实际需求改造即可。