实现延迟队列的几种方案

背景

实际项目当中,经常会使用延迟队列,执行一些延迟任务,比如:

如果第一次通知失败,就延迟 X 分钟再通知一次,这在构建webhook的项目中很常见。
如果用户成功消费,X 天后没有评论,就默认给予好评。
这些任务具有的共同点可以抽象为:在某项任务执行完毕后,延迟一定时间执行另外一项任务。

我们需要什么

  • 延迟队列:一种带有 延迟功能 的消息队列

  • 延时队列模型,就是将未来执行的事件提前存储好,然后不断扫描这个存储,触发执行时间则执行对应的任务逻辑

可以选择的技术方案

定时扫表

服务启动时,开启一个异步协程 → 定时扫描 msg table,到了事件触发事件,调用对应的 handler

  • 优点:简单容易实现,而且容易理解
  • 缺点:
    • 每一个需要定时 / 延时任务的服务,都需要一个 msg table 做额外存储 → 存储与业务耦合,
    • 定时扫描 → 时间不好控制,可能会错过触发时间。
    • 对 msg table instance 是一个负担。反复有一个服务不断对数据库产生持续不断的压力
    • 会面临每次需要遍历庞大任务数据,并且执行时间判断造成的误差会导致某些任务也许无法执行的窘境

Kafka

  • 针对不同的延迟时间发布到不同的 topic 中,比如 topic_1s, topic_2s.
  • 这种设计在延时时间比较固定的场景下问题不太大,但如果是延时时间变化比较大会导致 topic 数目过多,会把磁盘从顺序读写会变成随机读写从导致性能衰减,同时也会带来其他类似重启或者恢复时间过长的问题

Redis

  • 延迟队列主要使用了 Redis 的数据结构:Sorted Set
  • Sorted Set 是一个有序的 Set,Set 内元素的排序基于其加入集合时指定的 Score。通过 ZRANGEBYSCORE ,可以得到基于 Score 在指定区间内的元素(排序)。
  • 基于 Sorted Set 的延时队列模型如下:
    • SortSet 的 key 作为业务维度的属性(队列)名字,比如一种命名方式为 <业务: 命名空间: 队列名>
    • SortSet 中的元素做为任务消息,Score 视为本任务延迟的时间(戳)

时间轮

  • 时间轮,简单理解就是一个时钟表盘,指针每隔一段时间前进一格,走玩一圈是一个周期。而需要执行的任务就放置在表盘的刻度处,当指针走到该位置时,就执行相应的任务。具体图片如下所示

在这里插入图片描述

  • 时间轮是一个环形队列,底层实现就是一个固定长度的数组,数组中的每个元素存储一个双向列表,选择双向列表的原因是在O(1)时间复杂度实现插入和删除操作。而这个双向链表存储的就是要在该位置执行的任务。

举例分析,假设时间轮盘每隔1s前进一格,那么上图中的时间轮盘的周期就是12s,如果此时时间在刻度为0的位置,此时需要添加一个定时任务,需要10s后执行,那么该任务就需要放到刻度10处。当指针到达刻度10时,执行在该位置上,双向链表存储的所有任务。

参考

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CoLiuRs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值