RocketMQ延时消息
本篇基于RocketMQ集群开发,如需了解集群的搭建,可参考文章RocketMQ集群安装
Producer
public class SchedulerMessageProducer {
public static void main(String[] args) throws Exception {
// 1.指定生产者组
DefaultMQProducer defaultMQProducer = new DefaultMQProducer(MyRocketMqConstant.SchedulerLearn.SCHEDULER_LEARN_PRODUCER_GROUP);
// 2.关联注册中心
defaultMQProducer.setNamesrvAddr(MyRocketMqConstant.NAME_SRV);
// 3.启动生产者
defaultMQProducer.start();
int totalMessageToSend = 10;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
for (int i = 0; i < totalMessageToSend; i++) {
String str = "This is a scheduler message";
Message message = new Message(MyRocketMqConstant.SchedulerLearn.SCHEDULER_LEARN_TOPIC
, MyRocketMqConstant.SchedulerLearn.SCHEDULER_LEARN_TAG_A
, "KEY" + i, str.getBytes(StandardCharsets.UTF_8));
// 4.设置延时等级3,这个消息将在10s后发送(现在只支持固定的几个时间,详见delayTimeLevel)
// 生产者主线程直接遍历跑完,消息的延迟应该是broker根据message的DELAY属性来判断是否延迟存储消息的
message.setDelayTimeLevel(3);
SendResult sendResult = defaultMQProducer.send(message);
System.out.println(String.format("SendResult status:%s,queueId:%d,body:%s",
sendResult.getSendStatus(),
sendResult.getMessageQueue().getQueueId(),
str+",msgSendBrokerTime "+sdf.format(new Date())));
}
// 关闭生产者
defaultMQProducer.shutdown();
}
}
Consumer
public class SchedulerMessageConsumer {
public static void main(String[] args) throws Exception {
// 1.消费者关联消费者组
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(MyRocketMqConstant.SchedulerLearn.SCHEDULER_LEARN_CONSUMER_GROUP);
// 2.关联注册中心
consumer.setNamesrvAddr(MyRocketMqConstant.NAME_SRV);
// 3.订阅消息
consumer.subscribe(MyRocketMqConstant.SchedulerLearn.SCHEDULER_LEARN_TOPIC
,MyRocketMqConstant.SchedulerLearn.SCHEDULER_LEARN_TAG_A);
// 4.注册监听器
consumer.registerMessageListener(new MessageListenerOrderly() {
Random random = new Random();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
for (MessageExt msg : msgs) {
Date msgDate = new Date(msg.getStoreTimestamp());
Date now = new Date();
System.out.println("[consumerThread=" + Thread.currentThread().getName()
+ "] [broker=" + msg.getBrokerName() + msg.getStoreHost()
+ "] [queueId=" + msg.getQueueId()
+ "] [now"+ sdf.format(now) +" storeDate"+sdf.format(msgDate)
+ "] [content=" + new String(msg.getBody()) +"]");
}
try {
// 模拟业务处理
TimeUnit.SECONDS.sleep(random.nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
System.out.println("消费者启动");
consumer.start();
}
}
测试结果
message.setDelayTimeLevel(3);
设置出的消息延时为10s,生产者控制台输出的msgSendBrokerTime和消费者接收到消息的storeTimestamp如果差距跟10s过大时,需要重置服务器的时间,设置服务器时间,可参考下面的***设置服务器时间跟随网络时间***。
观察输出消息,能够发现消息发送时间msgSendBrokerTime和消息存储时间storeDate相差的时间为10s,这是符合我们生产者发送消息设置的延时等级的。
tips:RocketMQ源码示例中说:*您将会看到消息的消费比存储时间晚10秒。*实践证明这个说法不正确,应该是:消息的存储时间比发送消息时间晚10s。
生产者
SendResult status:SEND_OK,queueId:1,body:This is a scheduler message,msgSendBrokerTime 2021-04-18 18:11:07
SendResult status:SEND_OK,queueId:2,body:This is a scheduler message,msgSendBrokerTime 2021-04-18 18:11:07
SendResult status:SEND_OK,queueId:3,body:This is a scheduler message,msgSendBrokerTime 2021-04-18 18:11:07
SendResult status:SEND_OK,queueId:0,body:This is a scheduler message,msgSendBrokerTime 2021-04-18 18:11:07
SendResult status:SEND_OK,queueId:1,body:This is a scheduler message,msgSendBrokerTime 2021-04-18 18:11:07
SendResult status:SEND_OK,queueId:2,body:This is a scheduler message,msgSendBrokerTime 2021-04-18 18:11:07
SendResult status:SEND_OK,queueId:3,body:This is a scheduler message,msgSendBrokerTime 2021-04-18 18:11:07
SendResult status:SEND_OK,queueId:0,body:This is a scheduler message,msgSendBrokerTime 2021-04-18 18:11:07
SendResult status:SEND_OK,queueId:1,body:This is a scheduler message,msgSendBrokerTime 2021-04-18 18:11:07
SendResult status:SEND_OK,queueId:2,body:This is a scheduler message,msgSendBrokerTime 2021-04-18 18:11:07
消费者
[consumerThread=ConsumeMessageThread_11] [broker=broker-b/192.168.15.16:10911] [queueId=1] [now2021-04-18 18:11:17 storeDate2021-04-18 18:11:17] [content=This is a scheduler message]
[consumerThread=ConsumeMessageThread_14] [broker=broker-b/192.168.15.16:10911] [queueId=2] [now2021-04-18 18:11:17 storeDate2021-04-18 18:11:17] [content=This is a scheduler message]
[consumerThread=ConsumeMessageThread_13] [broker=broker-b/192.168.15.16:10911] [queueId=3] [now2021-04-18 18:11:17 storeDate2021-04-18 18:11:17] [content=This is a scheduler message]
[consumerThread=ConsumeMessageThread_5] [broker=broker-a/192.168.15.15:10911] [queueId=0] [now2021-04-18 18:11:17 storeDate2021-04-18 18:11:17] [content=This is a scheduler message]
[consumerThread=ConsumeMessageThread_4] [broker=broker-a/192.168.15.15:10911] [queueId=1] [now2021-04-18 18:11:17 storeDate2021-04-18 18:11:17] [content=This is a scheduler message]
[consumerThread=ConsumeMessageThread_8] [broker=broker-a/192.168.15.15:10911] [queueId=2] [now2021-04-18 18:11:17 storeDate2021-04-18 18:11:17] [content=This is a scheduler message]
[consumerThread=ConsumeMessageThread_3] [broker=broker-a/192.168.15.15:10911] [queueId=3] [now2021-04-18 18:11:17 storeDate2021-04-18 18:11:17] [content=This is a scheduler message]
[consumerThread=ConsumeMessageThread_10] [broker=broker-b/192.168.15.16:10911] [queueId=0] [now2021-04-18 18:11:17 storeDate2021-04-18 18:11:17] [content=This is a scheduler message]
[consumerThread=ConsumeMessageThread_14] [broker=broker-b/192.168.15.16:10911] [queueId=2] [now2021-04-18 18:11:24 storeDate2021-04-18 18:11:17] [content=This is a scheduler message]
[consumerThread=ConsumeMessageThread_11] [broker=broker-b/192.168.15.16:10911] [queueId=1] [now2021-04-18 18:11:26 storeDate2021-04-18 18:11:17] [content=This is a scheduler message]
设置服务器时间跟随网络时间
登录服务器,检查当前的时间是否与网络时间一致。不一致需要重新校验,校验步骤如下,其中ntpdate 0.asia.pool.ntp.org
可以多执行一次,避免校验不准确
[root@rocketmq-slave02 rocketmq-all-4.8.0-bin-release]# yum -y install ntp ntpdate
#<============time.nist.gov、time.nuri.net、0.asia.pool.ntp.org、1.asia.pool.ntp.org、2.asia.pool.ntp.org、3.asia.pool.ntp.org中任意一个,只要保证可用就OK。
[root@rocketmq-slave02 rocketmq-all-4.8.0-bin-release]# ntpdate 0.asia.pool.ntp.org
18 Apr 17:36:00 ntpdate[5269]: step time server 80.241.0.72 offset 51.385016 sec
[root@rocketmq-master01 rocketmq-all-4.8.0-bin-release]# ntpdate 0.asia.pool.ntp.org
18 Apr 18:09:14 ntpdate[42050]: step time server 211.233.84.186 offset 3.543249 sec
#<============写入硬件时间
[root@rocketmq-slave02 rocketmq-all-4.8.0-bin-release]# hwclock --systohc
[root@rocketmq-slave02 rocketmq-all-4.8.0-bin-release]# timedatectl
Local time: 日 2021-04-18 17:36:18 CST
Universal time: 日 2021-04-18 09:36:18 UTC
RTC time: 日 2021-04-18 09:36:19
Time zone: Asia/Shanghai (CST, +0800)
NTP enabled: no
NTP synchronized: no
RTC in local TZ: no
DST active: n/a
[root@rocketmq-slave02 rocketmq-all-4.8.0-bin-release]#
总结
延时消息的使用场景
比如电商里,提交了一个订单就可以发送一个延时消息,1h后去检查这个订单的状态,如果还是未付款就取消订单释放库存。
延时消息的使用限制
// org/apache/rocketmq/store/config/MessageStoreConfig.java
private String messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h";
现在RocketMq并不支持任意时间的延时,需要设置几个固定的延时等级,从1s到2h分别对应着等级1到18; 消息消费失败会进入延时消息队列,消息发送时间与设置的延时等级和重试次数有关,详见代码SendMessageProcessor.java
。