文章目录
前言
一直想找一个比较好用的轻量级的好用的异步任务队列的服务或工具,最好是用golang开发的,不用说太多,太喜欢用 golang开发的一些服务,例如gocron用在多们的生产环境一直很不错,很稳定,有时间了出一个gocron的安装和使用孝程分亨给大家,今天我们还是来看一下如何一步一步的安装Asynq和会使用Asynq
一、Asynq是什么?
Asynq是一个go语言实现的分布式任务队列和异步处理库,基于redis,类似sidekiq和celery,他具有以下特点:
- 保证至少执行一次任务
- 持久化
- 失败重试
- worker崩溃自动恢复
- 优先队列
- 暂停队列
- 支持中间件
- 允许唯一任务
- 支持Redis Cluster实现自动分片
- 支持Redis Sentinels实现高可用
- 提供web ui管理
- 提供cli管理
二、安装步骤
1.安装golang开发和编译环境
Asynq是用go语言开发的,需要golang的开发和编译环境,如果服务器或本地没有golang开发或编译环境,请先看一下我原来的文章:
centos7.9快速安装golang运行和开发环境图文教程
2、安装REDIS服务环境
Asynq使用Redis作为消息代理,Redis Server 版本最好4.0版本以上,具体的安装步骤大家可以参考我原来的文章:
CENTOS7.9源码编译安装REDIS7.0图文教程
3、安装asynqmon服务
虽然Asynq提供了webui 和 命令行工具asynq,用起来很方便,但还是没有asynqmon更直接,最好还是事先安装一下asynqmon,asynqmon是Asynq分布式任务队列实时监视器和Web管理工具
Asynqmon是asynq的一个web UI工具,用于监控和管理Asynq队列和任务。它支持与Prometheus集成来显示时序数据。
Asynqmon既是一个可以包含在web应用程序中的库,也是一个可以简单安装和运行的二进制文件
现在我们看如何安装它
Asynqmon的github的库地址是:
https://github.com/hibiken/asynqmon.git
cd /data/go/src/ #进入go的工作目录
git clone https://github.com/hibiken/asynqmon.git #拉取项目代码
go mod tidy #拉取asynqmon的项目的go依赖包
make build #打包aqynqmon
如果打包成功/data/go/src/asynqmon目录会生成一个二进制可执行文件
可能在编译过程中提示需要nodejs的支持和yarn的支持,如果没有node环境请先安装node环境,然后
yarn npm install -g
然后再打包编译asynqmon, 编译成功后我们直接运行:
/data/go/src/asynqmon/asynqmon -port 8809 -redis-addr 172.16.0.38:6379 -redis-db 9
-port 8809 你的asynqmon服务的端口
-redis-addr 你的redis server的服务ip和端口 resdis server ip最好用内网ip
-redis-db 你的asynq服务使用的redis db库编号 0-15
把asynqmon加入supervisor去管理,启动和监控
vi /etc/supervisord.d/asynqmon.ini
增加内容:
[program:asynqmon]
directory=/data/go/src/asynqmon/
command=/data/go/src/asynqmon/asynqmon -port 8809 -redis-addr 172.16.0.38:6379 -redis-db 9
numprocs=1
redirect_stderr=true
autostart=true
autorestart=true
user=root
stdout_logfile=/data/logs/go/asynqmon.log
:wq保存后,启supervisor控制台,update 一下后启运asynqmon服务
最后在用浏览器打开asynqmon可以看到效果:
4、新建go项目,实现asynq的produce和consume服务
make /data/go/src/asynq
make /data/go/src/asynq/producer
make /data/go/src/asynq/consumer
make /data/go/src/asynq/consumer/task
go mod init asynq
vim /data/go/src/asynq/producer/producer.go
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/go-redis/redis/v8"
"github.com/hibiken/asynq"
"log"
"time"
)
type Tqueue struct {
// typename indicates the type of task to be performed.
T string `json:"t"`
// payload holds data needed to perform the task.
P string `json:"p"`
// add to task times
C int `json:"c"`
}
var ctx context.Context
var rdb *redis.Client
var asynqClient *asynq.Client
func main() {
log.Printf("start producer main")
ctx = context.Background()
rdb = redis.NewClient(&redis.Options{
Addr: "172.16.0.38:6379",
Password: "", // no password set
DB: 8, // use default DB
})
ra := asynq.RedisClientOpt{Addr: "172.16.0.38:6379", DB:9}
asynqClient = asynq.NewClient(ra)
defer func() {
rdb.Close()
asynqClient.Close()
}()
for {
result, err := rdb.BLPop(ctx,10*time.Second,"wdmqueen").Result()
if err != nil {
log.Printf(" ... ")
continue
}
log.Printf("get result 0 : %s", result[0])
log.Printf("get result 1 : %s ", result[1])
go AddNewTask(result[1])
}
}
func AddNewTask(data string) error {
log.Printf("start add new task")
var t Tqueue
if err := json.Unmarshal([]byte(data), &t); err != nil {
log.Printf("Unmarshal err %s,%v",data,err)
return fmt.Errorf("json.Unmarshal failed: %s: %v", data,err)
}
t1 := asynq.NewTask(t.T, []byte(t.P))
info, err := asynqClient.Enqueue(t1)
if err != nil {
log.Fatal(err)
if t.C <10 {
t.C +=1
str, _ := json.Marshal(t)
rdb.RPush(ctx,"wdmqueen", string(str))
}
}
log.Printf("enqueued task: id=%s queue=%s", info.ID, info.Queue)
return nil
}
vi /data/go/src/asynq/consumer/consumer.go
package main
import (
"github.com/hibiken/asynq"
"log"
"wdmasynq/consumer/task"
)
func main() {
rd := asynq.RedisClientOpt{Addr: "172.16.0.38:6379", DB:9}
srv := asynq.NewServer(rd, asynq.Config{
Concurrency: 10,
})
r := asynq.NewServeMux()
r .HandleFunc("email:welcome", task.SendWelcomeEmail)
r .HandleFunc("email:reminder", task.SendReminderEmail)
if err := srv.Run(r); err != nil {
log.Fatal(err)
}
}
vi /data/go/src/asynq/consumer/task/sendmail.go
package task
import (
"context"
"encoding/json"
"fmt"
"github.com/hibiken/asynq"
"log"
)
type EmailDeliveryPayload struct {
UserID int
TemplateID string
}
func SendWelcomeEmail(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
}
func SendReminderEmail(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 Reminder Email to User: user_id=%d, template_id=%s", p.UserID, p.TemplateID)
return nil
}
然后拉取一下用到的依赖包
go mod tidy
然后启动两个终端分别运行:
go run consumer/consumer.go
go run producer/producer.go
就可以看到效果了,我这里的producer是从redis的另一个db取的,已用于实际的生产环境,由于实际的业务是用php写的,php想把任务加入asynq的队列中,中间中转了一下,整体的服务架构图:
最后把我在asynq的讨论区的问题截个图,供大家参考一下:
提供了一种php使用asynq的方法
总结
asynq轻量式高性能分布式任务队列库,开发自谷歌员工,必是精品。
如有问题,欢迎大家留言沟通,点赞支持!!