解决RocketMQ延时消息失效的问题

解决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延时消息的收费是如何实现的。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
RocketMQ支持延时消息,可以通过设置消息延时级别(Delay Level)来指定消息延时时间。延时级别是通过设置消息的属性来实现的。 在RocketMQ中,延时消息的实现原理是通过将消息发送到延时消息队列(Delay Queue)中,在指定的延时时间后再将消息延时消息队列中取出,发送到目标消息队列中。 延时消息可以用于各种场景,比如定时任务、订单超时提醒等。 下面是使用RocketMQ发送延时消息的示例代码: ``` // 创建生产者实例 DefaultMQProducer producer = new DefaultMQProducer("producer_group"); // 设置NameServer地址 producer.setNamesrvAddr("localhost:9876"); // 启动生产者实例 producer.start(); // 创建消息实例,设置消息内容 Message msg = new Message("topic_name", "tag_name", "Hello RocketMQ".getBytes()); // 设置消息延时级别为3,表示延时10s发送 msg.setDelayTimeLevel(3); // 发送消息 SendResult result = producer.send(msg); // 输出发送结果 System.out.println(result); // 关闭生产者实例 producer.shutdown(); ``` 在这个示例中,我们创建了一个生产者实例,并设置了NameServer地址。然后创建一个消息实例,设置消息内容,并将延时级别设置为3。最后发送消息,并输出发送结果。 需要注意的是,延时消息延时时间是在消息发送后计算的,而不是在消息创建时计算的。因此,如果需要精确控制延时时间,需要考虑网络延迟等因素。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值