2023-08-07 17:58:24 +08:00
|
|
|
// Package queue
|
|
|
|
// @Link https://github.com/bufanyun/hotgo
|
|
|
|
// @Copyright Copyright (c) 2023 HotGo CLI
|
|
|
|
// @Author Ms <133814250@qq.com>
|
|
|
|
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
2022-02-25 17:11:17 +08:00
|
|
|
package queue
|
|
|
|
|
|
|
|
import (
|
2023-05-10 23:54:50 +08:00
|
|
|
"context"
|
2022-02-25 17:11:17 +08:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2023-05-10 23:54:50 +08:00
|
|
|
"github.com/gogf/gf/v2/database/gredis"
|
2022-02-25 17:11:17 +08:00
|
|
|
"github.com/gogf/gf/v2/errors/gerror"
|
2022-11-24 23:37:34 +08:00
|
|
|
"github.com/gogf/gf/v2/frame/g"
|
|
|
|
"hotgo/utility/encrypt"
|
2022-02-25 17:11:17 +08:00
|
|
|
"math/rand"
|
2023-05-10 23:54:50 +08:00
|
|
|
"strconv"
|
2022-02-25 17:11:17 +08:00
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
type RedisMq struct {
|
|
|
|
poolName string
|
|
|
|
groupName string
|
2023-05-10 23:54:50 +08:00
|
|
|
timeout int64
|
2022-02-25 17:11:17 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
type RedisOption struct {
|
2023-05-10 23:54:50 +08:00
|
|
|
Timeout int64
|
2022-02-25 17:11:17 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// SendMsg 按字符串类型生产数据
|
|
|
|
func (r *RedisMq) SendMsg(topic string, body string) (mqMsg MqMsg, err error) {
|
|
|
|
return r.SendByteMsg(topic, []byte(body))
|
|
|
|
}
|
|
|
|
|
|
|
|
// SendByteMsg 生产数据
|
|
|
|
func (r *RedisMq) SendByteMsg(topic string, body []byte) (mqMsg MqMsg, err error) {
|
|
|
|
if r.poolName == "" {
|
|
|
|
return mqMsg, gerror.New("RedisMq producer not register")
|
|
|
|
}
|
2023-05-30 12:09:40 +08:00
|
|
|
|
2022-02-25 17:11:17 +08:00
|
|
|
if topic == "" {
|
|
|
|
return mqMsg, gerror.New("RedisMq topic is empty")
|
|
|
|
}
|
|
|
|
|
2023-05-10 23:54:50 +08:00
|
|
|
mqMsg = MqMsg{
|
|
|
|
RunType: SendMsg,
|
|
|
|
Topic: topic,
|
|
|
|
MsgId: getRandMsgId(),
|
|
|
|
Body: body,
|
|
|
|
Timestamp: time.Now(),
|
|
|
|
}
|
2022-02-25 17:11:17 +08:00
|
|
|
|
2023-05-10 23:54:50 +08:00
|
|
|
data, err := json.Marshal(mqMsg)
|
2022-02-25 17:11:17 +08:00
|
|
|
if err != nil {
|
2023-05-10 23:54:50 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
key := r.genKey(r.groupName, topic)
|
|
|
|
if _, err = g.Redis().Do(ctx, "LPUSH", key, data); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.timeout > 0 {
|
|
|
|
if _, err = g.Redis().Do(ctx, "EXPIRE", key, r.timeout); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *RedisMq) SendDelayMsg(topic string, body string, delaySecond int64) (mqMsg MqMsg, err error) {
|
|
|
|
if delaySecond < 1 {
|
|
|
|
return r.SendMsg(topic, body)
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.poolName == "" {
|
|
|
|
err = gerror.New("SendDelayMsg RedisMq not register")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if topic == "" {
|
|
|
|
err = gerror.New("SendDelayMsg RedisMq topic is empty")
|
|
|
|
return
|
2022-02-25 17:11:17 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
mqMsg = MqMsg{
|
2022-11-24 23:37:34 +08:00
|
|
|
RunType: SendMsg,
|
|
|
|
Topic: topic,
|
2023-05-10 23:54:50 +08:00
|
|
|
MsgId: getRandMsgId(),
|
|
|
|
Body: []byte(body),
|
2022-11-24 23:37:34 +08:00
|
|
|
Timestamp: time.Now(),
|
2022-02-25 17:11:17 +08:00
|
|
|
}
|
2023-05-10 23:54:50 +08:00
|
|
|
|
|
|
|
data, err := json.Marshal(mqMsg)
|
2022-02-25 17:11:17 +08:00
|
|
|
if err != nil {
|
2023-05-10 23:54:50 +08:00
|
|
|
return
|
2022-02-25 17:11:17 +08:00
|
|
|
}
|
|
|
|
|
2023-05-10 23:54:50 +08:00
|
|
|
var (
|
|
|
|
conn = g.Redis()
|
|
|
|
key = r.genKey(r.groupName, "delay:"+topic)
|
|
|
|
expireSecond = time.Now().Unix() + delaySecond
|
|
|
|
timePiece = fmt.Sprintf("%s:%d", key, expireSecond)
|
|
|
|
z = gredis.ZAddMember{Score: float64(expireSecond), Member: timePiece}
|
|
|
|
)
|
2022-02-25 17:11:17 +08:00
|
|
|
|
2023-05-10 23:54:50 +08:00
|
|
|
if _, err = conn.ZAdd(ctx, key, &gredis.ZAddOption{}, z); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err = conn.RPush(ctx, timePiece, data); err != nil {
|
|
|
|
return
|
2022-02-25 17:11:17 +08:00
|
|
|
}
|
|
|
|
|
2023-05-10 23:54:50 +08:00
|
|
|
// consumer will also delete the item
|
2022-02-25 17:11:17 +08:00
|
|
|
if r.timeout > 0 {
|
2023-05-10 23:54:50 +08:00
|
|
|
_, _ = conn.Expire(ctx, timePiece, r.timeout+delaySecond)
|
|
|
|
_, _ = conn.Expire(ctx, key, r.timeout)
|
2022-02-25 17:11:17 +08:00
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// ListenReceiveMsgDo 消费数据
|
|
|
|
func (r *RedisMq) ListenReceiveMsgDo(topic string, receiveDo func(mqMsg MqMsg)) (err error) {
|
|
|
|
if r.poolName == "" {
|
|
|
|
return gerror.New("RedisMq producer not register")
|
|
|
|
}
|
|
|
|
if topic == "" {
|
|
|
|
return gerror.New("RedisMq topic is empty")
|
|
|
|
}
|
|
|
|
|
2023-05-10 23:54:50 +08:00
|
|
|
var (
|
|
|
|
key = r.genKey(r.groupName, topic)
|
|
|
|
key2 = r.genKey(r.groupName, "delay:"+topic)
|
|
|
|
)
|
|
|
|
|
2022-02-25 17:11:17 +08:00
|
|
|
go func() {
|
2023-05-10 23:54:50 +08:00
|
|
|
for range time.Tick(300 * time.Millisecond) {
|
|
|
|
mqMsgList := r.loopReadQueue(key)
|
2022-02-25 17:11:17 +08:00
|
|
|
for _, mqMsg := range mqMsgList {
|
|
|
|
receiveDo(mqMsg)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
2023-05-10 23:54:50 +08:00
|
|
|
|
|
|
|
go func() {
|
|
|
|
mqMsgCh, errCh := r.loopReadDelayQueue(key2)
|
|
|
|
for mqMsg := range mqMsgCh {
|
|
|
|
receiveDo(mqMsg)
|
|
|
|
}
|
|
|
|
for err = range errCh {
|
|
|
|
if err != nil && err != context.Canceled && err != context.DeadlineExceeded {
|
2023-06-05 20:14:57 +08:00
|
|
|
Logger().Infof(ctx, "ListenReceiveMsgDo Delay topic:%v, err:%+v", topic, err)
|
2023-05-10 23:54:50 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2022-11-24 23:37:34 +08:00
|
|
|
select {}
|
2022-02-25 17:11:17 +08:00
|
|
|
}
|
|
|
|
|
2023-05-10 23:54:50 +08:00
|
|
|
// 生成队列key
|
|
|
|
func (r *RedisMq) genKey(groupName string, topic string) string {
|
2023-01-25 11:49:21 +08:00
|
|
|
return fmt.Sprintf("queue:%s_%s", groupName, topic)
|
2022-02-25 17:11:17 +08:00
|
|
|
}
|
|
|
|
|
2023-05-10 23:54:50 +08:00
|
|
|
func (r *RedisMq) loopReadQueue(key string) (mqMsgList []MqMsg) {
|
|
|
|
conn := g.Redis()
|
2022-02-25 17:11:17 +08:00
|
|
|
for {
|
2023-05-10 23:54:50 +08:00
|
|
|
data, err := conn.Do(ctx, "RPOP", key)
|
2022-11-24 23:37:34 +08:00
|
|
|
if err != nil {
|
2023-06-05 20:14:57 +08:00
|
|
|
Logger().Warningf(ctx, "loopReadQueue redis RPOP err:%+v", err)
|
2022-02-25 17:11:17 +08:00
|
|
|
break
|
|
|
|
}
|
2022-11-24 23:37:34 +08:00
|
|
|
|
2023-05-10 23:54:50 +08:00
|
|
|
if data.IsEmpty() {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2022-02-25 17:11:17 +08:00
|
|
|
var mqMsg MqMsg
|
2023-05-10 23:54:50 +08:00
|
|
|
if err = data.Scan(&mqMsg); err != nil {
|
2023-06-05 20:14:57 +08:00
|
|
|
Logger().Warningf(ctx, "loopReadQueue Scan err:%+v", err)
|
2022-11-24 23:37:34 +08:00
|
|
|
break
|
|
|
|
}
|
2023-05-10 23:54:50 +08:00
|
|
|
|
2022-02-25 17:11:17 +08:00
|
|
|
if mqMsg.MsgId != "" {
|
|
|
|
mqMsgList = append(mqMsgList, mqMsg)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return mqMsgList
|
|
|
|
}
|
|
|
|
|
2023-05-10 23:54:50 +08:00
|
|
|
func RegisterRedisMqProducer(connOpt RedisOption, groupName string) (client MqProducer) {
|
|
|
|
return RegisterRedisMq(connOpt, groupName)
|
2022-02-25 17:11:17 +08:00
|
|
|
}
|
|
|
|
|
2023-02-08 20:29:34 +08:00
|
|
|
// RegisterRedisMqConsumer 注册消费者
|
2023-05-10 23:54:50 +08:00
|
|
|
func RegisterRedisMqConsumer(connOpt RedisOption, groupName string) (client MqConsumer) {
|
|
|
|
return RegisterRedisMq(connOpt, groupName)
|
2022-02-25 17:11:17 +08:00
|
|
|
}
|
|
|
|
|
2022-11-24 23:37:34 +08:00
|
|
|
// RegisterRedisMq 注册redis实例
|
2023-05-10 23:54:50 +08:00
|
|
|
func RegisterRedisMq(connOpt RedisOption, groupName string) *RedisMq {
|
|
|
|
return &RedisMq{
|
|
|
|
poolName: encrypt.Md5ToString(fmt.Sprintf("%s-%d", groupName, time.Now().UnixNano())),
|
2022-02-25 17:11:17 +08:00
|
|
|
groupName: groupName,
|
|
|
|
timeout: connOpt.Timeout,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-10 23:54:50 +08:00
|
|
|
func getRandMsgId() string {
|
|
|
|
rand.NewSource(time.Now().UnixNano())
|
|
|
|
radium := rand.Intn(999) + 1
|
|
|
|
timeCode := time.Now().UnixNano()
|
|
|
|
return fmt.Sprintf("%d%.4d", timeCode, radium)
|
2022-02-25 17:11:17 +08:00
|
|
|
}
|
|
|
|
|
2023-05-10 23:54:50 +08:00
|
|
|
func (r *RedisMq) loopReadDelayQueue(key string) (resCh chan MqMsg, errCh chan error) {
|
2023-05-30 12:09:40 +08:00
|
|
|
resCh = make(chan MqMsg)
|
2023-05-10 23:54:50 +08:00
|
|
|
errCh = make(chan error, 1)
|
2022-02-25 17:11:17 +08:00
|
|
|
|
2023-05-10 23:54:50 +08:00
|
|
|
go func() {
|
|
|
|
defer close(resCh)
|
|
|
|
defer close(errCh)
|
|
|
|
|
|
|
|
conn := g.Redis()
|
|
|
|
for {
|
|
|
|
now := time.Now().Unix()
|
|
|
|
do, err := conn.Do(ctx, "zrangebyscore", key, "0", strconv.FormatInt(now, 10), "limit", 0, 1)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2023-02-08 20:29:34 +08:00
|
|
|
|
2023-05-10 23:54:50 +08:00
|
|
|
val := do.Strings()
|
|
|
|
if len(val) == 0 {
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
errCh <- ctx.Err()
|
|
|
|
return
|
|
|
|
case <-time.After(time.Second):
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, listK := range val {
|
|
|
|
for {
|
|
|
|
pop, err := conn.LPop(ctx, listK)
|
|
|
|
if err != nil {
|
|
|
|
errCh <- err
|
|
|
|
return
|
|
|
|
} else if pop.IsEmpty() {
|
2023-05-30 12:09:40 +08:00
|
|
|
_, _ = conn.ZRem(ctx, key, listK)
|
|
|
|
_, _ = conn.Del(ctx, listK)
|
2023-05-10 23:54:50 +08:00
|
|
|
break
|
|
|
|
} else {
|
|
|
|
var mqMsg MqMsg
|
|
|
|
if err = pop.Scan(&mqMsg); err != nil {
|
|
|
|
g.Log().Warningf(ctx, "loopReadDelayQueue Scan err:%+v", err)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
if mqMsg.MsgId == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
select {
|
|
|
|
case resCh <- mqMsg:
|
|
|
|
case <-ctx.Done():
|
|
|
|
errCh <- ctx.Err()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-02-08 20:29:34 +08:00
|
|
|
}
|
2023-05-10 23:54:50 +08:00
|
|
|
}()
|
|
|
|
return resCh, errCh
|
2022-02-25 17:11:17 +08:00
|
|
|
}
|