背景

在业务发展过程中,会出现一些需要延时处理的场景,比如:

a.订单下单之后超过30分钟用户未支付,需要取消订单
b.订单一些评论,如果48h用户未对商家评论,系统会自动产生一条默认评论
c.点我达订单下单后,超过一定时间订单未派出,需要超时取消订单等。。。
处理这类需求,比较直接简单的方式就是定时任务轮训扫表。这种处理方式在数据量不大的场景下是完全没问题,但是当数据量大的时候高频的轮训数据库就会比较的耗资源,导致数据库的慢查或者查询超时。所以在处理这类需求时候,采用了延时队列来完成。


几种延时队列

延时队列就是一种带有延迟功能的消息队列。下面会介绍几种目前已有的延时队列:
1.Java中java.util.concurrent.DelayQueue
优点:JDK自身实现,使用方便,量小适用
缺点:队列消息处于jvm内存,不支持分布式运行和消息持久化
2.Rocketmq延时队列
优点:消息持久化,分布式
缺点:不支持任意时间精度,只支持特定level的延时消息
3.Rabbitmq延时队列(TTL+DLX实现)
优点:消息持久化,分布式
缺点:延时相同的消息必须扔在同一个队列

根据自身业务和公司情况,如果实现一个自己的延时队列服务需要考虑一下几点:

* 消息存储
* 过期延时消息实时获取
* 高可用性

基于Redis实现

1.0版本

功能特性

* 消息可靠性,消息持久化,消息至少被消费一次
* 实时性:存在一定的时间误差(定时任务间隔)
* 支持指定消息remove
* 高可用性

整体结构

- Messages Pool所有的延时消息存放,结构为KV结构,key为消息ID,value为一个具体的message(这里选择Redis Hash结构主要是因为hash结构能存储较大的数据量,数据较多时候会进行渐进式rehash扩容,并且对于HSET和HGET命令来说时间复杂度都是O(1))
- Delayed Queue是16个有序队列(队列支持水平扩展),结构为ZSET,value为messages pool中消息ID,score为过期时间(分为多个队列是为了提高扫描的速度)
- Timed Task定时任务,负责扫描处理每个队列过期消息

消息结构

每个延时消息必须包括以下参数:

* tags:消息过期之后发送mq的tags
* keys:消息过期之后发送mq的keys
* body:消息过期之后发送mq的body,提供给消费这做具体的消息处理
* delayTime:延时发送时间(默认,delayTime、expectDate有一个即可)
* expectDate:期望发送时间

流程


注:上图1、2、3或者2、3是一个事务操作
取出过期消息过程是通过一个外部定时任务每隔1min分钟去查询队列中过期的消息,然后发送mq && remove

2.0版本

1.0上有一个可改进的地方就是队列中过期的消息是通过定时任务触发查询。所有有了2.0
2.0版本在1.0上做了一个优化,废弃掉了1min定时任务触发过期消息发送,采用了java Lock await/singlal方式实现过期消息的实时发送低延时

多节点部署结构:

- pull job:这里分别为每一个队列创建了一个pull job thread,功能很简单,就是负责去队列中拉取过期的消息数据(这里保证一个队列有且只有一个pull job)
- worker:pull job拉取到的过期消息会交给一个worker thread去处理,这样的好处是处理过期的消息实时性更高(pull job不必等去除过期消息全部处理完成在继续去拉取新的过期数据)
- zookeeper coordinate:通过zk的操作来完成对队列的重新分配工作,daemon thread监听zk节点的创建和删除

主要流程:


服务启动会注册zk,获取分配处理的queues,启动后台线程监听zk 
为每个分配queue创建一个pull job 
pull job首先会去queue中查询是否有过期消息:
Y:将取出消息交给worker处理
N:查询queue中最后一个成员(zset结构默认按score递增排序),如果为空,则await;不为空则await(成员score-System.currentTimeMillis())

由于过期消息发送成功才会从队列中remove,所以pull job会记录上一次查询队列的一个offset,每次获取到过期消息会将offset向前偏移,过期消息交给worker处理,当worker由于某些异常原因处理失败会重置pull job中offset,这样可以避免消息发送一次失败之后没办法在继续处理(除了新节点add || remove时候)
当部署服务有新增,延时队列服务会重新计算得到当前处理队列,并将之前创建pull job cancel,为新处理队列重新创建pull job。删除同理。
</ol>

【转】基于Redis实现延时队列服务的更多相关文章

  1. 基于Redis实现延时队列服务

    背景 在业务发展过程中,会出现一些需要延时处理的场景,比如: a.订单下单之后超过30分钟用户未支付,需要取消订单 b.订单一些评论,如果48h用户未对商家评论,系统会自动产生一条默认评论 c.点我达 ...

  2. Redis作为消息队列服务场景应用案例

    NoSQL初探之人人都爱Redis:(3)使用Redis作为消息队列服务场景应用案例   一.消息队列场景简介 “消息”是在两台计算机间传送的数据单位.消息可以非常简单,例如只包含文本字符串:也可以更 ...

  3. Redis简单延时队列

    Redis实现简单延队列, 利用zset有序的数据结构, score设置为延时的时间戳. 实现思路: 1.使用命令 [zrangebyscore keyName socreMin socreMax] ...

  4. NoSQL初探之人人都爱Redis:(3)使用Redis作为消息队列服务场景应用案例

    一.消息队列场景简介 “消息”是在两台计算机间传送的数据单位.消息可以非常简单,例如只包含文本字符串:也可以更复杂,可能包含嵌入对象.消息被发送到队列中,“消息队列”是在消息的传输过程中保存消息的容器 ...

  5. 【转】NoSQL初探之人人都爱Redis:(3)使用Redis作为消息队列服务场景应用案例

    一.消息队列场景简介 “消息”是在两台计算机间传送的数据单位.消息可以非常简单,例如只包含文本字符串:也可以更复杂,可能包含嵌入对象.消息被发送到队列中,“消息队列”是在消息的传输过程中保存消息的容器 ...

  6. 使用Redis作为消息队列服务场景应用案例

    一.消息队列场景简介 "消息"是在两台计算机间传送的数据单位.消息可以非常简单,例如只包含文本字符串:也可以更复杂,可能包含嵌入对象.消息被发送到队列中,"消息队列&qu ...

  7. 基于Redis的消息队列php-resque

    转载:http://netstu.5iunix.net/archives/201305-835/ 最近的做一个短信群发的项目,需要用到消息队列.因此开始了我对消息队列选型的漫长路. 为什么选型会纠结呢 ...

  8. 基于rabbitMQ 消息延时队列方案 模拟电商超时未支付订单处理场景

    前言 传统处理超时订单 采取定时任务轮训数据库订单,并且批量处理.其弊端也是显而易见的:对服务器.数据库性会有很大的要求,并且当处理大量订单起来会很力不从心,而且实时性也不是特别好 当然传统的手法还可 ...

  9. 基于Redis的开源分布式服务Codis

    Redis在豌豆荚的使用历程--单实例==>多实例,业务代码中做sharding==>单个Twemproxy==>多个Twemproxy==>Codis,豌豆荚自己开发的分布式 ...

随机推荐

  1. 留用 未验证 js适配根字体大小

    方法一:<script>                (function (doc, win) {                var docEl = doc.documentElem ...

  2. jvm的happens-before原则

    提到并发,通常首先想到是锁,其实对共享资源的互斥操作是一方面,在Java中还有一方面是内存的可见性和顺序化,了解JMM的同学可能会更清楚些,内存可见性和顺序性同样非常重要,在这里简单提一下JMM模型, ...

  3. Core Data 入门

    1. 基本概念 Core Data是一种被称为对象关系映射(Object-Relational Mapping,ORM)技术的实现. Core Data 架构图如下: 五个概念: (1)数据模型(Da ...

  4. C#中打开设计视图时报"未将对象引用设置到对象的实例"

    通常情况下,若是你将用户控件写好了放入窗体中,若是有不合理的代码,则会弹出错误提示框,不让你放.若是你之前只是随便加了一个用户控件,并且没有什么问题,但后来你又把控件改坏掉了,那么你打开就会报错(在窗 ...

  5. 使用OpenSsl自己CA根证书,二级根证书和颁发证书(亲测步骤)

    ---恢复内容开始--- 一.介绍 企业自用, 到证书机构签发证书的费用和时间等都可以省下..... SSl证书的背景功用.......(省略万字,不废话) 可以参考: SSL证书_百度百科 X509 ...

  6. Python_11-正则表达式

    目录: 1.1      引言 1.2      python 正则式概述及常用字符 1.2.1       元字符 1.2.2       用 "" 开始的特殊字符所表示的预定义 ...

  7. 10个android开发必备的开源项目

    You are here: Home » » Blog » 10 Open Source Android Apps which every Android developer must look in ...

  8. Python 序列与映射的解包操作-乾颐堂

    解包就是把序列或映射中每个元素单独提取出来,序列解包的一种简单用法就是把首个或前几个元素与后面几个元素分别提取出来,例如: first, seconde, *rest = sequence 如果seq ...

  9. Django框架 之 信号

    Django中提供了“信号调度”,用于在框架执行操作时解耦.通俗来讲,就是一些动作发生的时候,信号允许特定的发送者去提醒一些接受者. Django内置信号 Model signals pre_init ...

  10. DESC和 ACS

    用 DESC 表示按倒序排序(即:从大到小排序)用 ACS 表示按正序排序(即:从小到大排序)