前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >redis 队列_Redis之延迟队列的实现

redis 队列_Redis之延迟队列的实现

作者头像
Java架构师必看
发布2021-08-23 10:36:47
5800
发布2021-08-23 10:36:47
举报
文章被收录于专栏:Java架构师必看

延迟队列,顾名思义它是一种带有延迟功能的消息队列。那么,是在什么场景下我才需要这样的队列呢?

1. 背景

我们先看看以下业务场景:

        当订单一直处于未支付状态时,如何及时的关闭订单

        如何定期检查处于退款状态的订单是否已经退款成功

        在订单长时间没有收到下游系统的状态通知的时候,如何实现阶梯式的同步订单状态的策略

        在系统通知上游系统支付成功终态时,上游系统返回通知失败,如何进行异步通知实行分频率发送:15s 3m 10m 30m 30m 1h 2h 6h 15h

1.1 解决方案

        最简单的方式,定时扫表。例如对于订单支付失效要求比较高的,每2S扫表一次检查过期的订单进行主动关单操作。优点是简单,缺点是每分钟全局扫表,浪费资源,如果遇到表数据订单量即将过期的订单量很大,会造成关单延迟。

        使用RabbitMq或者其他MQ改造实现延迟队列,优点是,开源,现成的稳定的实现方案,缺点是:MQ是一个消息中间件,如果团队技术栈本来就有MQ,那还好,如果不是,那为了延迟队列而去部署一套MQ成本有点大

  • 使用Redis的zset、list的特性,我们可以利用redis来实现一个延迟队列RedisDelayQueue

2. 设计目标

实时性:允许存在一定时间的秒级误差

高可用性:支持单机、支持集群

支持消息删除:业务会随时删除指定消息

消息可靠性:保证至少被消费一次

消息持久化:基于Redis自身的持久化特性,如果Redis数据丢失,意味着延迟消息的丢失,不过可以做主备和集群保证。这个可以考虑后续优化将消息持久化到MangoDB中。

3. 设计方案

    设计主要包含以下几点

    将整个Redis当做消息池,以KV形式存储消息

    使用ZSET做优先队列,按照Score维持优先级

    使用LIST结构,以先进先出的方式消费

    ZSET和LIST存储消息地址(对应消息池的每个KEY)

    自定义路由对象,存储ZSET和LIST名称,以点对点的方式将消息从ZSET路由到正确的LIST

使用定时器维护路由

根据TTL规则实现消息延迟

3.1 设计图

4af9633fe2fa7e9101974bc57f945018.png
4af9633fe2fa7e9101974bc57f945018.png

3.2 数据结构

ZING:DELAY_QUEUE:JOB_POOL 是一个Hash_Table结构,里面存储了所有延迟队列的信息。KV结构:K=prefix+projectName field = topic+jobId V=CONENT;V由客户端传入的数据,消费的时候回传

ZING:DELAY_QUEUE:BUCKET 延迟队列的有序集合ZSET,存放K=ID和需要的执行时间戳,根据时间戳排序

ZING:DELAY_QUEUE:QUEUE LIST结构,每个Topic一个LIST,list存放的都是当前需要被消费的JOB

4853fc39c9d4cf4bd4518d792257c524.png
4853fc39c9d4cf4bd4518d792257c524.png

3.3 任务的生命周期

   1. 新增一个JOB,会在ZING:DELAY_QUEUE:JOB_POOL中插入一条数据,记录了业务方消费方。ZING:DELAY_QUEUE:BUCKET也会插入一条记录,记录执行的时间戳

2. 搬运线程会去ZING:DELAY_QUEUE:BUCKET中查找哪些执行时间戳的RunTimeMillis比现在的时间小,将这些记录全部删除;同时会解析出每个任务的Topic是什么,然后将这些任务PUSH到TOPIC对应的列表ZING:DELAY_QUEUE:QUEUE

3. 每个TOPIC的LIST都会有一个监听线程去批量获取LIST中的待消费数据,获取到的数据全部扔给这个TOPIC的消费线程池

4. 消费线程池执行会去ZING:DELAY_QUEUE:JOB_POOL查找数据结构,返回给回调结构,执行回调方法。

3.4 设计要点

3.4.1 基本概念

JOB:需要异步处理的任务,是延迟队列里的基本单元

Topic:一组相同类型Job的集合(队列)。供消费者来订阅

3.4.2 消息结构

每个JOB必须包含以下几个属

        jobId:Job的唯一标识。用来检索和删除指定的Job信息

        topic:Job类型。可以理解成具体的业务名称

        delay:Job需要延迟的时间。单位:秒。(服务端会将其转换为绝对时间)

        body:Job的内容,供消费者做具体的业务处理,以json格式存储

        retry:失败重试次数

        url:通知URL

3.5 设计细节

3.5.1 如何快速消费ZING:DELAY_QUEUE:QUEUE

       最简单的实现方式就是使用定时器进行秒级扫描,为了保证消息执行的时效性,可以设置每1S请求Redis一次,判断队列中是否有待消费的JOB。但是这样会存在一个问题,如果queue中一直没有可消费的JOB,那频繁的扫描就失去了意义,也浪费了资源,幸好LIST中有一个BLPOP阻塞原语,如果list中有数据就会立马返回,如果没有数据就会一直阻塞在那里,直到有数据返回,可以设置阻塞的超时时间,超时会返回NULL;具体的实现方式及策略会在代码中进行具体的实现介绍

3.5.2 避免定时导致的消息重复搬运及消费

        使用Redis的分布式锁来控制消息的搬运,从而避免消息被重复搬运导致的问题

        使用分布式锁来保证定时器的执行频率

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档