feat: 重新设计 delay consumer producer

This commit is contained in:
2026-04-13 17:27:56 +08:00
parent ad44a84718
commit c14e5f84dc
6 changed files with 475 additions and 164 deletions

129
delay/redis_delay.go Normal file
View File

@@ -0,0 +1,129 @@
// Package delay ...
//
// Description : 基于redis实现的延迟事件队列
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2025-08-29 09:11
package delay
import (
"context"
"time"
"git.zhangdeman.cn/zhangdeman/queue/abstract"
"git.zhangdeman.cn/zhangdeman/queue/define"
redisPkg "git.zhangdeman.cn/zhangdeman/redis"
redisPkgDefine "git.zhangdeman.cn/zhangdeman/redis/define"
"git.zhangdeman.cn/zhangdeman/serialize"
"github.com/tidwall/gjson"
)
// NewRedisDelayEvent 获取延迟队列实例
// 参数说明:
// - redisFlag: 使用那个redis实例
// - queueName: 延迟队列名称
// - pullTimeInterval: 延迟队列扫描的时间间隔, 单位秒
// - distributeProducer: 消息生产者示例, 扫描到的数据通过此实例向二级任务队列分发
func NewRedisDelayEvent(redisFlag string, queueName string, pullTimeInterval int64, distributeProducer abstract.IProducer) abstract.IDelayQueue {
if queueName == "" || redisFlag == "" {
panic("init redis delay event: queue name or redis flag is empty")
}
if nil == distributeProducer {
panic("distributeProducer is nil")
}
// 验证redis实例是否存在
if _, err := redisPkg.Client.GetRealClientWithError(redisFlag); nil != err {
panic(err.Error())
}
if pullTimeInterval <= 0 {
pullTimeInterval = 60
}
return &redisDelayEvent{
stopChan: make(chan bool, 1),
redisFlag: redisFlag,
delayQueueName: queueName,
pullTimeInterval: pullTimeInterval,
distributeProducer: distributeProducer,
}
}
type redisDelayEvent struct {
stopChan chan bool // 停止请求的chan
redisFlag string // 使用的redis实例
delayQueueName string // 延迟队列名称
pullTimeInterval int64 // 延迟队列多久拉取一次数据
distributeProducer abstract.IProducer // 事件分发的生产者
}
// Send 生成一条延时事件
// 参数说明:
// - delayTime: 延时时长, 单位: s
// - data: 要发送的数据
func (rde redisDelayEvent) Send(ctx context.Context, delayTime int64, data *define.EventData) *redisPkgDefine.RedisResult {
if delayTime <= 0 {
// 默认延迟 1min
delayTime = 60
}
if delayTime >= time.Now().Unix() {
// 指定具体的延迟时间, 重置延时时间为相对时间
delayTime = delayTime - time.Now().Unix()
}
return redisPkg.Wrapper.ZAdd(ctx, rde.redisFlag, rde.delayQueueName, time.Now().Unix()+delayTime, serialize.JSON.MarshalForStringIgnoreError(data))
}
// Distribute 到期事件事件分发
func (rde redisDelayEvent) Distribute(ctx context.Context) {
for {
hasFinish := false
select {
case <-ctx.Done():
// 收到上下文已完成事件, 退出
hasFinish = true
case <-rde.stopChan:
// 程序调用stop方法, 退出
hasFinish = true
default:
now := time.Now()
// 计时器, 固定时长扫描一次
// ZRangeAndRemByScore 此方法内部已经自动包装了删除逻辑
eventDataListRes := redisPkg.Wrapper.ZRangeAndRemByScore(ctx, rde.redisFlag, rde.delayQueueName, 0, now.Unix())
// ZRange 返回数据时二维数组 [["数据部分", "设置的数据score"]]
resList := gjson.Parse(eventDataListRes.Result).Array()
if len(resList) > 0 {
for _, itemData := range resList {
var (
resFormat define.EventData
err error
)
if err = serialize.JSON.UnmarshalWithNumberForString(itemData.Get("0").String(), &resFormat); nil != err {
// 数据解析失败, 触发失败回调
rde.distributeProducer.GetProducerHandler().FailureCallback(ctx, &define.EventData{
Type: "UNMARSHAL_FAILURE",
Data: itemData.Get("0").String(),
}, &define.EventProduceResult{
Queue: "",
Success: false,
Err: err,
Cost: 0,
})
continue
} else {
// 生产数据, 向耳机队列分发任务
rde.distributeProducer.Sync(ctx, &resFormat)
}
}
}
// 间隔指定时间拉取一次
time.Sleep(time.Second * time.Duration(rde.pullTimeInterval))
}
if hasFinish {
break
}
}
}
func (rde redisDelayEvent) Stop() {
rde.stopChan <- true
}