Asynq包

今天晚上,突然想代码实现一下asynq的小demo,问了问GPT关于Asynq的使用,结合以前看到的文章。

Asynq是Go的一个包,可用作消息队列,延时任务。市面上实现消息队列,延时队列的技术有很多,rabbitmq,kafka等,但asynq底层使用的是redis作为存储,相比于使用rabbitmq,kafka,可以少维护一个中间件,所以我们此处来学习如何使用asynq。

废话不多说,我们来使用!!!

----------------------------------------------------------------------------------------------------------------------------

asynq的几个概念首先要明白:

任务:Task,客户端会发送任务到队列,任务包含任务类型和负载数据payload。

队列:Task会被发送到队列,队列收到Task会通知任务处理器,任务处理器根据任务的类型选择对应的任务处理函数处理任务。

任务处理器:任务处理函数的集合,需要将任务类型和对应的任务处理函数注册到任务处理器。

任务处理函数:不同的任务类型对应不同的任务处理函数,任务处理函数需要自行实现。

客户端:客户端创建Task,发送Task给队列。

此图形象。

-------------------------------------------------------------------------------------------------------------------------

一:任务处理器/ 任务处理函数/ 队列

 1.首先创建一个任务处理器,即asynq.NewServer()

参数Queues:此处指定了三个队列,三个队列的名称和优先级如图所示,优先级高的队列中的任务会优先被任务处理器处理。不指定该参数,默认只会创建一个defult队列。

参数Concurrency:最大同时执行任务数。

 2.然后我们将 任务类型 和 任务处理函数 注册到了任务处理器,此处我们定义了两种任务类型,分别为my_task1和my_task2。然后启动任务处理器。

3.两个任务处理函数,这里的逻辑只是做了简单的输出。 

************以上为任务处理器的实现,如果有多个任务类型和对应的任务处理函数**************

*********************************同样可以注册到任务处理函数*********************************

------------------------------------------------------------------------------------------------------------------------------

二:任务

 

 4.此处我们创建了两个任务,NewTask时需要指定任务的类型(我们上文创建了两种任务类型),负载数据。任务创建好后将器推送入队,推送入队时可以指定延迟时间,指定延迟时间后,任务会被立刻推入队列,但会延迟执行。可以指定任务要推入哪个队列(上述创建了三种优先级不同的队列),任务进入不同的优先级队列,某种情况下,任务执行的优先级就会不同。

 上述为入队函数的实现,参数time为可选参数,有值时为延时任务,没有传值时为普通任务。

-----------------------------------------------------------------------------------------------------------------------

到此,延时任务的实现就基本完成了,下面是完整代码:

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/hibiken/asynq"
	"time"
)

// 任务类型 Tasktype
var (
	TaskType1 = "发送短信通知"
	TaskType2 = "问好"
)

// 任务处理函数
func myTask1Handler(ctx context.Context, t *asynq.Task) error {
	fmt.Println("I am TaskHandler1,receive data: ", string(t.Payload()))
	return nil
}

// 可以针对不同的Task编写不同的TaskHandler
// 不同的任务类型对应着不同的任务处理函数
func myTask2Handler(ctx context.Context, t *asynq.Task) error {
	fmt.Println("I am TaskHandler2,receive data:", string(t.Payload()))
	return nil
}

func main() {
	// 创建 asynq 任务处理器
	srv := asynq.NewServer(
		asynq.RedisClientOpt{
			Addr: "127.0.0.1:6379",
		},
		asynq.Config{
			Concurrency: 10, // 同时处理任务的最大数量
			Queues: map[string]int{ // 任务处理器会优先处理具有更高权重的队列中的任务
				"critical": 6, // 设置了3个队列,分别指定了不同的优先级
				"default":  3,
				"low":      1,
			},
		})

	// 注册任务处理函数到任务处理器   参数:任务类型   对应的处理函数
	mux := asynq.NewServeMux()
	mux.HandleFunc(TaskType1, myTask1Handler)
	mux.HandleFunc(TaskType2, myTask2Handler)

	// 启动一个groutine来启动任务处理器,因为Run()方法会阻塞,
	// 若不使用grountone,会阻塞main函数后续程序运行
	go func() {
		// 启动任务处理器
		err := srv.Run(mux)
		if err != nil {
			fmt.Println("启动任务处理器错误")
			return
		}
	}()

	// 创建一个任务Task   参数: 任务类型     payload负载数据
	// my_task1任务将被推送到critical队列,通过指定任务的队列,来间接指定任务的优先级
	my_payload1, _ := json.Marshal("要给发送信息通知的人的id为001")
	my_task1 := asynq.NewTask(TaskType1, my_payload1, asynq.Queue("critical"))

	my_payload2, _ := json.Marshal("向用户002用户问好")
	my_task2 := asynq.NewTask(TaskType2, my_payload2, asynq.Queue("low"))

	// 将任务推送入队列,设置延时时间
	err := Enq(my_task1, 2*time.Minute)
	if err != nil {
		return
	}
	err = Enq(my_task2, 1*time.Minute)
	if err != nil {
		return
	}

	defer srv.Shutdown()
	// 阻塞main函数,避免立马执行完结束
	select {}
}

// Enq 推送消息入队 参数一:Task 参数二:可选参数,延迟时间
func Enq(t *asynq.Task, time ...time.Duration) error {

	// 创建一个推送消息入队的客户端
	client := asynq.NewClient(asynq.RedisClientOpt{
		Addr: "127.0.0.1:6379",
	})
	defer client.Close()

	if len(time) > 0 {
		// 延时任务
		taskInfo, err := client.Enqueue(t, asynq.ProcessIn(time[0]))
		if err != nil {
			fmt.Println("推送消息入队失败")
			return err
		}
		fmt.Println("推送消息入队成功,入队消息的唯一ID为:", taskInfo.ID)
		return nil
	} else {
		// 非延时任务   将消息推送入队
		taskInfo, err := client.Enqueue(t)
		if err != nil {
			fmt.Println("推送消息入队失败")
			return err
		}
		// 程序一启动,会将消息马上入队,不管消息是否延时
		fmt.Println("推送消息入队成功,入队消息的唯一ID为:", taskInfo.ID)
		return nil
	}
}

-----------------------------------------------------------------------------------------------------------------------------

运行结果:

程序一旦启动,即刻的结果:

启动一分钟的结果:

启动两分钟的结果:

 

正常情况下,延时消息只会执行一次。

 ----------------------------------------------------------------------------------------------------------------------------

 

 为什么会出现延时消息执行两次的情况?

这是因为在第一次启动程序后,我们立刻终止了程序,此时任务被发送到队列,但任务处理器还没来得及处理任务,我们就将程序断开。上文也说了,asynq是基于redis存储的,任务被发送到队列,但没有被处理,任务会挤压在redis。当我们第二次启动程序时,队列再次收到新来的任务后,会将任务转发给任务处理器,任务处理器收到请求,处理任务,任务包含新来的任务和第一次挤压在队列中的任务,所以任务会执行两次。

此处也反映出,并不是任务处理器一直监听队列是否有消息,而是消息队列收到消息会通知任务处理器处理。否则的化,一旦第二次启动程序,挤压的任务会立刻执行,而不是等到新任务到底队列才去执行。

----------------------------------------------------------------------------------------------------------------------------

其他:

参考链接:Asynq简单、可靠、高效的分布式任务队列 - 掘金 (juejin.cn)

谢谢~ 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值