MQ任意延时消息基于服务端实现

Posted zby9527

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MQ任意延时消息基于服务端实现相关的知识,希望对你有一定的参考价值。

启动RocketMQ

  • 启动nameserver

  • 修改broker配置参数,新增

    messageDelayLevel=1s 2s 4s 8s 16s 32s 64s 128s 256s 512s 1024s 2048s 4096s 8192s 16384s 32768s 65536s 131072s
    
  • org.apache.rocketmq.common.message.MessageConst新增常量

public static final String PROPERTY_MSG_REMAIN_DELAY = "MSG_REMAIN_DELAY_SERVER";
  • org.apache.rocketmq.store.util包下新增工具类
package org.apache.rocketmq.store.util;

/**
 * 任意延时工具类
 * 
 * @author zby
 *
 */
public class ArbitrarilyDelayUtil {

	/**
	 * 延时时间
	 */
	private static final long[] delayArray = new long[] { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192,
			16384, 32768, 65536, 131072 };

	private ArbitrarilyDelayUtil() {
	}

	/**
	 * 根据延时等级获取延时时间
	 * 
	 * @param delayLevel 延时等级
	 * @return 延时时间
	 */
	public static long getDelayTimeFromDelayLevel(int delayLevel) {
		validDelayLevel(delayLevel);
		if (delayLevel == 0) {
			return 0;
		}
		return delayArray[delayLevel - 1];

	}

	/**
	 * 根据延时时间获取最近的延时等级
	 * 
	 * @param delayTime
	 * @return
	 */
	public static int getLatestDelayLevelFromDelayTime(long delayTime) {
		validDelayTime(delayTime);
		if (delayTime == 0) {
			return 0;
		}
		for (int i = 0; i < delayArray.length - 1; i++) {
			if (delayTime >= delayArray[i] && delayTime < delayArray[i + 1]) {
				return i + 1;
			}
		}
		return delayArray.length;
	}

	/**
	 * 校验延时时间
	 */
	private static void validDelayTime(long delayTime) {
		if (delayTime < 0) {
			throw new IllegalArgumentException("Not supported delay time:" + delayTime);
		}
	}

	/**
	 * 校验延时等级
	 */
	private static void validDelayLevel(int delayLevel) {
		if (delayLevel < 0 || delayLevel > delayArray.length) {
			throw new IllegalArgumentException("Not supported delay level:" + delayLevel);
		}
	}
}

  • org.apache.rocketmq.store.CommitLog类新增代码,根据新增代码前的代码找到新增的位置
if (tranType == MessageSysFlag.TRANSACTION_NOT_TYPE || tranType == MessageSysFlag.TRANSACTION_COMMIT_TYPE) {
			// Delay Delivery
			if (msg.getDelayTimeLevel() > 0) {
        省略.....
      }
  		<!-- 新增代码开始-->
			if (msg.getUserProperty(MessageConst.PROPERTY_MSG_REMAIN_DELAY) != null
					&& Long.parseLong(msg.getUserProperty(MessageConst.PROPERTY_MSG_REMAIN_DELAY)) > 0) {
				long remainDelay = Long.parseLong(msg.getUserProperty(MessageConst.PROPERTY_MSG_REMAIN_DELAY));
				int delayTimeLevel = ArbitrarilyDelayUtil.getLatestDelayLevelFromDelayTime(remainDelay);
				msg.setDelayTimeLevel(delayTimeLevel);
				msg.putUserProperty(MessageConst.PROPERTY_MSG_REMAIN_DELAY,
						Long.toString(remainDelay - ArbitrarilyDelayUtil.getDelayTimeFromDelayLevel(delayTimeLevel)));

				topic = ScheduleMessageService.SCHEDULE_TOPIC;
				queueId = ScheduleMessageService.delayLevel2QueueId(msg.getDelayTimeLevel());
				// Backup real topic, queueId
				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);
			}
  		<!-- 新增代码结束-->
		}
  • 启动broker

公共代码

延时等级转换工具类

package com.zby.server;

import java.util.concurrent.TimeUnit;

import org.apache.rocketmq.common.message.Message;

/**
 * 消息延时工具类
 * 
 * @author mac
 *
 */
public class DelayUtil {

	public static final String PROPERTY_MSG_REMAIN_DELAY = "MSG_REMAIN_DELAY_SERVER";

	private DelayUtil() {
	}

	/**
	 * 消息任意延时发送
	 * 
	 * @param msg       消息
	 * @param delayTime 延迟时间
	 * @param timeUnit  延迟时间单位
	 */
	public static void delayMsg(Message msg, long delayTime, TimeUnit timeUnit) {
		msg.putUserProperty(PROPERTY_MSG_REMAIN_DELAY, Long.toString(timeUnit.toSeconds(delayTime)));
	}
}

生产者

生产者示例

package com.zby.server;

import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.concurrent.TimeUnit;

import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;

public class Producer {
	public static void main(String[] args) throws Exception {

		DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");
		producer.setNamesrvAddr("localhost:9876");
		producer.start();

		Message msg = new Message("TopicTest", ("消息发送时间 :" + new Date()).getBytes(StandardCharsets.UTF_8));
		DelayUtil.delayMsg(msg, 27, TimeUnit.MINUTES);
		SendResult sendResult = producer.send(msg);
		System.out.println("消息发送结果:" + sendResult.getSendStatus());
		producer.shutdown();
	}
}

消费者

消费者示例

package com.zby.server;

import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.List;

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.remoting.common.RemotingHelper;

public class Consumer {

	public static void main(String[] args) throws Exception {

		DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_4");
		consumer.setNamesrvAddr("localhost:9876");
		consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
		consumer.subscribe("TopicTest", "*");

		consumer.registerMessageListener(new MessageListenerConcurrently() {

			@Override
			public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
				try {
					System.out.printf("now:%s,%s Receive New Messages: %s %n", new Date(),
							Thread.currentThread().getName(),
							new String(msgs.get(0).getBody(), RemotingHelper.DEFAULT_CHARSET));
				} catch (UnsupportedEncodingException e) {
					e.printStackTrace();
				}
				return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
			}
		});

		consumer.start();

		System.out.printf("Consumer Started.%n");
	}
}

启动测试

  • 启动消费者
  • 启动生产者
  • 消费者输出结果
Consumer Started.
now:Wed Jul 22 12:42:45 CST 2020,ConsumeMessageThread_1 Receive New Messages: 消息发送时间 :Wed Jul 22 12:42:18 CST 2020 

优点

  • 客户端唯一需要的就是发送消息前调用DelayUtil.delayMsg(msg, 27, TimeUnit.MINUTES);
  • 生产消费代码都是copy的quickstart的demo,可以看出侵入性很小

以上是关于MQ任意延时消息基于服务端实现的主要内容,如果未能解决你的问题,请参考以下文章

如何在MQ中实现支持任意延迟的消息?

MQ任意延时消息实现原理概述

教你如何基于Redis来实现高性能延时消息队列!

一口气说出 6 种实现延时消息的方案

框架篇——Spring整合ActiveMQ(MQ服务端与消费端演示)

java-消息中间件-基于内存的mq