【RocketMQ】5.顺序消息、延时消息、批量消息

顺序消息

消息有序指的是可以按照消息的发送顺序来消费(FIFO)。RocketMq可以严格的保证消息有序,可以分为区分有序或者全局有序。

顺序消费的原理解析,在默认的情况下消息发送会采取Round Robin轮询方式把消息发送到不同的queue(分区队列);而消费消息的时候从多个queue上拉取消息,这种情况发送和消息式不能保证顺序。但是如果控制发送的顺序消息只依次发送到同一个queue中,消费的时候只从这个queue上拉取,则就保证了顺序。当发送和消费参与的queue只有一个,则是全局有序;如果多个queue参与,则为份去有序,即相对每个queue,消息都是有序的。

下面用订单进行份去有序的示例。一个订单的顺序流程是:创建、付款、推送、完成。订单号相同的消息会被先后发送到同一个队列中,消费时,同一个OrderId获取到的肯定时同一个队列。
在这里插入图片描述

创建生成订单类
/**
    * 订单的步骤
    */
   private static class OrderStep {
       private long orderId;
       private String desc;

       public long getOrderId() {
           return orderId;
       }

       public void setOrderId(long orderId) {
           this.orderId = orderId;
       }

       public String getDesc() {
           return desc;
       }

       public void setDesc(String desc) {
           this.desc = desc;
       }

       @Override
       public String toString() {
           return "OrderStep{" +
               "orderId=" + orderId +
               ", desc='" + desc + '\'' +
               '}';
       }
   }

   /**
    * 生成模拟订单数据
    */
   private List<OrderStep> buildOrders() {
       List<OrderStep> orderList = new ArrayList<OrderStep>();

       OrderStep orderDemo = new OrderStep();
       orderDemo.setOrderId(15103111039L);
       orderDemo.setDesc("创建");
       orderList.add(orderDemo);

       orderDemo = new OrderStep();
       orderDemo.setOrderId(15103111065L);
       orderDemo.setDesc("创建");
       orderList.add(orderDemo);

       orderDemo = new OrderStep();
       orderDemo.setOrderId(15103111039L);
       orderDemo.setDesc("付款");
       orderList.add(orderDemo);

       orderDemo = new OrderStep();
       orderDemo.setOrderId(15103117235L);
       orderDemo.setDesc("创建");
       orderList.add(orderDemo);

       orderDemo = new OrderStep();
       orderDemo.setOrderId(15103111065L);
       orderDemo.setDesc("付款");
       orderList.add(orderDemo);

       orderDemo = new OrderStep();
       orderDemo.setOrderId(15103117235L);
       orderDemo.setDesc("付款");
       orderList.add(orderDemo);

       orderDemo = new OrderStep();
       orderDemo.setOrderId(15103111065L);
       orderDemo.setDesc("完成");
       orderList.add(orderDemo);

       orderDemo = new OrderStep();
       orderDemo.setOrderId(15103111039L);
       orderDemo.setDesc("推送");
       orderList.add(orderDemo);

       orderDemo = new OrderStep();
       orderDemo.setOrderId(15103117235L);
       orderDemo.setDesc("完成");
       orderList.add(orderDemo);

       orderDemo = new OrderStep();
       orderDemo.setOrderId(15103111039L);
       orderDemo.setDesc("完成");
       orderList.add(orderDemo);

       return orderList;
   }
}
生产者消费者
public class Producer {

    public static void main(String[] args) throws Exception {
        //1.创建消息生产者producer,并制定生产者组名
        DefaultMQProducer producer = new DefaultMQProducer("group1");
        //2.指定Nameserver地址
        producer.setNamesrvAddr("一号服务器公网IP:9876;二号服务器公网IP:9876");
        //3.启动producer
        producer.start();
        //构建消息集合
        List<OrderStep> orderSteps = OrderStep.buildOrders();
        //发送消息
        for (int i = 0; i < orderSteps.size(); i++) {
            String body = orderSteps.get(i) + "";
            Message message = new Message("OrderTopic", "Order", "i" + i, body.getBytes());
            /**
             * 参数一:消息对象
             * 参数二:消息队列的选择器
             * 参数三:选择队列的业务标识(订单ID)
             */
            SendResult sendResult = producer.send(message, new MessageQueueSelector() {
                /**
                 *
                 * @param mqs:队列集合
                 * @param msg:消息对象
                 * @param arg:业务标识的参数
                 * @return
                 */
                @Override
                public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
                    long orderId = (long) arg;
                    long index = orderId % mqs.size();
                    return mqs.get((int) index);
                }
            }, orderSteps.get(i).getOrderId());

            System.out.println("发送结果:" + sendResult);
        }
        producer.shutdown();
    }

}

消费者
public class Consumer {
    public static void main(String[] args) throws MQClientException {
        //1.创建消费者Consumer,制定消费者组名
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
        //2.指定Nameserver地址
        consumer.setNamesrvAddr("一号服务器公网IP:9876;二号服务器公网IP:9876");
        //3.订阅主题Topic和Tag
        consumer.subscribe("OrderTopic", "*");

        //4.注册消息监听器
        consumer.registerMessageListener(new MessageListenerOrderly() {

            @Override
            public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
                for (MessageExt msg : msgs) {
                    System.out.println("线程名称:【" + Thread.currentThread().getName() + "】:" + new String(msg.getBody()));
                }
                return ConsumeOrderlyStatus.SUCCESS;
            }
        });

        //5.启动消费者
        consumer.start();

        System.out.println("消费者启动");

    }
}

消费结果
消费者启动
线程名称:【ConsumeMessageThread_1】:OrderStep{orderId=1065, desc='创建'}
线程名称:【ConsumeMessageThread_1】:OrderStep{orderId=1065, desc='付款'}
线程名称:【ConsumeMessageThread_1】:OrderStep{orderId=1065, desc='完成'}
线程名称:【ConsumeMessageThread_2】:OrderStep{orderId=7235, desc='创建'}
线程名称:【ConsumeMessageThread_2】:OrderStep{orderId=7235, desc='付款'}
线程名称:【ConsumeMessageThread_2】:OrderStep{orderId=7235, desc='完成'}
线程名称:【ConsumeMessageThread_3】:OrderStep{orderId=1039, desc='创建'}
线程名称:【ConsumeMessageThread_3】:OrderStep{orderId=1039, desc='付款'}
线程名称:【ConsumeMessageThread_3】:OrderStep{orderId=1039, desc='推送'}
线程名称:【ConsumeMessageThread_3】:OrderStep{orderId=1039, desc='完成'}

从上面我们可以看到实现了分区有序,即一个线程只完成唯一标识的订单消息

延时消息

比如电商里,提交了一个订单就可以发一个延时消息,1h后去检查这个订单的状态,如果还是未付款就取消订单释放库存。

使用限制

现在RocketMq并不支持任意时间的延迟,需要设置几个固定的延时等级从1s到2h分别对应着等级1-18

// 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";

延时消费

与之前不同的时,多了msg.setDelayTimeLevel(2)来控制延时

public class Producer {

    public static void main(String[] args) throws InterruptedException, RemotingException, MQClientException, MQBrokerException {
        //1.创建消息生产者producer,并制定生产者组名
        DefaultMQProducer producer = new DefaultMQProducer("group1");
        //2.指定Nameserver地址
        producer.setNamesrvAddr("一号服务器公网IP:9876;二号服务器公网IP:9876");
        //3.启动producer
        producer.start();

        for (int i = 0; i < 10; i++) {
            //4.创建消息对象,指定主题Topic、Tag和消息体
            /**
             * 参数一:消息主题Topic
             * 参数二:消息Tag
             * 参数三:消息内容
             */
            Message msg = new Message("DelayTopic", "Tag1", ("Hello World" + i).getBytes());
            //设定延迟时间
            msg.setDelayTimeLevel(2);
            //5.发送消息
            SendResult result = producer.send(msg);
            //发送状态
            SendStatus status = result.getSendStatus();

            System.out.println("发送结果:" + result);

            //线程睡1秒
            TimeUnit.SECONDS.sleep(1);
        }

        //6.关闭生产者producer
        producer.shutdown();
    }

}

消费者
public class Consumer {

    public static void main(String[] args) throws Exception {
        //1.创建消费者Consumer,制定消费者组名
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
        //2.指定Nameserver地址
        consumer.setNamesrvAddr("一号服务器公网IP:9876;二号服务器公网IP:9876");
        //3.订阅主题Topic和Tag
        consumer.subscribe("DelayTopic", "*");

        //4.设置回调函数,处理消息
        consumer.registerMessageListener(new MessageListenerConcurrently() {

            //接受消息内容
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
                for (MessageExt msg : msgs) {
                    System.out.println("消息ID:【" + msg.getMsgId() + "】,延迟时间:" + (System.currentTimeMillis() - msg.getStoreTimestamp()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        //5.启动消费者consumer
        consumer.start();

        System.out.println("消费者启动");
    }
}

延时与非延时比较

延时

消费者启动
消息ID:【C0A80325321C00B4AAC2373564AA0003】,延迟时间:27884
消息ID:【C0A80325321C00B4AAC2373570C60006】,延迟时间:24777
消息ID:【C0A80325321C00B4AAC2373558250000】,延迟时间:31027
消息ID:【C0A80325321C00B4AAC2373578E30008】,延迟时间:22699
消息ID:【C0A80325321C00B4AAC2373568B20004】,延迟时间:29139
消息ID:【C0A80325321C00B4AAC237355C720001】,延迟时间:32241
消息ID:【C0A80325321C00B4AAC237357CF40009】,延迟时间:23954
消息ID:【C0A80325321C00B4AAC23735609E0002】,延迟时间:31204
消息ID:【C0A80325321C00B4AAC2373574D50007】,延迟时间:26058
消息ID:【C0A80325321C00B4AAC237356CB90005】,延迟时间:28134

非延时

消息ID:【C0A8032527E800B4AAC2373811BC0000】,延迟时间:1126
消息ID:【C0A8032527E800B4AAC2373815F20001】,延迟时间:1218
消息ID:【C0A8032527E800B4AAC237381A800002】,延迟时间:1231
消息ID:【C0A8032527E800B4AAC237381F700003】,延迟时间:1132
消息ID:【C0A8032527E800B4AAC2373824620004】,延迟时间:1242
消息ID:【C0A8032527E800B4AAC2373829070005】,延迟时间:1133
消息ID:【C0A8032527E800B4AAC237382D160006】,延迟时间:1133
消息ID:【C0A8032527E800B4AAC2373831240007】,延迟时间:1127
消息ID:【C0A8032527E800B4AAC23738352F0008】,延迟时间:1132
消息ID:【C0A8032527E800B4AAC23738393B0009】,延迟时间:1127

考虑到网络延迟,机器卡顿等一些因素,消息可能不是即时送达,但是延时与非延时的延时时间有很大区别

批量消息

如果每次只发送不超过4MB的消息,则很容易使用批处理,样例如下

生产者
消息ID:【C0A8032527E800B4AAC2373811BC0000】,延迟时间:1126
消息ID:【C0A8032527E800B4AAC2373815F20001】,延迟时间:1218
消息ID:【C0A8032527E800B4AAC237381A800002】,延迟时间:1231
消息ID:【C0A8032527E800B4AAC237381F700003】,延迟时间:1132
消息ID:【C0A8032527E800B4AAC2373824620004】,延迟时间:1242
消息ID:【C0A8032527E800B4AAC2373829070005】,延迟时间:1133
消息ID:【C0A8032527E800B4AAC237382D160006】,延迟时间:1133
消息ID:【C0A8032527E800B4AAC2373831240007】,延迟时间:1127
消息ID:【C0A8032527E800B4AAC23738352F0008】,延迟时间:1132
消息ID:【C0A8032527E800B4AAC23738393B0009】,延迟时间:1127

发送结果

消息ID:【C0A8032527E800B4AAC2373811BC0000】,延迟时间:1126
消息ID:【C0A8032527E800B4AAC2373815F20001】,延迟时间:1218
消息ID:【C0A8032527E800B4AAC237381A800002】,延迟时间:1231
消息ID:【C0A8032527E800B4AAC237381F700003】,延迟时间:1132
消息ID:【C0A8032527E800B4AAC2373824620004】,延迟时间:1242
消息ID:【C0A8032527E800B4AAC2373829070005】,延迟时间:1133
消息ID:【C0A8032527E800B4AAC237382D160006】,延迟时间:1133
消息ID:【C0A8032527E800B4AAC2373831240007】,延迟时间:1127
消息ID:【C0A8032527E800B4AAC23738352F0008】,延迟时间:1132
消息ID:【C0A8032527E800B4AAC23738393B0009】,延迟时间:1127

消息分割

如果消息的总长度可能大于4MB,这时候最好把消息进行分割

消息ID:【C0A8032527E800B4AAC2373811BC0000】,延迟时间:1126
消息ID:【C0A8032527E800B4AAC2373815F20001】,延迟时间:1218
消息ID:【C0A8032527E800B4AAC237381A800002】,延迟时间:1231
消息ID:【C0A8032527E800B4AAC237381F700003】,延迟时间:1132
消息ID:【C0A8032527E800B4AAC2373824620004】,延迟时间:1242
消息ID:【C0A8032527E800B4AAC2373829070005】,延迟时间:1133
消息ID:【C0A8032527E800B4AAC237382D160006】,延迟时间:1133
消息ID:【C0A8032527E800B4AAC2373831240007】,延迟时间:1127
消息ID:【C0A8032527E800B4AAC23738352F0008】,延迟时间:1132
消息ID:【C0A8032527E800B4AAC23738393B0009】,延迟时间:1127

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值