RocketMQ使用場景之可靠性優先Case

一、順序消息的實現

1.全局順序消息
要保證全局順序消息,需要先把 Topic 的讀寫隊列數設置爲 1,
然後 Producer 和 Consumer 的併發設置也要是1。
在發送端,要做到 把同一業務 ID 的消息發送到同一個 Message Queue
(使用 MessageQueueSelector類)

2.部分順序消息
在消費過程中,要做到從 同一個 Message Queue 讀取的消息不被併發處理
(使用 MessageListenerOrderly類)
具體實現方式:

在 MessageListenerOrderly 的實現中,爲每個 Consumer Queue 加個鎖,
消費每個消息前,需要先獲得這個消息對應的 Consumer Queue 所對應的鎖,
這樣保證了同一時間,同一個 Consumer Queue 的消息不被併發消 費,
但不同 Consumer Queue 的消息可以併發處理 。

具體參考代碼如下:

package org.apache.rocketmq.example.ordermessage;

import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
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.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.message.MessageExt;

public class Consumer {

    public static void main(String[] args) throws MQClientException {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_3");

        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);

        consumer.subscribe("TopicTest", "TagA || TagC || TagD");

        consumer.registerMessageListener(new MessageListenerOrderly() {
            AtomicLong consumeTimes = new AtomicLong(0);

            @Override
            public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
                context.setAutoCommit(false);
                System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs);
                this.consumeTimes.incrementAndGet();
                if ((this.consumeTimes.get() % 2) == 0) {
                    return ConsumeOrderlyStatus.SUCCESS;
                } else if ((this.consumeTimes.get() % 3) == 0) {
                    return ConsumeOrderlyStatus.ROLLBACK;
                } else if ((this.consumeTimes.get() % 4) == 0) {
                    return ConsumeOrderlyStatus.COMMIT;
                } else if ((this.consumeTimes.get() % 5) == 0) {
                    context.setSuspendCurrentQueueTimeMillis(3000);
                    return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT;
                }

                return ConsumeOrderlyStatus.SUCCESS;
            }
        });

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

}

二、消息重複問題

消息重複一般情況下不會發生,但是如果消息量大,網絡有波動,消息重複就是個大概率事件。 主要造成的原因是:
Producer有個setRetryTimesWhenSendFailed函數, 設置在同步方式下自動重試的次數,默認值是 2,這樣當第一次發送消息時, Broker端接收到了消息但是沒有正確返回發送成功的狀態,就造成了消息重複。

解決辦法:

  1. 保證消費邏輯的幕等性(多次調用和一次調用效果相同)
  2. 維護一個巳消費消息的記錄,消費前查詢這個消息是否被消費過 (只能在入庫時,手動寫代碼去重)

三、消息優先級

RocketMQ 是個先入先出的隊列,不支持消息級別或者 Topic 級別的優先級 ,只能通過間接方式。

情景1:
同一Topic下某一消息需要被及時處理,可另外開闢topic
情景2:

一個訂單處理系統,接收從 100家快遞門店過來的請求,把這些請求通過 Producer 寫人 RocketMQ ;
訂單處理程序通過 Consumer 從隊列裏讀取消 息並處理,每天最多處理 1 萬單 。 
如果這 100 個快遞門店中某幾個門店訂單量大增,
比如門店一接了個大客戶,一個上午就發出 2萬單消息請求,
這樣其他的 99 家門店可能被迫等待門店一的 2 萬單處理完,
也就是兩天後訂單才能被處理,顯然很不公平 。

解決:

創建一個 Topic, 設置 Topic 的 MessageQueue 數 量 超過 100 個,
Producer根據訂單的門店號,把每個門店的訂單寫人 一 個 MessageQueue。 
DefaultMQPushConsumer默認是採用循環的方式逐個讀取一個 Topic 的 所有 
MessageQueue,這樣如果某家門店訂單 量 大增,這家門店對應的 
MessageQueue 消息數增多,等待時間增長,但不會造成其他家門店等待時間增長。

情景3:

一個應用程序同時處理 TypeA、 TypeB、 TypeC 三類消息 。 
TypeA 處於第 一優先級,要確保只要有 TypeA消息,必須優先處理; 
TypeB處於第二優先級; 
TypeC 處於第三 優先級 。 

解決:

對這種要求,或者邏輯更復雜的要求,需要自己編碼實現優先級控制,
如果上述的三類消息在一個 Topic 裏,可以使 用 PullConsumer,
自主控制 Messag巳Queue 的遍歷,以及消息的讀取;

四、各種故障對消息隊列的影響

故障清單:

1) Broker正常關閉,啓動;
2) Broker異常 Crash,然後啓動;
3) OS Crash,重啓;
4 )機器斷電,但能馬上恢復供電;
5 )磁盤損壞;
6) CPU、 主板、內存等關鍵設備損壞 。

分析:

1 )情況 屬於可控的軟件 問題,內存中的數據不會丟失 。 
若重啓過程中有持續運行的 Consumer, Master機器出故障後, 
Consumer會自動重連到對應的 Slave 機器,不會有消息丟失和偏差 。 
當 Master 角色的機器 重啓 以 後, Consumer又會重新連接到 Master機器
若有持續運行的 Producer,一臺Master 出故障後,
Producer只能向 Topic下其他的 Master機器發送消息,
如果Producer採用同步發送方式,不會有消息丟失 。

234)情況屬於軟件故障,內存的數據可能丟失,所以刷盤策略不同,造成的影響也不同,
如果 Master、 Slave都配置成 SYNC_FLUSH,可以達到和第 1 種情況相同的效果 。

56 )情況屬於硬件故障 ,發生第 56 種情況的故障,原有機器的磁盤數據可能會丟失。 如果Master和Slave機器間配置成同步複製方式,某一臺機器發生 56 的故障,也可以達到消息不丟失的效果 。 機器間是異步複製,兩次 Sync間的消息會丟失。

常規思路:

總的來說,當設置成:
1 )多 Master,每個 Master 帶有 Slave; 
2 )主從之間設置成 SYNC_MASTER; 
3 ) Producer 用同步方式寫;
4 )刷盤策略設置成 SYNC FLUSH。
就可以消除單點依賴,即使某臺機器出現極端故障也不會丟消息 。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章