【RocketMQ】(二) 順序消息

消息有序:

        指的是可以按照消息的發送順序來消費(FIFO)。RocketMQ可以嚴格的保證消息有序,可以分爲分區有序或者全局有序。 

 順序消費的原理解析

         在默認的情況下消息發送會採取Round Robin輪詢方式把消息發送到不同的queue(分區隊列);而消費消息的時候從多個queue上拉取消息,這種情況發送和消費是不能保證順序。

         如果控制發送的順序消息只依次發送到同一個queue 中,消費的時候只從這個queue上依次拉取,則就保證了順序。

        (1)當發送和消費參與的 queue只有一個,則是全局有序;

       (2)如果多個queue參與,則爲分區有序,即相對每個 queue,消息都是有序的。

我個人的理解就是

       默認情況下,發送消息是採用輪詢的方式去發送消息,發送不同的queue(隊列)中,假如數據庫有一張user表中沒有任何數據,此時新增一條數據:id=1,name="張三";再修改爲id=1,name="李四";最後再刪除id=1,name="李四";也就是id爲1的這條數據,經過了三次數據庫操作,如果數據庫的每一次操作都需要發送消息到隊列當中,假設有queueA,queueB,queueC,這時候會將三條消息發送到三個不同的隊列當中,消費的話,也是在三個queue中進行拉取,rocketmq這麼做,你在業務處理是沒有辦法,判斷是先新增呢,還是先修改,還是先刪除,這個順序你就沒有辦法去保證;

     有兩種辦法去解決,<1>如果不考慮性能的話,全局消息應該是最容易想到的一種辦法,因爲隊列天然的先進先出的優勢就可以滿足這點;新增一條數據:id=1,name="張三";再修改爲id=1,name="李四";最後再刪除id=1,name="李四";進去隊列是這個順序,取出來也肯定是這個順序。<2>分區消息:就是根據某一個key,比如orderId,userId等這種sharding-key來做分區,拿上面的例子來說,這個id就可以作爲一個sharding-key,根據不同的sharding-key發送到不同的隊列當中,因此在每個隊列當中,這個數據都是有序的。

下面來一張分區消息原理的圖片:

這個原理圖是採用訂單的創建、付款、完成的例子,根據orderId這個key,將orderId=2001訂單的一系列操作發送到其中一個queue,將orderId=3001訂單的一系列操作發送到另一個queue中;這個分配的規則,可以通過取模;

 

例子:張三和李四到銀行去存取錢,然後銀行發送短信告訴用戶,餘額多少?

分析:肯定是先存錢,賬戶有錢後,才能取錢,取完錢或者存完錢後,銀行給用戶發送短信,這個順序是不能亂的,總不可能用戶賬戶沒有錢,還沒有存,就可以取錢了或者就收到了存錢或者取錢短信了。

 生產者代碼:

/**
 * @author lucifer
 * @date 2020/4/14 14:15
 * @description 順序消息 -生產者
 */
public class OrderedProducer {

    public static final String NAME_SERVER_ADDR = "192.168.160.131:9876";

    public static void main(String[] args) throws MQClientException, UnsupportedEncodingException, RemotingException, InterruptedException, MQBrokerException {
        //1.創建生產者對象,並指定組名
        DefaultMQProducer defaultMQProducer = new DefaultMQProducer("GROUP_TEST");
        //2.指定NameServer地址
        defaultMQProducer.setNamesrvAddr(NAME_SERVER_ADDR);
        //3.啓動生產者
        defaultMQProducer.start();
        //設置異步發送失敗重試次數,默認爲2次
        defaultMQProducer.setRetryTimesWhenSendAsyncFailed(0);

        //4.定義消息隊列選擇器
        MessageQueueSelector messageQueueSelector = new MessageQueueSelector() {
            /**
             * 消息隊列選擇器,保證同一條業務數據的消息在同一隊列當中
             * @param list topic中所有隊列的集合
             * @param message   發送的消息
             * @param o 此參數是本示例中defaultMQProducer.send()中的第三個參數
             * @return
             */
            @Override
            public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
                Integer id = (Integer) o;
                //通過id與topic中所有隊列的集合的大小進行取模,求出索引值index
                int index = id % list.size();
                //分區順序:同一個index的消息在同一個隊列當中
                return list.get(index);

                //全局消息,放在同一個queue
                //return list.get(0);
            }
        };

        String[] tags = new String[]{"TagA", "TagB", "TagC"};
        List<Map> bizData = getBizData();

        //5.循環發送消息
        for (int i = 0; i < bizData.size(); i++) {
            Map bizMap = bizData.get(i);
            // keys:業務數據的ID,比如用戶ID、訂單編號等等
            Message message = new Message("TopicTest", tags[i % tags.length],
                    bizMap.get("userId") + "",
                    bizMap.toString().getBytes(RemotingHelper.DEFAULT_CHARSET));
            //發送有序消息
            SendResult sendResult = defaultMQProducer.send(message, messageQueueSelector, bizMap.get("userId"));
            System.out.printf("%s, body:%s%n", sendResult, bizMap);
        }


    }

    private static List<Map> getBizData() {
        List<Map> orders = new ArrayList<>();

        HashMap orderData = new HashMap();
        orderData.put("userId", 1000);
        orderData.put("userName", "張三");
        orderData.put("description", "銀行存錢,存1000");
        orders.add(orderData);

        orderData = new HashMap();
        orderData.put("userId", 1000);
        orderData.put("userName", "張三");
        orderData.put("description", "短信提示:存1000,賬戶餘額1000");
        orders.add(orderData);

        orderData = new HashMap();
        orderData.put("userId", 1000);
        orderData.put("userName", "張三");
        orderData.put("description", "銀行取錢,取300");
        orders.add(orderData);

        orderData = new HashMap();
        orderData.put("userId", 1000);
        orderData.put("userName", "張三");
        orderData.put("description", "短信提示:取300,賬戶餘額700");
        orders.add(orderData);

        orderData = new HashMap();
        orderData.put("userId", 1000);
        orderData.put("userName", "張三");
        orderData.put("description", "銀行存錢,存400");
        orders.add(orderData);

        orderData = new HashMap();
        orderData.put("userId", 1000);
        orderData.put("userName", "張三");
        orderData.put("description", "短信提示:存款400,賬戶餘額1100");
        orders.add(orderData);

        orderData = new HashMap();
        orderData.put("userId", 1001);
        orderData.put("userName", "李四");
        orderData.put("description", "銀行存錢,存600");
        orders.add(orderData);

        orderData = new HashMap();
        orderData.put("userId", 1001);
        orderData.put("userName", "李四");
        orderData.put("description", "短信提示:存款600,賬戶餘額600");
        orders.add(orderData);

        return orders;
    }

}

 啓動生產者,控制檯打印:

(1).如果是全局消息,這裏的queueId是一樣的,放在了同一個queue中;

SendResult [sendStatus=SEND_OK, msgId=24098A1E7A3ECE402C3D4AADC1A10079000018B4AAC2464469920000, offsetMsgId=C0A8A08300002A9F000000000000C1CB, messageQueue=MessageQueue [topic=TopicTest, brokerName=localhost.localdomain, queueId=0], queueOffset=26], body:{description=銀行存錢,存1000, userName=張三, userId=1000}
SendResult [sendStatus=SEND_OK, msgId=24098A1E7A3ECE402C3D4AADC1A10079000018B4AAC2464469980001, offsetMsgId=C0A8A08300002A9F000000000000C2E6, messageQueue=MessageQueue [topic=TopicTest, brokerName=localhost.localdomain, queueId=0], queueOffset=27], body:{description=短信提示:存1000,賬戶餘額1000, userName=張三, userId=1000}
SendResult [sendStatus=SEND_OK, msgId=24098A1E7A3ECE402C3D4AADC1A10079000018B4AAC24644699B0002, offsetMsgId=C0A8A08300002A9F000000000000C414, messageQueue=MessageQueue [topic=TopicTest, brokerName=localhost.localdomain, queueId=0], queueOffset=28], body:{description=銀行取錢,取300, userName=張三, userId=1000}
SendResult [sendStatus=SEND_OK, msgId=24098A1E7A3ECE402C3D4AADC1A10079000018B4AAC24644699D0003, offsetMsgId=C0A8A08300002A9F000000000000C52E, messageQueue=MessageQueue [topic=TopicTest, brokerName=localhost.localdomain, queueId=0], queueOffset=29], body:{description=短信提示:取300,賬戶餘額700, userName=張三, userId=1000}
SendResult [sendStatus=SEND_OK, msgId=24098A1E7A3ECE402C3D4AADC1A10079000018B4AAC24644699F0004, offsetMsgId=C0A8A08300002A9F000000000000C65A, messageQueue=MessageQueue [topic=TopicTest, brokerName=localhost.localdomain, queueId=0], queueOffset=30], body:{description=銀行存錢,存400, userName=張三, userId=1000}
SendResult [sendStatus=SEND_OK, msgId=24098A1E7A3ECE402C3D4AADC1A10079000018B4AAC2464469A10005, offsetMsgId=C0A8A08300002A9F000000000000C776, messageQueue=MessageQueue [topic=TopicTest, brokerName=localhost.localdomain, queueId=0], queueOffset=31], body:{description=短信提示:存款400,賬戶餘額1100, userName=張三, userId=1000}
SendResult [sendStatus=SEND_OK, msgId=24098A1E7A3ECE402C3D4AADC1A10079000018B4AAC2464469A30006, offsetMsgId=C0A8A08300002A9F000000000000C8A4, messageQueue=MessageQueue [topic=TopicTest, brokerName=localhost.localdomain, queueId=0], queueOffset=32], body:{description=銀行存錢,存600, userName=李四, userId=1001}
SendResult [sendStatus=SEND_OK, msgId=24098A1E7A3ECE402C3D4AADC1A10079000018B4AAC2464469A50007, offsetMsgId=C0A8A08300002A9F000000000000C9C0, messageQueue=MessageQueue [topic=TopicTest, brokerName=localhost.localdomain, queueId=0], queueOffset=33], body:{description=短信提示:存款600,賬戶餘額600, userName=李四, userId=1001}

 (1).如果是分區消息,這裏的queueId是不一樣的,放在了不同queue中;

SendResult [sendStatus=SEND_OK, msgId=24098A1E7A3ECE402C3D4AADC1A10079000018B4AAC24648E14E0000, offsetMsgId=C0A8A08300002A9F000000000000E653, messageQueue=MessageQueue [topic=TopicTest, brokerName=localhost.localdomain, queueId=8], queueOffset=42], body:{description=銀行存錢,存1000, userName=張三, userId=1000}
SendResult [sendStatus=SEND_OK, msgId=24098A1E7A3ECE402C3D4AADC1A10079000018B4AAC24648E1540001, offsetMsgId=C0A8A08300002A9F000000000000E76E, messageQueue=MessageQueue [topic=TopicTest, brokerName=localhost.localdomain, queueId=8], queueOffset=43], body:{description=短信提示:存1000,賬戶餘額1000, userName=張三, userId=1000}
SendResult [sendStatus=SEND_OK, msgId=24098A1E7A3ECE402C3D4AADC1A10079000018B4AAC24648E1560002, offsetMsgId=C0A8A08300002A9F000000000000E89C, messageQueue=MessageQueue [topic=TopicTest, brokerName=localhost.localdomain, queueId=8], queueOffset=44], body:{description=銀行取錢,取300, userName=張三, userId=1000}
SendResult [sendStatus=SEND_OK, msgId=24098A1E7A3ECE402C3D4AADC1A10079000018B4AAC24648E1590003, offsetMsgId=C0A8A08300002A9F000000000000E9B6, messageQueue=MessageQueue [topic=TopicTest, brokerName=localhost.localdomain, queueId=8], queueOffset=45], body:{description=短信提示:取300,賬戶餘額700, userName=張三, userId=1000}
SendResult [sendStatus=SEND_OK, msgId=24098A1E7A3ECE402C3D4AADC1A10079000018B4AAC24648E15C0004, offsetMsgId=C0A8A08300002A9F000000000000EAE2, messageQueue=MessageQueue [topic=TopicTest, brokerName=localhost.localdomain, queueId=8], queueOffset=46], body:{description=銀行存錢,存400, userName=張三, userId=1000}
SendResult [sendStatus=SEND_OK, msgId=24098A1E7A3ECE402C3D4AADC1A10079000018B4AAC24648E15F0005, offsetMsgId=C0A8A08300002A9F000000000000EBFE, messageQueue=MessageQueue [topic=TopicTest, brokerName=localhost.localdomain, queueId=8], queueOffset=47], body:{description=短信提示:存款400,賬戶餘額1100, userName=張三, userId=1000}
SendResult [sendStatus=SEND_OK, msgId=24098A1E7A3ECE402C3D4AADC1A10079000018B4AAC24648E1620006, offsetMsgId=C0A8A08300002A9F000000000000ED2C, messageQueue=MessageQueue [topic=TopicTest, brokerName=localhost.localdomain, queueId=9], queueOffset=45], body:{description=銀行存錢,存600, userName=李四, userId=1001}
SendResult [sendStatus=SEND_OK, msgId=24098A1E7A3ECE402C3D4AADC1A10079000018B4AAC24648E1650007, offsetMsgId=C0A8A08300002A9F000000000000EE48, messageQueue=MessageQueue [topic=TopicTest, brokerName=localhost.localdomain, queueId=9], queueOffset=46], body:{description=短信提示:存款600,賬戶餘額600, userName=李四, userId=1001}

消費者代碼:

/**
 * @author lucifer
 * @date 2020/4/14 14:50
 * @description 順序消息 -消費者
 */
public class OrderedConsumer {

    public static final String NAME_SERVER_ADDR = "192.168.160.131:9876";

    public static void main(String[] args) throws MQClientException {
        //1.創建消費者
        DefaultMQPushConsumer defaultMQPushConsumer = new DefaultMQPushConsumer("GROUP_TEST");
        //2.設置NameServer地址
        defaultMQPushConsumer.setNamesrvAddr(NAME_SERVER_ADDR);
        //設置Consumer第一次啓動是從隊列頭部開始消費還是隊列尾部開始消費
        // 如果不是第一次啓動,那麼按照上次消費的位置繼續消費
        defaultMQPushConsumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
        //3.訂閱對應的主題和Tag
        defaultMQPushConsumer.subscribe("TopicTest", "TagA || TagB || TagC");
        defaultMQPushConsumer.setMaxReconsumeTimes(-1);
        //順序消費消息
        defaultMQPushConsumer.registerMessageListener(new MessageListenerOrderly() {
            @Override
            public ConsumeOrderlyStatus consumeMessage(List<MessageExt> list,
                                                       ConsumeOrderlyContext consumeOrderlyContext) {
                consumeOrderlyContext.setAutoCommit(true);
                doBiz(list.get(0));

                return ConsumeOrderlyStatus.SUCCESS;
            }
        });
        // 5. 啓動消費者(必須在註冊完消息監聽器後啓動,否則會報錯)
        defaultMQPushConsumer.start();

        System.out.println("已啓動消費者");

    }

    /**
     * 模擬處理業務
     *
     * @param message
     */
    public static void doBiz(Message message) {
        try {
            System.out.printf("線程:%-25s 接收到新消息 %s --- %s %n", Thread.currentThread().getName(), message.getTags(), new String(message.getBody(), RemotingHelper.DEFAULT_CHARSET));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }
}

 啓動消費者,控制檯打印:

已啓動消費者
線程:ConsumeMessageThread_1    接收到新消息 TagA --- {description=銀行存錢,存1000, userName=張三, userId=1000} 
線程:ConsumeMessageThread_1    接收到新消息 TagB --- {description=短信提示:存1000,賬戶餘額1000, userName=張三, userId=1000} 
線程:ConsumeMessageThread_1    接收到新消息 TagC --- {description=銀行取錢,取300, userName=張三, userId=1000} 
線程:ConsumeMessageThread_1    接收到新消息 TagA --- {description=短信提示:取300,賬戶餘額700, userName=張三, userId=1000} 
線程:ConsumeMessageThread_1    接收到新消息 TagB --- {description=銀行存錢,存400, userName=張三, userId=1000} 
線程:ConsumeMessageThread_1    接收到新消息 TagC --- {description=短信提示:存款400,賬戶餘額1100, userName=張三, userId=1000} 
線程:ConsumeMessageThread_1    接收到新消息 TagA --- {description=銀行存錢,存600, userName=李四, userId=1001} 
線程:ConsumeMessageThread_1    接收到新消息 TagB --- {description=短信提示:存款600,賬戶餘額600, userName=李四, userId=1001} 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章