6.顺序消息的发送与消费

4.2 顺序消息

消息有序指的是可以按照消息的发送顺序来消费(FIFO)。

RocketMQ可以严格的保证消息有序,可以分为分区有序或者全局有序。

顺序消费的原理解析,在默认的情况下消息发送会采取Round Robin轮询方式把消息发送到不同的queue(分区队列);而消费消息的时候从多个queue上拉取消息,这种情况发送和消费是不能保证顺序。

但是如果控制发送的顺序消息只依次发送到同一个queue中,消费的时候只从这个queue上依次拉取,则就保证了顺序。

当发送和消费参与的queue只有一个,则是全局有序;如果多个queue参与,则为分区有序,即相对每个queue,消息都是有序的。

下面用订单进行分区有序的示例。

一个订单的顺序流程是:创建、付款、推送、完成。

订单号相同的消息会被先后发送到同一个队列中,消费时,同一个OrderId获取到的肯定是同一个队列。

4.2.1 顺序消息生产

java package com.itheima.mq.rocketmq.order; ​ import java.util.ArrayList; import java.util.List; ​ /** * 订单构建者 */ public class OrderStep {    private long orderId;    private String desc;    private Long date = System.currentTimeMillis(); ​    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 + ''' +                ", date=" + date +                '}';   } ​    public static List<OrderStep> buildOrders() {        // 1039L   : 创建   付款 推送 完成        // 1065L   : 创建   付款        // 7235L   : 创建   付款        List<OrderStep> orderList = new ArrayList<OrderStep>(); ​        OrderStep orderDemo = new OrderStep();        orderDemo.setOrderId(1039L);        orderDemo.setDesc("创建");        orderList.add(orderDemo); ​        orderDemo = new OrderStep();        orderDemo.setOrderId(1065L);        orderDemo.setDesc("创建");        orderList.add(orderDemo); ​        orderDemo = new OrderStep();        orderDemo.setOrderId(1039L);        orderDemo.setDesc("付款");        orderList.add(orderDemo); ​        orderDemo = new OrderStep();        orderDemo.setOrderId(7235L);        orderDemo.setDesc("创建");        orderList.add(orderDemo); ​        orderDemo = new OrderStep();        orderDemo.setOrderId(1065L);        orderDemo.setDesc("付款");        orderList.add(orderDemo); ​        orderDemo = new OrderStep();        orderDemo.setOrderId(7235L);        orderDemo.setDesc("付款");        orderList.add(orderDemo); ​        orderDemo = new OrderStep();        orderDemo.setOrderId(1065L);        orderDemo.setDesc("完成");        orderList.add(orderDemo); ​        orderDemo = new OrderStep();        orderDemo.setOrderId(1039L);        orderDemo.setDesc("推送");        orderList.add(orderDemo); ​        orderDemo = new OrderStep();        orderDemo.setOrderId(7235L);        orderDemo.setDesc("完成");        orderList.add(orderDemo); ​        orderDemo = new OrderStep();        orderDemo.setOrderId(1039L);        orderDemo.setDesc("完成");        orderList.add(orderDemo); ​        return orderList;   } ​ ​ } ​

RocketMq提供了3种不同的选择队列方式: SelectMessageQueueByHash implements MessageQueueSelector SelectMessageQueueByMachineRoom implements MessageQueueSelector SelectMessageQueueByRandom implements MessageQueueSelector

java package com.itheima.mq.rocketmq.order; ​ import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.MessageQueueSelector; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; ​ import java.util.List; ​ public class Producer { ​    public static void main(String[] args) throws Exception {        //1.创建消息生产者producer,并制定生产者组名        DefaultMQProducer producer = new DefaultMQProducer("group1");        //2.指定Nameserver地址        producer.setNamesrvAddr("127.0.0.1:9876");        //3.启动producer        producer.start();        //构建消息集合        List<OrderStep> orderSteps = OrderStep.buildOrders();        //发送消息        for (int i = 0; i < orderSteps.size(); i++) {            String body = orderSteps.get(i).toString() + "---" + i ;            Message message = new Message("test", "order", "i" + i, body.getBytes());            /**             * 参数一:消息对象             * 参数二:消息队列的选择器             * 参数三:选择队列的业务标识(订单ID)             */            SendResult sendResult = producer.send(message, new MessageQueueSelector() {            /**             @param mqs:当前topic对应的队列集合             @param msg:消息对象             @param arg:业务标识的参数即orderId             @return            **/        @Override       public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {                    byte[] byteArray = (byte[]) msg.getBody();                    System.out.println(new String(byteArray));                    long orderId = (long) arg;                    //使用orderId对当前topic对应的队列集合个数取模                    //保证同一个orderId发送到同一个queue                    long index = orderId % mqs.size();                    return mqs.get((int) index);               }           }, orderSteps.get(i).getOrderId()); ​            System.out.println("发送结果:" + sendResult);       }        producer.shutdown();   } ​ } ​

md OrderStep{orderId=1039, desc='创建', date=1617153133257}---0 发送结果:SendResult [sendStatus=SLAVE_NOT_AVAILABLE, msgId=AC1278E72E2014DAD5DC9C785DC20000, offsetMsgId=AC1278E700002A9F0000000000005C89, messageQueue=MessageQueue [topic=test, brokerName=broker-a, queueId=7], queueOffset=38] OrderStep{orderId=1065, desc='创建', date=1617153133257}---1 发送结果:SendResult [sendStatus=SLAVE_NOT_AVAILABLE, msgId=AC1278E72E2014DAD5DC9C785E390001, offsetMsgId=AC1278E700002A9F0000000000005D6D, messageQueue=MessageQueue [topic=test, brokerName=broker-a, queueId=1], queueOffset=32] OrderStep{orderId=1039, desc='付款', date=1617153133257}---2 发送结果:SendResult [sendStatus=SLAVE_NOT_AVAILABLE, msgId=AC1278E72E2014DAD5DC9C785E950002, offsetMsgId=AC1278E700002A9F0000000000005E51, messageQueue=MessageQueue [topic=test, brokerName=broker-a, queueId=7], queueOffset=39] OrderStep{orderId=7235, desc='创建', date=1617153133257}---3 发送结果:SendResult [sendStatus=SLAVE_NOT_AVAILABLE, msgId=AC1278E72E2014DAD5DC9C785EE40003, offsetMsgId=AC1278E700002A9F0000000000005F35, messageQueue=MessageQueue [topic=test, brokerName=broker-a, queueId=3], queueOffset=29] OrderStep{orderId=1065, desc='付款', date=1617153133257}---4 发送结果:SendResult [sendStatus=SLAVE_NOT_AVAILABLE, msgId=AC1278E72E2014DAD5DC9C785F610004, offsetMsgId=AC1278E700002A9F0000000000006019, messageQueue=MessageQueue [topic=test, brokerName=broker-a, queueId=1], queueOffset=33] OrderStep{orderId=7235, desc='付款', date=1617153133257}---5 发送结果:SendResult [sendStatus=SLAVE_NOT_AVAILABLE, msgId=AC1278E72E2014DAD5DC9C785FBF0005, offsetMsgId=AC1278E700002A9F00000000000060FD, messageQueue=MessageQueue [topic=test, brokerName=broker-a, queueId=3], queueOffset=30] OrderStep{orderId=1065, desc='完成', date=1617153133257}---6 发送结果:SendResult [sendStatus=SLAVE_NOT_AVAILABLE, msgId=AC1278E72E2014DAD5DC9C78604B0006, offsetMsgId=AC1278E700002A9F00000000000061E1, messageQueue=MessageQueue [topic=test, brokerName=broker-a, queueId=1], queueOffset=34] OrderStep{orderId=1039, desc='推送', date=1617153133257}---7 发送结果:SendResult [sendStatus=SLAVE_NOT_AVAILABLE, msgId=AC1278E72E2014DAD5DC9C7860A80007, offsetMsgId=AC1278E700002A9F00000000000062C5, messageQueue=MessageQueue [topic=test, brokerName=broker-a, queueId=7], queueOffset=40] OrderStep{orderId=7235, desc='完成', date=1617153133257}---8 发送结果:SendResult [sendStatus=SLAVE_NOT_AVAILABLE, msgId=AC1278E72E2014DAD5DC9C7861060008, offsetMsgId=AC1278E700002A9F00000000000063A9, messageQueue=MessageQueue [topic=test, brokerName=broker-a, queueId=3], queueOffset=31] OrderStep{orderId=1039, desc='完成', date=1617153133257}---9 发送结果:SendResult [sendStatus=SLAVE_NOT_AVAILABLE, msgId=AC1278E72E2014DAD5DC9C7861630009, offsetMsgId=AC1278E700002A9F000000000000648D, messageQueue=MessageQueue [topic=test, brokerName=broker-a, queueId=7], queueOffset=41]

可以看出来我们发送的消息是编号是0 - 9,对应的queueId是1,3,7。

4.2.2 顺序消费消息

如何保证顺序发送的消息被顺序消费的呢?

那就要知道每个消费者负载均衡的过程。

我们现在启动了3个消费者,每个消费者监听1个队列。

每个消费者依次从自己监听的队列上面拉取消息。

拉取的消息的顺序和发送消息的顺序是一致的。

因此就保证了顺序消费。

java package com.itheima.mq.rocketmq.order; ​ import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.message.MessageExt; ​ import java.util.List; ​ public class Consumer {    public static void main(String[] args) throws MQClientException {        //1.创建消费者Consumer,制定消费者组名        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");        //2.指定Nameserver地址        consumer.setNamesrvAddr("127.0.0.1:9876");        //3.订阅主题Topic和Tag        consumer.subscribe("test", "order"); ​        //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("消费者启动"); ​   } } ​

java 启动3个消费者,可以发现消息分别被3个消费者消费:可以看到每个orderId对应的操作都被同一个消费者消费。并且是严格按照创建 付款 推送 完成的顺序推送。 ​ 消费者1: 线程名称:【ConsumeMessageThread_2】:OrderStep{orderId=1039, desc='创建', date=1617153133257}---0 线程名称:【ConsumeMessageThread_3】:OrderStep{orderId=1039, desc='付款', date=1617153133257}---2 线程名称:【ConsumeMessageThread_4】:OrderStep{orderId=1039, desc='推送', date=1617153133257}---7 线程名称:【ConsumeMessageThread_5】:OrderStep{orderId=1039, desc='完成', date=1617153133257}---9 ​ 消费者2: 线程名称:【ConsumeMessageThread_1】:OrderStep{orderId=7235, desc='创建', date=1617153133257}---3 线程名称:【ConsumeMessageThread_2】:OrderStep{orderId=7235, desc='付款', date=1617153133257}---5 线程名称:【ConsumeMessageThread_3】:OrderStep{orderId=7235, desc='完成', date=1617153133257}---8 ​ 消费者3: 线程名称:【ConsumeMessageThread_1】:OrderStep{orderId=1065, desc='创建', date=1617153133257}---1 线程名称:【ConsumeMessageThread_2】:OrderStep{orderId=1065, desc='付款', date=1617153133257}---4 线程名称:【ConsumeMessageThread_3】:OrderStep{orderId=1065, desc='完成', date=1617153133257}---6

4.2.3提高顺序消费并发:增加queue

队列在 RocketMQ 中是一个非常重要的概念,那队列在 RocketMQ 中的作用是什么呢?

这就要从消息队列的消费机制说起。

几乎所有的消息队列产品都使用一种非常朴素的“请求 - 确认”机制,确保消息不会在传递过程中由于网络或服务器故障丢失。

在生产端,生产者先将消息发送给服务端,也就是 Broker,服务端在收到消息并将消息写入主题或者队列中后,会给生产者发送确认的响应。

如果生产者没有收到服务端的确认或者收到失败的响应,则会【重新发送消息】。

在消费端,消费者在收到消息并完成自己的消费业务逻辑(比如,将数据保存到数据库中)后,也会给服务端发送消费成功的确认,服务端只有收到消费确认后,才认为一条消息被成功消费,否则它会给消费者重新发送这条消息,直到收到对应的消费成功确认。

这个确认机制很好地保证了消息传递过程中的可靠性,但是,引入这个机制在消费端带来了一个消息的有序性的问题?

在某一条消息被成功消费之前,下一条消息是不能被消费的,否则就会出现消息空洞,违背了有序性这个原则。 也就是说,每个主题在任意时刻,至多只能有一个消费者实例在进行消费,那就没法通过水平扩展消费者的数量来提升消费端总体的消费性能。

为了解决这个问题,RocketMQ 在主题下面增加了队列的概念。每个主题包含多个队列,通过多个队列来实现多实例并行生产和消费。

需要注意的是,RocketMQ 只在队列上保证消息的有序性,主题层面是无法保证消息的严格顺序的。如果需要主题层面和全局有序需要全局1个队列!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值