Open dduo518 opened 1 year ago
延时队列是一种常见的数据结构,用于处理需要延迟执行的任务或事件。它常见的使用场景包括:
分布式延时队列是一种在分布式系统中处理延时任务的解决方案。它可以用于在多个节点上进行任务调度和执行,并确保任务按预期的延时时间执行。
实现分布式延时队列的一种常见方式是使用分布式消息队列。以下是一个基本的分布式延时队列的实现思路:
分布式延时队列的实现中,还需要考虑一些其他因素,如数据一致性、分片处理、负载均衡等。这些因素取决于具体的系统需求和设计。
开源的分布式消息队列系统(如Apache Kafka、RabbitMQ、RocketMQ、Plusar等)提供了一些原生支持或扩展来实现分布式延时队列的功能。此外,也可以使用其他分布式技术(如分布式锁、分布式调度器等)结合消息队列来构建自定义的分布式延时队列解决方案。
Apache Kafka、RabbitMQ、RocketMQ处理延时消息都并不是投递到要发送的topic中,都是先投递到一个delay topic,通过一个中间的topic 进行实现。
Kafka 跟 RocketMQ 的实现方式大同小异,都是通过另外一个任务进程进行轮询消费检查。
Kafka并不支持延迟队列的功能,需要我们手动去实现,一般来说Kafka不支持任意时间精度的延迟消息,只支持固定级别的延迟,创建一个topic,该topic创建18个partiition,每个partition对应不同的延迟级别。
key
consumer
ConsumerGroup
pause
seek
ConsumerRecord
offset
TopicPartition
resume
header
RabbitMQ本身并不存在延迟队列的概念,但是可以通过DLX死信交换机和TTL消息过期来实现延迟队列。
消息被存储在BookKeeper中。在消息发布到代理服务器后,DelayedDeliveryTracker在内存中维护时间索引(time -> messageId)。一旦指定的延迟时间结束,该消息将被传递给消费者。
内存单点延时队列是一种在单个节点上使用内存存储来处理延时任务的解决方案。它适用于小规模或单节点环境下的延时任务处理。
以下是内存单点延时队列的基本实现思路:
内存单点延时队列适用于简单的延时任务场景,如定时任务调度、简单的消息提醒等。由于是单点模式,系统的可靠性和可扩展性受限,不适用于高负载或分布式环境。对于大规模、高并发的延时任务需求,分布式延时队列是更合适的选择。
延时队列的实现方式有多种,常见的有以下几种:
延迟队列旨在存储具有特定延迟的项目,并在延迟过去后处理它们。 它可用于调度将来需要执行的任务或事件。
两个模块组成
queue.go 文件定义了管理延迟队列的数据结构和实现时间堆排序的方法
scheduler.go 文件提供了处理队列中项目的调度程序逻辑。
queueItem
type queueItem struct { order *domain.TradeOrder delay int64 index int }
堆元素由订单Order、延迟时间delay和位置索引组成
delayQueue
type delayQueue []*queueItem
自定义一个底层为数组的大小堆数据结构
delayQueue 定义了以下方法实现 container/heap 官方包的大小堆接口
container/heap
Len()
Less(i,j int) bool
Swap(i,j int)
Push(x any)
Pop() any
scheduler
type scheduler struct { dq *delayQueue callback orderCallBack mu sync.Mutex }
定义 scheduler 结构体,表示处理延时队列中项目的调度器。该结构体包含了延时队列和一个回调函数,用于处理延时结束回调。
Push
方法用于将具有指定延迟的订单添加到延时队列中。该方法计算订单应该被处理的绝对时间,并将其插入到延时队列中。
func (t *scheduler) Push(order *domain.TradeOrder, delay int64) { t.mu.Lock() heap.Push(t.dq, &queueItem{ delay: time.Now().UnixMilli() + delay, order: order, }) t.mu.Unlock() }
RunScheduler
调度器的主循环,它在一个独立的 goroutine 中运行。该方法使用定时器触发,并在每次触发时尝试处理延时队列中。
func (t *scheduler) RunScheduler(ctx context.Context) { var tick = time.NewTicker(time.Millisecond) defer tick.Stop() for range tick.C { select { case <-ctx.Done(): return default: } t.pop(ctx) } }
pop
用于从延时队列中取出最早的项目,并检查其是否已经达到延迟时间。如果还未达到延迟时间,将重新将项目放回延时队列,并延迟处理。否则,将在新的 goroutine 中调用回调函数处理。
func (t *scheduler) pop(ctx context.Context) { if t.dq.Len() == 0 { return } t.mu.Lock() item := heap.Pop(t.dq).(*queueItem) t.mu.Unlock() delay := item.delay - time.Now().UnixMilli() if delay > 0 { t.Push(item.order, delay) return } go t.callback(ctx, item.order) }
延时队列
使用场景
延时队列是一种常见的数据结构,用于处理需要延迟执行的任务或事件。它常见的使用场景包括:
分类
分布式延时队列
分布式延时队列是一种在分布式系统中处理延时任务的解决方案。它可以用于在多个节点上进行任务调度和执行,并确保任务按预期的延时时间执行。
实现分布式延时队列的一种常见方式是使用分布式消息队列。以下是一个基本的分布式延时队列的实现思路:
分布式延时队列的实现中,还需要考虑一些其他因素,如数据一致性、分片处理、负载均衡等。这些因素取决于具体的系统需求和设计。
开源的分布式消息队列系统(如Apache Kafka、RabbitMQ、RocketMQ、Plusar等)提供了一些原生支持或扩展来实现分布式延时队列的功能。此外,也可以使用其他分布式技术(如分布式锁、分布式调度器等)结合消息队列来构建自定义的分布式延时队列解决方案。
Apache Kafka、RabbitMQ、RocketMQ处理延时消息都并不是投递到要发送的topic中,都是先投递到一个delay topic,通过一个中间的topic 进行实现。
Apache Kafka & RocketMQ
Kafka 跟 RocketMQ 的实现方式大同小异,都是通过另外一个任务进程进行轮询消费检查。
Kafka并不支持延迟队列的功能,需要我们手动去实现,一般来说Kafka不支持任意时间精度的延迟消息,只支持固定级别的延迟,创建一个topic,该topic创建18个partiition,每个partition对应不同的延迟级别。
原理
key
为延迟时间,同时把原 topic 保存到 header 中consumer
单独设置一个ConsumerGroup
去消费延迟 topic 消息,消费到消息之后如果没有达到延迟时间那么就进行pause
,然后seek
到当前ConsumerRecord
的offset
位置,同时使用定时器去轮询延迟的TopicPartition
,达到延迟时间之后进行resume
header
中的真实 topic ,直接转发RabbitMQ
RabbitMQ本身并不存在延迟队列的概念,但是可以通过DLX死信交换机和TTL消息过期来实现延迟队列。
原理
Plusar
消息被存储在BookKeeper中。在消息发布到代理服务器后,DelayedDeliveryTracker在内存中维护时间索引(time -> messageId)。一旦指定的延迟时间结束,该消息将被传递给消费者。
内存单点延时队列
内存单点延时队列是一种在单个节点上使用内存存储来处理延时任务的解决方案。它适用于小规模或单节点环境下的延时任务处理。
以下是内存单点延时队列的基本实现思路:
内存单点延时队列适用于简单的延时任务场景,如定时任务调度、简单的消息提醒等。由于是单点模式,系统的可靠性和可扩展性受限,不适用于高负载或分布式环境。对于大规模、高并发的延时任务需求,分布式延时队列是更合适的选择。
实现方式
延时队列的实现方式有多种,常见的有以下几种:
基于时间堆的实现
延迟队列旨在存储具有特定延迟的项目,并在延迟过去后处理它们。 它可用于调度将来需要执行的任务或事件。
两个模块组成
queue.go 文件定义了管理延迟队列的数据结构和实现时间堆排序的方法
scheduler.go 文件提供了处理队列中项目的调度程序逻辑。
queueItem
Struct堆元素由订单Order、延迟时间delay和位置索引组成
delayQueue
Type自定义一个底层为数组的大小堆数据结构
delayQueue
MethodsdelayQueue 定义了以下方法实现
container/heap
官方包的大小堆接口Len()
: 返回队列长度Less(i,j int) bool
: 比较俩个订单延时大小Swap(i,j int)
: 交换俩个元素位置Push(x any)
: 新增元素Pop() any
: 返回堆顶元素scheduler
Struct定义
scheduler
结构体,表示处理延时队列中项目的调度器。该结构体包含了延时队列和一个回调函数,用于处理延时结束回调。Push
Method方法用于将具有指定延迟的订单添加到延时队列中。该方法计算订单应该被处理的绝对时间,并将其插入到延时队列中。
RunScheduler
Method调度器的主循环,它在一个独立的 goroutine 中运行。该方法使用定时器触发,并在每次触发时尝试处理延时队列中。
pop
Method用于从延时队列中取出最早的项目,并检查其是否已经达到延迟时间。如果还未达到延迟时间,将重新将项目放回延时队列,并延迟处理。否则,将在新的 goroutine 中调用回调函数处理。