解决RocketMQ延时消息失效的问题
- 前言
- 问题的排查
- 解决方案
- 总结
前言
先说一下使用场景,RocketMQ客户端使用的是阿里云的
<dependency>
<groupId>com.aliyun.openservices</groupId>
<artifactId>ons-client</artifactId>
<version>1.8.0.Final</version>
</dependency>
broker和nameServer使用的是RocketMQ开源版本,最终导致的结果是客户端的延时消息功能失效不可用,跟踪源码后最后通过修改源码解决了问题。
问题的排查
首先延时消息的逻辑是在broker进行消息存储的时候进行处理的。所以定位一下broker的源码。
org.apache.rocketmq.store.CommitLog#putMessage
// Delay Delivery
if (msg.getDelayTimeLevel() > 0) {
if (msg.getDelayTimeLevel() > this.defaultMessageStore.getScheduleMessageService().getMaxDelayLevel()) {
// 若设置的延时级别大于最大值,则取默认最大值
msg.setDelayTimeLevel(this.defaultMessageStore.getScheduleMessageService().getMaxDelayLevel());
}
// 将原消息的topic和queue进行替换
topic = TopicValidator.RMQ_SYS_SCHEDULE_TOPIC;
queueId = ScheduleMessageService.delayLevel2QueueId(msg.getDelayTimeLevel());
// 备份原消息的topic和queue
MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_TOPIC, msg.getTopic());
MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_QUEUE_ID, String.valueOf(msg.getQueueId()));
msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties()));
msg.setTopic(topic);
msg.setQueueId(queueId);
}
关键点就在于msg.getDelayTimeLevel()
这里的判断。
org.apache.rocketmq.common.message.Message#getDelayTimeLevel
public int getDelayTimeLevel() {
// public static final String PROPERTY_DELAY_TIME_LEVEL = "DELAY";
String t = this.getProperty(MessageConst.PROPERTY_DELAY_TIME_LEVEL);
if (t != null) {
return Integer.parseInt(t);
}
return 0;
}
可以发现延时消息属性的key为DELAY。接下来看阿里云RocketMQ客户端Message的延时消息属性
com.aliyun.openservices.ons.api.Message#setStartDeliverTime
public void setStartDeliverTime(final long value) {
// public static final String STARTDELIVERTIME = "__STARTDELIVERTIME";
putSystemProperties(SystemPropKey.STARTDELIVERTIME, String.valueOf(value));
}
可以发现这里对延时消息属性的设置key为__STARTDELIVERTIME,和broker设置的key是不同的,所以在broker判断是否为延时消息时,跳过了判断。
解决方案
Message里面有一个userProperties属性,我试着手动把DELAY变量通过k-v的形式加入到消息中,然而失败了,原因如下
com.aliyun.openservices.ons.api.impl.rocketmq.ONSUtil#msgConvert(com.aliyun.openservices.ons.api.Message)
Properties userProperties = message.getUserProperties();
if (userProperties != null) {
Iterator<Entry<Object, Object>> it = userProperties.entrySet().iterator();
while (it.hasNext()) {
Entry<Object, Object> next = it.next();
// RESERVED_KEY_SET_RMQ集合中维护了MQ本身用到的一些关键字,其中已经包括了DELAY
// 所以通过userProperties属性进行添加是没有办法
if (!RESERVED_KEY_SET_RMQ.contains(next.getKey().toString())) {
com.aliyun.openservices.shade.com.alibaba.rocketmq.common.message.MessageAccessor.putProperty(msgRMQ, next.getKey().toString(),
next.getValue().toString());
}
}
}
所以最后的解决办法是在项目中新增了一个和com.aliyun.openservices.ons.api.Message路径完全相同的类,然后直接进行延时消息key的修改
最后进行测试,确认延时消息的功能得以修复。
总结
问题的产生是因为RocketMQ服务端使用的是开源版本,而客户端使用的是阿里云的版本,导致了延时消息失效的问题。最好的解决方案当然是更换客户端的jar包,使得RocketMQ服务端和客户端版本一致。最近在钻研RocketMQ的源码,通过这次问题的排查和解决对源码有了更深的理解和实战经验,也明白了阿里云RocketMQ延时消息的收费是如何实现的。