asynq | go-zero学习 第五章 集成asynq

asynq | go-zero学习 第五章 集成asynq

  • 1 前提
  • 2 源代码
    • 2.1 官方介绍
    • ※2.2 官方Wiki文档(强烈推荐阅读)
    • 2.3 官方示例
  • 3 asynq介绍
    • 3.1 简单介绍
    • 3.2 常用API
    • 3.3 应用场景
  • ※4 asynq具体讲解及示例
    • 4.1 asynq入门
    • 4.2 server端自定义结构体类型实现处理任务
    • 4.3 server端使用中间件
    • 4.4 任务的生命周期
    • 4.4 信号
    • 4.5 队列优先级
    • 4.6 任务重试
    • 4.7 任务超时和取消
    • 4.8 周期性任务
    • 4.9 周期性任务(动态)
    • 4.10 速率限制
    • 4.11 任务唯一性
    • 4.12 任务保留和结果
    • 4.13 任务聚合
    • 4.14 Redis集群
    • 4.15 自动故障转移
    • 4.16 监控和警报
  • 5 亲自实践
    • 5.1 参考文档
    • 5.2 异步任务处理
      • 5.2.1 具体代码
      • ※5.2.2 自定义server端处理任务方法
    • 5.3 定时任务处理
      • 5.3.1 使用定时器(Ticker)实现
      • ※5.3.2 使用cron实现
    • 5.4 任务重试处理
      • 5.4.1 创建任务时设置
      • 5.4.2 任务入队时设置
    • 5.5 任务优先级管理
    • 5.6 延迟任务处理
    • 5.7 Web UI工具asynqmon
  • 6 go-zero中使用asynq

1 前提

asynq是基于redis实现的,且需要高版本的redis,本文使用的是Redis-x64-5.0.14.1

redis下载地址:

  1. linux/mac系统:https://redis.io/download
  2. windows系统:https://github.com/tporadowski/redis/releases

2 源代码

2.1 官方介绍

  1. Asynq是Go语言中简单、可靠、高效的分布式任务队列。
  2. Asynq 是一个 Go 库,用于对任务进行排队并与工作线程异步处理它们。它由Redis支持,旨在可扩展且易于入门。
  3. Asynq支持高可用性和水平扩展。

Asynq 工作原理的高级概述:

  • 客户端将任务放入队列
  • 服务器从队列中取出任务并为每个任务启动一个工作协程
  • 任务由多个worker同时处理

Asynq的特性:

  1. 保证至少执行一次任务
  2. 支持任务调度
  3. 失败任务的重试
  4. 在工作进程崩溃时自动恢复任务
  5. 权重优先级队列
  6. 严格的优先级队列
  7. 由于 Redis 中的写入速度很快,因此添加任务的延迟较低
  8. 使用唯一选项对任务进行去重
  9. 允许为每个任务设置超时和截止时间
  10. 允许聚合任务组来批处理多个连续操作
  11. 灵活的处理程序接口,支持中间件
  12. 能够暂停队列以停止正在处理队列中的任务
  13. 定时任务
  14. 支持Redis Cluster进行自动分片和高可用性
  15. 支持Redis Sentinels实现高可用性
  16. 与Prometheus集成,以收集和可视化队列指标
  17. Web界面,用于检查和远程控制队列和任务(有Web页面)
  18. CLI命令行界面,用于检查和远程控制队列和任务(有CLI页面)

※2.2 官方Wiki文档(强烈推荐阅读)

githubasynq代码仓库里,可以看到Wiki,这里就是asynq的官方文档,里面有更详细的使用demo

Wiki链接地址
asynq | go-zero学习 第五章 集成asynq_第1张图片

2.3 官方示例

本次示例代码

源代码README.md中的代码示例

  1. /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{}
}
  1. /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)
}
  1. /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

  1. NewServer(opts ...Option) *Server:创建一个新的异步任务处理器。
  2. Server.Enqueue(task *Task) error:将任务添加到任务队列中。
  3. Server.EnqueueAt(task *Task, t time.Time) error:将任务添加到定时任务队列中,指定任务的执行时间。
  4. Server.EnqueueIn(task *Task, d time.Duration) error:将任务添加到延迟任务队列中,指定任务的延迟时间。
  5. Server.ProcessTask(fn func(ctx context.Context, task *Task) error) error:自定义任务处理器,可以编写自己的任务处理逻辑。
  6. Server.Start() error:启动异步任务处理器。
  7. Server.Shutdown(ctx context.Context) error:停止异步任务处理器。
  8. NewTask(typ string, payload Payload) *Task:创建一个新的任务。
  9. Task.Meta():获取任务的元数据。
  10. Task.Payload() Payload:获取任务的负载数据。
  11. Task.Type() string:获取任务的类型。
  12. Task.ID() string:获取任务的唯一标识符。

此外,asynq 还提供了许多其他的 API,如任务重试、任务优先级、任务过期等。具体的 API 可以参考 asynq 的文档。

3.3 应用场景

  1. 异步任务处理:asynq 可以将异步任务添加到任务队列中,并在后台异步处理这些任务。这对于需要进行耗时操作的应用程序非常有用,如发送电子邮件、生成 PDF 文件、处理图像等。
  2. 定时任务处理:asynq 可以在指定的时间执行任务,这对于需要在特定时间执行操作的应用程序非常有用,如发送定期报告、执行备份操作等。
  3. 任务重试:asynq 可以自动重试失败的任务,这对于需要处理不稳定网络或外部系统的应用程序非常有用。
  4. 任务优先级管理:asynq 可以根据任务的优先级来处理任务,这对于需要处理紧急任务或高优先级任务的应用程序非常有用。
  5. 延迟任务处理:asynq 可以在指定的时间后执行任务,这对于需要处理延迟操作的应用程序非常有用,如处理付款、取消订单等。

※4 asynq具体讲解及示例

  1. 官方Wiki文档
  2. 源代码

通过官方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 参考文档

  1. 官方文档
  2. 源代码

通过官方文档可以选择合适的解决方案。(官方文档源代码为主要参考)

这里仅展示 异步任务处理定时任务处理任务重试处理任务优先级管理延迟任务处理 这五个类型以及asynqWeb UI工具asynqmon的使用。

5.2 异步任务处理

本次示例代码

以异步发送邮件为例,这个示例其实与官方示例是大同小异,这里是拆分成单个示例了。

5.2.1 具体代码

  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
}
  1. /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:      "[email protected]",
	//	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:      "[email protected]",
			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)
	}
}
  1. /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端自定义结构体类型实现处理任务:有代码示例。
具体代码(核心代码):

  1. /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
}
  1. /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.goserver.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:      "[email protected]",
			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:      "[email protected]",
		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:      "[email protected]",
		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:      "[email protected]",
		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 延迟任务处理

参考:入门。

主要是客户端在将任务入队时使用ProcessInProcessAt选项来安排将来要处理的任务。

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:      "[email protected]",
			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

本次代码示例
参考文档:

  1. go-zero-looklook中定时任务
  2. go-zero asynq 定时任务最佳实践

go-zero使用asynq前,如果对asynq服务端、客户端的代码结构有更清楚的了解,在go-zero集成asynq时会更加轻松,很快能够明白各处的作用。

本次示例是将asynq服务端asynq客户端放到同一个服务里,按照先服务端后客户端的顺序启动,也可以拆分成两个独立的服务,根据自己的实际需求改造即可。

你可能感兴趣的:(go-zero,golang)