rocketmq实现延迟队列(精确到秒级)
最近项目里需要在延时队列,但是开源版本rocketmq不支持任意时间延时,造成有些任务无法执行
参考了网上的不少文章,但是都么有实现,所以再开源的基础上改造了个支持任意时间延时的队列。
源码地址: https://gitee.com/venus-suite/rocketmq-with-delivery-time
概念介绍
开源版本中,只有RocketMQ支持延迟消息,且只支持18个特定级别的延迟
付费版本中,阿里云和腾讯云上的MQ产品都支持精度为秒级别的延迟消息
定时消息:Producer将消息发送到消息队列RocketMQ版服务端,但并不期望立马投递这条消息,而是推迟到在当前时间点之后的某一个时间投递到Consumer进行消费,该消息即定时消息。
延时消息:Producer将消息发送到消息队列RocketMQ版服务端,但并不期望立马投递这条消息,而是延迟一定时间后才投递到Consumer进行消费,该消息即延时消息。
定时消息与延时消息在代码配置上存在一些差异,但是最终达到的效果相同:消息在发送到消息队列RocketMQ版服务端后并不会立马投递,而是根据消息中的属性延迟固定时间后才投递给消费者。
实现原理(3种实现方案)
1.代理实现 [链接](https://gitee.com/venus-suite/rocketmq-with-delivery-time/wikis/1.%E4%BB%A3%E7%90%86%E5%AE%9E%E7%8E%B0)
2.时间轮和delay-commit-log实现 [链接](https://gitee.com/venus-suite/rocketmq-with-delivery-time/wikis/2.%E6%97%B6%E9%97%B4%E8%BD%AE%E5%92%8Cdelay-file%E5%AE%9E%E7%8E%B0)
3.时间轮和时间file实现 [链接](https://gitee.com/venus-suite/rocketmq-with-delivery-time/wikis/3.%E6%97%B6%E9%97%B4%E8%BD%AE%E5%92%8C%E7%A7%92%E7%BA%A7%E6%96%87%E4%BB%B6%E5%AE%9E%E7%8E%B0)
## 适用场景
定时消息和延时消息适用于以下一些场景:
消息生产和消费有时间窗口要求,例如在电商交易中超时未支付关闭订单的场景,在订单创建时会发送一条延时消息。
这条消息将会在30分钟以后投递给消费者,消费者收到此消息后需要判断对应的订单是否已完成支付。
如支付未完成,则关闭订单。如已完成支付则忽略。
通过消息触发一些定时任务,例如在某一固定时间点向用户发送提醒消息。
使用方式
定时消息和延时消息的使用在代码编写上存在略微的区别:
发送定时消息需要明确指定消息发送时间点之后的某一时间点作为消息投递的时间点。
发送延时消息时需要设定一个延时时间长度,消息将从当前发送时间点开始延迟固定时间之后才开始投递。
### 注意事项
定时消息的精度会有1s~2s的延迟误差。
定时和延时消息的msg.setStartDeliverTime参数需要设置成当前时间戳之后的某个时刻(单位毫秒)。
如果被设置成当前时间戳之前的某个时刻,消息将立刻投递给消费者。
定时和延时消息的msg.setStartDeliverTime参数可设置40天内的任何时刻(单位毫秒),超过40天消息发送将失败。
StartDeliverTime是服务端开始向消费端投递的时间。如果消费者当前有消息堆积,那么定时和延时消息会排在堆积消息后面,将不能严格按照配置的时间进行投递。
由于客户端和服务端可能存在时间差,消息的实际投递时间与客户端设置的投递时间之间可能存在偏差。
## 如何使用
推荐使用阿里云提供的rocketmq版本的pom


1 ```java
2 <dependency>
3 <groupId>com.aliyun.openservices</groupId>
4 <artifactId>ons-client</artifactId>
5 <version>1.8.4.Final</version>
6 </dependency>
7
8 ```
#### 消息发送
```java import com.aliyun.openservices.ons.api.*;
import com.aliyun.openservices.shade.org.apache.commons.lang3.time.DateFormatUtils; import java.util.Date;
import java.util.Properties; public class ProducerDelayTest {
public static void main(String[] args) {
Properties properties = new Properties();
// AccessKey ID阿里云身份验证,在阿里云RAM控制台创建。
properties.put(PropertyKeyConst.AccessKey, "XXX");
// AccessKey Secret阿里云身份验证,在阿里云RAM控制台创建。
properties.put(PropertyKeyConst.SecretKey, "XXX");
// 设置TCP接入域名,进入消息队列RocketMQ版控制台实例详情页面的接入点区域查看。
properties.put(PropertyKeyConst.NAMESRV_ADDR, "localhost:9876");
Producer producer = ONSFactory.createProducer(properties);
// 在发送消息前,必须调用start方法来启动Producer,只需调用一次即可。
producer.start(); for(int i=0;i<1;i++) {
Message msg = new Message(
// 您在消息队列RocketMQ版控制台创建的Topic。
"TopicTest",
// Message Tag,可理解为Gmail中的标签,对消息进行再归类,方便Consumer指定过滤条件在消息队列RocketMQ版服务器过滤。
"TagA",
// Message Body可以是任何二进制形式的数据,消息队列RocketMQ版不做任何干预,需要Producer与Consumer协商好一致的序列化和反序列化方式。
"演示15秒钟>>> ".getBytes());
// 设置代表消息的业务关键属性,请尽可能全局唯一。
// 以方便您在无法正常收到消息情况下,可通过控制台查询消息并补发。
// 注意:不设置也不会影响消息正常收发。
msg.setKey("ORDERID_100e");
try {
// 延时消息,单位毫秒(ms),在指定延迟时间(当前时间之后)进行投递,例如消息在15秒后投递。
long delayTime = System.currentTimeMillis() + 15000;
System.out.println("发送时间>>" + DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss")); // 设置消息需要被投递的时间。
msg.setStartDeliverTime(delayTime); SendResult sendResult = producer.send(msg);
// 同步发送消息,只要不抛异常就是成功。
if (sendResult != null) {
System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss") + " Send mq message success. Topic is:" + msg.getTopic() + " msgId is: " + sendResult.getMessageId());
}
} catch (Exception e) {
// 消息发送失败,需要进行重试处理,可重新发送这条消息或持久化这条数据进行补偿处理。
System.out.println(new Date() + " Send mq message failed. Topic is:" + msg.getTopic());
e.printStackTrace();
}
}
// 在应用退出前,销毁Producer对象。
// 注意:如果不销毁也没有问题。
producer.shutdown();
}
}
```
#### 消息接收


1 ```java
2
3 import com.aliyun.openservices.shade.org.apache.commons.lang3.time.DateFormatUtils;
4 import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
5 import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
6 import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
7 import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
8 import org.apache.rocketmq.client.exception.MQClientException;
9 import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
10 import org.apache.rocketmq.common.message.MessageExt;
11
12 import java.util.Date;
13 import java.util.List;
14
15 /**
16 * This example shows how to subscribe and consume messages using providing {@link DefaultMQPushConsumer}.
17 */
18 public class Consumer {
19
20 public static void main(String[] args) throws InterruptedException, MQClientException {
21
22 /*
23 * Instantiate with specified consumer group name.
24 */
25
26 final int[] totals = {0};
27 DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_4");
28
29 consumer.setNamesrvAddr("localhost:9876");
30 /*
31 * Specify name server addresses.
32 * <p/>
33 *
34 * Alternatively, you may specify name server addresses via exporting environmental variable: NAMESRV_ADDR
35 * <pre>
36 * {@code
37 * consumer.setNamesrvAddr("name-server1-ip:9876;name-server2-ip:9876");
38 * }
39 * </pre>
40 */
41
42 /*
43 * Specify where to start in case the specified consumer group is a brand new one.
44 */
45 consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
46
47 /*
48 * Subscribe one more more topics to consume.
49 */
50 consumer.subscribe("TopicTest", "*");
51
52 /*
53 * Register callback to execute on arrival of messages fetched from brokers.
54 */
55 consumer.registerMessageListener(new MessageListenerConcurrently() {
56
57 @Override
58 public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
59 ConsumeConcurrentlyContext context) {
60 System.out.println("接收到消息:"+ DateFormatUtils.format(new Date(),"yyyy-MM-dd HH:mm:ss"));
61 System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs);
62 for(MessageExt m: msgs){
63 System.out.println(">>>"+new String( m.getBody()));
64 }
65 totals[0] +=1;
66 System.out.println(">>>>>total="+ totals[0]);
67 return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
68 }
69 });
70
71 /*
72 * Launch the consumer instance.
73 */
74 consumer.start();
75
76 System.out.printf("Consumer Started.%n");
77 }
78 }
79
80
81 ```
82 ### 如何使用社区版本的rocketmq 发送延迟消息
83
84 ```java
85 /*设置为您在消息队列RocketMQ版控制台创建的Topic。*/
86 Message msg = new Message("YOUR TOPIC",
87 /*设置消息的Tag。*/
88 "YOUR MESSAGE TAG",
89 /*消息内容。*/
90 "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET));
91 /*发送延时消息,需要设置延时时间,单位毫秒(ms),消息将在指定延时时间后投递,例如消息将在3秒后投递。*/
92 long delayTime = System.currentTimeMillis() + 3000;
93 msg.putUserProperty("__STARTDELIVERTIME", String.valueOf(delayTime));
94
95 /**
96 *若需要发送定时消息,则需要设置定时时间,消息将在指定时间进行投递,例如消息将在2021-08-10 18:45:00投递。
97 *定时时间格式为:yyyy-MM-dd HH:mm:ss,若设置的时间戳在当前时间之前,则消息将被立即投递给Consumer。
98 * long timeStamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2021-08-10 18:45:00").getTime();
99 * msg.putUserProperty("__STARTDELIVERTIME", String.valueOf(timeStamp));
100 */
101 SendResult sendResult = producer.send(msg);
102 System.out.printf("%s%n", sendResult);
103
104 ```
参考文章实现思路:
https://www.cnblogs.com/hzmark/p/mq-delay-msg.html
rocketmq实现延迟队列(精确到秒级)的更多相关文章
- rocketmq实现延迟队列精确到秒级实现方案3-时间轮和秒级文件实现
时间轮和秒级文件实现原理图 这种方案比较简单实现,通过秒级时间,建立对应的文件夹,只要相同的时间超时的消息,就在同一个目录,通过msgid保证文件不重复,等到了时间后,就扫描对应的文件夹的文件,发送到 ...
- rocketmq实现延迟队列精确到秒级实现(总结编)
前言篇: 为了节约成本,决定通过自研来改造rocketmq,添加任意时间延迟的延时队列,开源版本的rocketmq只有支持18个等级的延迟时间, 其实对于大部分的功能是够用了的,但是以前的项目,全部都 ...
- rocketmq实现延迟队列精确到秒级实现方案2-时间轮和delay-file实现
上图是通过RocketMQ源码分析一个实现原理方案示意图. 分为两个部分: 消息的写入消息的Schedule 在写入CommitLog之前,如果是延迟消息,按照每10分钟写入delayfile文件,对 ...
- rocketmq实现延迟队列精确到秒级实现方案1-代理实现
简单的来说,就是rocketmq发送消息到broker的时候,判断是否定时消息, 如果是定时消息,将消息发送到代理服务(这个是一个独立的服务,需要自己开发,定时地把消息发送出去), 当然了消息用什么来 ...
- android 可以精确到秒级的时间选择器
android自带的时间选择器只能精确到分,但是对于某些应用要求选择的时间精确到秒级,此时只有自定义去实现这样的时间选择器了.下面介绍一个可以精确到秒级的时间选择器. 先上效果图: 下面是工程目录: ...
- js网页倒计时精确到秒级
网页实时倒计时,精确到秒级,和天数倒计时原理一样. 一个很好用的js倒计时!网页实时倒计时,精确到秒级,和天数倒计时原理一样.js倒计时一般用于商城网站团购,特卖,很多地方都可用到!希望能够给大家带来 ...
- rocketmq之延迟队列(按照18个等级来发送)
1 启动消费者等待传入的订阅消息 import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache ...
- golang gin框架中实现一个简单的不是特别精确的秒级限流器
起因 看了两篇关于golang中限流器的帖子: Gin 开发实践:如何实现限流中间件 常用限流策略--漏桶与令牌桶介绍 我照着用,居然没效果-- 时间有限没有深究.这实在是一个很简单的功能,我的需求是 ...
- 基于Redis实现延迟队列
背景 在后端服务中,经常有这样一种场景,写数据库操作在异步队列中执行,且这个异步队列是多进程运行的,这时如果对同一资源进行写库操作,很有可能产生数据被覆盖等问题,于是就需要业务层在更新数据库之前进行加 ...
随机推荐
- 共享资源库(Project)
<Project2016 企业项目管理实践>张会斌 董方好 编著 既然要共享资源库,那就得先建一个可供共享的资源库文件,好吧,说得这么高大上,其实就是一个里面只有资源数据的mpp项目文件, ...
- LuoguP6857 梦中梦与不再有梦 题解
Update \(\texttt{2020.10.20}\) 增加了证明.感谢@东北小蟹蟹(dbxxxqwq)的提醒. Content 有一个 \(n\) 个点的无向图,每两个点之间都有一条边直接相连 ...
- CF1454A Special Permutation 题解
Content 给定一个整数 \(n\),请构造出一个长度为 \(n\) 的排列 \(\{a_i\}_{i=1}^n\),使得对于每个 \(a_i\),都有 \(a_i\neq i\). 我们称一个长 ...
- linux查看磁盘SN
ls -l /dev/disk/by-id/ | grep -iE <SN>
- C++实现反射---RTTR库的使用
使用过C#或者Java 的童鞋,应该对这些语言提供的反射机制有所了解.所谓反射,在我看来就是在只知道一个类的名字(字符串形式)的情况下,自动创建出具体的类实例,并且能够枚举该类型拥有的属性.方法等信息 ...
- SpringBoot使用@Async实现异步调用
1.@EnableAsync 首先,我们需要在启动类上添加 @EnableAsync 注解来声明开启异步方法. @SpringBootApplication @EnableAsync public ...
- nim_duilib(3)之按钮
introduction 更多控件用法,请参考 here 和 源码. 本文的代码基于这里 lets go xml文件添加代码 下面的xml文件内容,删除label控件的相关代码,增加了3个按钮. 其中 ...
- 【LeetCode】Pascal's Triangle II 解题报告
[LeetCode]Pascal's Triangle II 解题报告 标签(空格分隔): LeetCode 题目地址:https://leetcode.com/problems/pascals-tr ...
- 【LeetCode】90. Subsets II 解题报告(Python & C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 递归 回溯法 日期 题目地址:https://leet ...
- Following Orders(poj1270)
Following Orders Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 4436 Accepted: 1791 ...