一、非任意时间
1、修改
在服务器端(rocketmq-broker端)的属性配置文件中加入以下行:
messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
缺点:需要重启rocketmq的服务端
二、任意时间
1、借鉴原生的逻辑
先建立多个时间范围的level,依靠一个定时任务搬运,到一天以内的时候建立时间轮的方式建立时分秒三个表来查着着几个区间的数据,上一个级别的查到才会注册下一个级别的定时任务,执行完成后取消注册,时间轮有HashedWheelTimer,需要考虑持久化问题
2、时间轮加rocketmq
1.rocketMQ默认支持18个等级 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
2.支持任意延时,设计逻辑为拆分法,用默认的rocketMQ去支持 任意时间都能够通过上面的时间组装出来
3.在1分中内需要发送的采用时间轮方式,发送出去
4.延时会走 guava-topic,不会走业务需要的topic,只有当真正需要发送的时候才会走业务topic
5.考虑1分钟内由于强制重启等所带来的数据丢失问题. (TODO)
代码
https://github.com/civism/civism-rocket
优点
不需要依赖除rocketMQ以外的任何中间间,可以算是0侵入
支持任意时间纬度的延时
实现简单,浅显易懂,安全与否取决于rocketMQ
rocketMQ所有的优点
缺点
极端情况下会有一分钟的数据丢失(服务重启并且满足刚好进入时间轮)
增大了rocketMQ的自带的延时压力
rocketMQ所有的缺点
3、存储介质加时间轮:
生产延迟消息:延迟消息由两部分组成–该笔消息的订单号key+业务数据value;
存储消息:当把延迟消息组装好之后,把该消息(key,value)放入redis中并设置一定的超时时间同时存入时间轮数据结构中;
取出消息:当该消息在时间轮数据结构中到期时,取出key,然后根据这个key去redis中取value;
通过RocketMQ的生产者线程,把消息发送出去,若发送成功,则把redis中该key删除;若是发送失败,则记录日志,人工补偿;
每部分的作用是:
HashedWheelTimer:存储消息的key,key到期时,自动弹出—起到一个定时器的作用;
Redis:将完整的延迟消息存储到内存中时,还把数据持久化到硬盘,当redis重启时,基本不丢数据;
RocketMQ:发送延迟消息;
这里有几个问题需要注意:
当系统突然宕机,服务器重启后,时间轮HashedWheelTimer中的key都将消失,并且很难恢复,此时丢失的key对应在Redis中的value只能等待时间到期,这种情况怎么办,即数据丢失问题?也可以不使用Redis存储完整的消息,把完整的消息直接放入时间轮数据结构中或放入延迟队列DelayQueue中;用这种方式也会存在数据丢失的问题:即系统突然宕机,服务器重启后,未到期的数据都将丢失,因为对数据没有进行持久化;
当key从HashedWheelTimer中取出后,根据该key在Redis中没取到数据,这种情况该怎么办,即数据不一致的问题?
当消息到期后,用RocketMQ发送时,发送好几次都失败了,这时候除了记录日志,人工进行补偿之外,还有什么好的解决方案?–解决办法之一是:把这些发送失败的消息,存入数据库表中;然后启动一个定时任务,定时把发送失败的消息,通过RocketMQ再次发送出去,若发送成功,将该消息从数据库中删除;若这次还是发送失败,则下次定时任务执行时,再继续尝试发送。
这里的HashedWheelTimer可以用Delayqueue代替,它两相比较而言,HashedWheelTimer的时间复杂度比Delayqueue要好些。
参考借鉴:
1、https://github.com/civism/civism-rocket
2、https://blog.csdn.net/zhaoming19870124/article/details/94152008