Apache RocketMQ分散式訊息傳遞和流資料平臺及大廠面試寶典v4.9.2

itxiaoshen發表於2021-12-03

概述

**本人部落格網站 **IT小神 www.itxiaoshen.com

定義

Apache RocketMQ官網地址 https://rocketmq.apache.org/ Latest release v4.9.2

Apache RocketMQ GitHub原始碼地址 https://github.com/apache/rocketmq

Apache RocketMQ™是一個分散式訊息傳遞和流媒體平臺、統一的訊息傳遞引擎,輕量級的資料處理平臺;具有低延遲、高效能和可靠性、萬億級容量和靈活的可伸縮性。

今天我們又來學習一個Apache頂級專案Apache RocketMQ,RocketMQ由國人阿里團隊採用Java語言開發和開源的,曾獲得2016、2018中國最受歡迎的開源軟體獎。RocketMQ憑藉其強大的儲存能力和強大的訊息索引能力,以及各種型別訊息和訊息的特性脫穎而出。Apache RocketMQ官網地址及其GitHub都提供非常詳細中文學習文件如Apache RocketMQ開發者指南等,學習起來可謂是非常之流暢、酸爽、so easy!讓我們通過官網和及其GitHub來深入學習這個與時俱進非常優秀網際網路主流的訊息中介軟體。

為何需要Apache RocketMQ?

阿里早期是基於ActiveMQ 5的分散式訊息傳遞中介軟體,隨著佇列和虛擬主題的增加ActiveMQ IO模組達到了瓶頸,當時也研討過Kafka但當時的Kafka不能滿足阿里的要求(特別是在低延遲和高可靠性方面),因此阿里決定自行研發一個訊息中介軟體,從傳統的釋出/訂閱場景到高容量的實時零損失容忍度事務系統,這就是RocketMQ誕生的原因。

RocketMQ vs. ActiveMQ vs. Kafka

下表展示了RocketMQ、ActiveMQ和Kafka(根據awesome-java的Apache最流行的訊息傳遞解決方案)之間的比較。根據個人經驗,如果不是大資料場景下如大資料日誌採集等場景外建議優先使用RocketMQ,效能和功能都有保障,當然需要用於雲原生領域還有Apache Pulsar雲原生分散式訊息和流平臺,這個在前面的文章也有較少。

image-20211129183527755

安裝部署

安裝說明

the latest release is 4.9.2

二進位制下載地址 https://dlcdn.apache.org/rocketmq/4.9.2/rocketmq-all-4.9.2-bin-release.zip

原始碼下載地址 https://dlcdn.apache.org/rocketmq/4.9.2/rocketmq-all-4.9.2-source-release.zip

Apache RocketMQ部署方式有單Master模式、多Master模式、多Master多slave模式、Dledger的叢集部署模式等,官網也提供額外的CLI Admin Tool和運維工具mqadmin。在二進位制包下conf目錄提供了兩主兩從非同步方式、兩主兩從同步方式、兩主無從、Dledger叢集的配置模板。

image-20211130113659514

網路部署特點

  • NameServer是一個幾乎無狀態節點,可叢集部署,節點之間無任何資訊同步。
  • Broker部署相對複雜,Broker分為Master與Slave,Master提供RW訪問,而Slave只接受讀訪問;一個Master可以對應多個Slave,但是一個Slave只能對應一個Master,Master與Slave 的對應關係通過指定相同的BrokerName,不同的BrokerId 來定義,BrokerId為0表示Master,非0表示Slave。Master也可以部署多個。每個Broker與NameServer叢集中的所有節點建立長連線,定時註冊Topic資訊到所有NameServer。 注意:當前RocketMQ版本在部署架構上支援一Master多Slave,但只有BrokerId=1的從伺服器才會參與訊息的讀負載。
  • Producer與NameServer叢集中的其中一個節點(隨機選擇)建立長連線,定期從NameServer獲取Topic路由資訊,並向提供Topic 服務的Master建立長連線,且定時向Master傳送心跳。Producer完全無狀態,可叢集部署。
  • Consumer與NameServer叢集中的其中一個節點(隨機選擇)建立長連線,定期從NameServer獲取Topic路由資訊,並向提供Topic服務的Master、Slave建立長連線,且定時向Master、Slave傳送心跳。Consumer既可以從Master訂閱訊息,也可以從Slave訂閱訊息,消費者在向Master拉取訊息時,Master伺服器會根據拉取偏移量與最大偏移量的距離(判斷是否讀老訊息,產生讀I/O),以及從伺服器是否可讀等因素建議下一次是從Master還是Slave拉取。

配置推薦

在部署RocketMQ叢集時,推薦的配置如下所示:

image-20211130172713508

部署方式說明

  • 單Master模式

    • 這種方式風險較大,一旦Broker重啟或者當機時,會導致整個服務不可用。不建議線上環境使用,可以用於本地測試。
  • 多Master模式

    • 一個叢集無Slave,全是Master,例如2個Master或者3個Master,這種模式的優缺點如下:
      • 優點:配置簡單,單個Master當機或重啟維護對應用無影響,在磁碟配置為RAID10時,即使機器當機不可恢復情況下,由於RAID10磁碟非常可靠,訊息也不會丟(非同步刷盤丟失少量訊息,同步刷盤一條不丟),效能最高;
      • 缺點:單臺機器當機期間,這臺機器上未被消費的訊息在機器恢復之前不可訂閱,訊息實時性會受到影響。
  • 多Master多Slave模式-非同步複製

    • 每個Master配置一個Slave,有多對Master-Slave,HA採用非同步複製方式,主備有短暫訊息延遲(毫秒級),這種模式的優缺點如下:
      • 優點:即使磁碟損壞,訊息丟失的非常少,且訊息實時性不會受影響,同時Master當機後,消費者仍然可以從Slave消費,而且此過程對應用透明,不需要人工干預,效能同多Master模式幾乎一樣;
      • 缺點:Master當機,磁碟損壞情況下會丟失少量訊息。
  • 多Master多Slave模式-同步雙寫

    • 每個Master配置一個Slave,有多對Master-Slave,HA採用同步雙寫方式,即只有主備都寫成功,才嚮應用返回成功,這種模式的優缺點如下:
      • 優點:資料與服務都無單點故障,Master當機情況下,訊息無延遲,服務可用性與資料可用性都非常高;
      • 缺點:效能比非同步複製模式略低(大約低10%左右),傳送單個訊息的RT會略高,且目前版本在主節點當機後,備機不能自動切換為主機。

單Master部署

單Master模式部署非常簡單,這種方式風險較大,一旦Broker重啟或者當機時,會導致整個服務不可用。不建議線上環境使用,可以用於本地測試。先啟動NameServer後啟動Broker。

#linux部署,解壓下載zip進入二級制加壓的根目錄
unzip rocketmq-all-4.9.2-bin-release.zip
cd rocketmq-4.9.2
#啟動NameServer
nohup sh bin/mqnamesrv &
#檢視NameServer執行日誌
tail -f ~/logs/rocketmqlogs/namesrv.log
#啟動Broker
nohup sh bin/mqbroker -n localhost:9876 &
#檢視Broker執行日誌
tail -f ~/logs/rocketmqlogs/broker.log 
#關閉Broker
sh bin/mqshutdown broker
#關閉NameServer
sh bin/mqshutdown namesrv

Dledger叢集部署

​ 多主多從模式有模板配置,根據不同配置拉起Broker即可,但是從上面我們知道在多主多從模式下是不支援自動容災切換功能,因此還不具備完全的高可用,我們這裡使用Dledger叢集部署實現自動容災切換;之前我們在ZooKeeper章節也瞭解到分散式一致性演算法,其實Dledger也是依賴Raft演算法實現選舉的功能。Dledger一個基於java庫用於構建高可用性、高耐用性、強一致性的提交,它可以作為分散式儲存系統的持久化層,如訊息傳遞、流、kv、db等。Dledger是已被證明可以應用於生產級別的產品。

​ NameServer需要先於Broker啟動,且如果在生產環境使用,為了保證高可用,建議一般規模的叢集啟動3個NameServer。我們本次準備3臺伺服器192.168.50.95(n0)、192.168.50.156(n1)、192.168.50.196(n2)。

cd rocketmq-4.9.2
#3臺伺服器啟動Name Server
nohup sh bin/mqnamesrv &
#驗證Name Server 是否啟動成功
tail -f ~/logs/rocketmqlogs/namesrv.log

image-20211202164734841

在conf\dledger參考broker-n0.conf資料建立檔名為broker.conf資料內容如下,其他兩臺和這個資料一樣,只需要修改dLegerSelfId為n1和n2即可。

vi conf/dledger/broker.conf

brokerClusterName = RaftCluster
brokerName=RaftNode00
listenPort=30911
namesrvAddr=192.168.50.95:9876;192.168.50.156:9876;192.168.50.196:9876
storePathRootDir=/home/commons/rocketmq-4.9.2/rmqstore/node00
storePathCommitLog=/home/commons/rocketmq-4.9.2/rmqstore/node00/commitlog
enableDLegerCommitLog=true
dLegerGroup=RaftNode00
dLegerPeers=n0-192.168.50.95:40911;n1-192.168.50.156:40911;n2-192.168.50.196:40911
## must be unique
dLegerSelfId=n0
sendMessageThreadPoolNums=16
#可以3臺分別先建立配置檔案路徑,非必要
mkdir /home/commons/rocketmq-4.9.2/rmqstore/node00
mkdir /home/commons/rocketmq-4.9.2/rmqstore/node00/commitlog
#3臺分別啟動broker
nohup sh bin/mqbroker -c conf/dledger/broker.conf &
#檢視Broker執行日誌
tail -f ~/logs/rocketmqlogs/broker.log

image-20211203110652137

通過 mqadmin 運維命令檢視叢集狀態,可指定任意一臺Name Server

sh bin/mqadmin clusterList -n 192.168.50.95:9876

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-PcYuzQb9-1638544959716)(http://www.itxiaoshen.com:3001/assets/1638501706653H0QPMbWB.png)]

BID 為 0 的表示 Master,其餘都是 Follower,從當前看192.168.50.156為Master,我們進行容災切換測試,停掉192.168.50.156上的Broker程式,等待約 10s 左右,用 clusterList 命令再次檢視叢集,就會發現 Leader 切換到另一個節點192.168.50.196上

image-20211203111848367

再次啟動192.168.50.156上的broker重新再加入叢集並作為叢集的Follower

image-20211203112043845

簡單收發訊息測試

#192.168.50.95上執行測試工具的生產者傳送訊息
export NAMESRV_ADDR="192.168.50.95:9876;192.168.50.156:9876;192.168.50.196:9876"
sh bin/tools.sh org.apache.rocketmq.example.quickstart.Producer

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-HFLt4ZFo-1638544959726)(http://www.itxiaoshen.com:3001/assets/1638502366597EjsQW4e8.png)]

#192.168.50.95上執行測試工具的消費者接收訊息
export NAMESRV_ADDR="192.168.50.95:9876;192.168.50.156:9876;192.168.50.196:9876"
sh bin/tools.sh org.apache.rocketmq.example.quickstart.Consumer

image-20211203113245569

Java示例

常用訊息樣例說明

image-20211203143211645

  • 簡單訊息(三種方式傳送訊息)

    • 可靠同步,使用的比較廣泛,比如:重要的訊息通知,簡訊通知。
    • 可靠非同步,通常用在對響應時間敏感的業務場景,即傳送端不能容忍長時間地等待Broker的響應。
    • 單向傳輸,用在不特別關心傳送結果的場景,例如日誌傳送。
  • 順序訊息

    • RocketMQ使用FIFO順序提供有序訊息,RocketMQ可以嚴格的保證訊息有序,可以分為分割槽有序或者全域性有序。
    • 比如用訂單場景,一個訂單的順序流程是:建立、付款、推送、完成。訂單號相同的訊息會被先後傳送到同一個佇列中,消費時,同一個OrderId獲取到的肯定是同一個佇列。
  • 廣播訊息

    • 向一個主題的所有訂閱者傳送訊息。
  • 延遲訊息

    • 延遲訊息與普通訊息的不同之處在於它們將在稍後提供的時間內被傳遞,比如電商裡提交了一個訂單就可以傳送一個延時訊息,1h後去檢查這個訂單的狀態,如果還是未付款就取消訂單釋放庫存。
  • 批量訊息

    • 批量傳送訊息可以提高傳送小訊息的效能。
    • 約束:同一批的訊息應該有:相同的主題,相同的waitStoreMsgOK,不支援延遲。
  • 過濾訊息

    • 在大多數情況下,TAG是一個簡單而有用的設計,其可以來選擇您想要的訊息。
    • 在RocketMQ定義的語法下可以使用SQL表示式篩選訊息,SQL特性可以通過傳送訊息時的屬性來進行計算。
    • 只有使用push模式的消費者才能用使用SQL92標準的sql語句。
  • Logappender日誌

    • RocketMQ日誌提供log4j、log4j2和logback日誌框架作為業務應用
  • OpenMessaging

    • 旨在建立訊息和流處理規範,以為金融、電子商務、物聯網和大資料領域提供通用框架及工業級指導方案。在分散式異構環境中,設計原則是面向雲、簡單、靈活和獨立於語言。符合這些規範將幫助企業方便的開發跨平臺和作業系統的異構訊息傳遞應用程式。提供了openmessaging-api 0.3.0-alpha的部分實現。
  • 事務訊息

    • 可以將其視為兩階段提交訊息實現,以確保分散式系統中的最終一致性。事務性訊息確保本地事務的執行和訊息的傳送能夠被原子地執行。

    • 限制約束

      • 事務訊息不支援延時訊息和批量訊息。
      • 為了避免單個訊息被檢查太多次而導致半佇列訊息累積,我們預設將單個訊息的檢查次數限制為 15 次,但是使用者可以通過 Broker 配置檔案的 transactionCheckMax引數來修改此限制。如果已經檢查某條訊息超過 N 次的話( N = transactionCheckMax ) 則 Broker 將丟棄此訊息,並在預設情況下同時列印錯誤日誌。使用者可以通過重寫 AbstractTransactionalMessageCheckListener 類來修改這個行為。
      • 事務訊息將在 Broker 配置檔案中的引數 transactionTimeout 這樣的特定時間長度之後被檢查。當傳送事務訊息時,使用者還可以通過設定使用者屬性 CHECK_IMMUNITY_TIME_IN_SECONDS 來改變這個限制,該引數優先於 transactionTimeout 引數。
      • 事務性訊息可能不止一次被檢查或消費。
      • 提交給使用者的目標主題訊息可能會失敗,目前這依日誌的記錄而定。它的高可用性通過 RocketMQ 本身的高可用性機制來保證,如果希望確保事務訊息不丟失、並且事務完整性得到保證,建議使用同步的雙重寫入機制。
      • 事務訊息的生產者 ID 不能與其他型別訊息的生產者 ID 共享。與其他型別的訊息不同,事務訊息允許反向查詢、MQ伺服器能通過它們的生產者 ID 查詢到消費者。
    • 事務性訊息有三種狀態:

      (1) TransactionStatus。CommitTransaction:提交事務,它意味著允許使用者使用此訊息。

      (2) TransactionStatus。rollback transaction:回滾事務,它意味著訊息將被刪除並且不允許使用。

      (3) TransactionStatus。未知:中間狀態,這意味著MQ需要進行回查以確定狀態。

簡單訊息示例程式碼

pom加入maven依賴

        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-client</artifactId>
            <version>4.9.2</version>
        </dependency>

可靠同步生產者實現程式碼

package com.itxs.rocketmq;

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

public class SyncProducer {
    public static void main(String[] args) throws Exception {
        //Instantiate with a producer group name.
        DefaultMQProducer producer = new
                DefaultMQProducer("default_group");
        // Specify name server addresses.
        producer.setNamesrvAddr("192.168.50.95:9876;192.168.50.156:9876;192.168.50.196:9876");
        //Launch the instance.
        producer.start();
        for (int i = 0; i < 10; i++) {
            //Create a message instance, specifying topic, tag and message body.
            Message msg = new Message("DefaultTopic" /* Topic */,
                    "TagA" /* Tag */,
                    ("Hello RocketMQ " +
                            i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
            );
            //Call send message to deliver message to one of brokers.
            SendResult sendResult = producer.send(msg);
            System.out.printf("%s%n", sendResult);
        }
        //Shut down once the producer instance is not longer in use.
        producer.shutdown();
    }
}

可靠非同步生產者實現程式碼

package com.itxs.rocketmq;

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

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class AsyncProducer {
    public static void main(String[] args) throws Exception {
        //Instantiate with a producer group name.
        DefaultMQProducer producer = new DefaultMQProducer("default_group");
        // Specify name server addresses.
        producer.setNamesrvAddr("192.168.50.95:9876;192.168.50.156:9876;192.168.50.196:9876");
        //Launch the instance.
        producer.start();
        producer.setRetryTimesWhenSendAsyncFailed(0);

        int messageCount = 10;
        final CountDownLatch countDownLatch = new CountDownLatch(messageCount);
        for (int i = 0; i < messageCount; i++) {
            try {
                final int index = i;
                Message msg = new Message("DefaultTopic",
                        "TagA",
                        "OrderID888888",
                        "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET));
                producer.send(msg, new SendCallback() {
                    @Override
                    public void onSuccess(SendResult sendResult) {
                        countDownLatch.countDown();
                        System.out.printf("%-10d OK %s %n", index, sendResult.getMsgId());
                    }

                    @Override
                    public void onException(Throwable e) {
                        countDownLatch.countDown();
                        System.out.printf("%-10d Exception %s %n", index, e);
                        e.printStackTrace();
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        countDownLatch.await(5, TimeUnit.SECONDS);
        producer.shutdown();
    }
}

單向傳輸生產者實現程式碼

package com.itxs.rocketmq;

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

public class OnewayProducer {
    public static void main(String[] args) throws Exception{
        //Instantiate with a producer group name.
        DefaultMQProducer producer = new DefaultMQProducer("default_group");
        // Specify name server addresses.
        producer.setNamesrvAddr("192.168.50.95:9876;192.168.50.156:9876;192.168.50.196:9876");
        //Launch the instance.
        producer.start();
        for (int i = 0; i < 10; i++) {
            //Create a message instance, specifying topic, tag and message body.
            Message msg = new Message("DefaultTopic" /* Topic */,
                    "TagA" /* Tag */,
                    ("Hello RocketMQ " +
                            i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
            );
            //Call send message to deliver message to one of brokers.
            producer.sendOneway(msg);
        }
        //Wait for sending to complete
        Thread.sleep(5000);
        producer.shutdown();
    }
}

消費者實現程式碼

package com.itxs.rocketmq;

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.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;

import java.util.List;

public class Consumer {
    public static void main(String[] args) throws InterruptedException, MQClientException {

        // Instantiate with specified consumer group name.
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("default_group");

        // Specify name server addresses.
        consumer.setNamesrvAddr("192.168.50.95:9876;192.168.50.156:9876;192.168.50.196:9876");

        // Subscribe one more more topics to consume.
        consumer.subscribe("DefaultTopic", "*");
        // Register callback to execute on arrival of messages fetched from brokers.
        consumer.registerMessageListener(new MessageListenerConcurrently() {

            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
                                                            ConsumeConcurrentlyContext context) {
                System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs);
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });

        //Launch the consumer instance.
        consumer.start();

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

可靠同步生產者傳送訊息

image-20211203162944466

消費者消費訊息

image-20211203163116316

其他訊息示例可以參考官網的樣例使用即可

面試題

說說RocketMQ架構和組成?

image-20211130104408036

從Apache RocketMQ官網架構圖看可知道其由四個大部分組成,分別為名稱伺服器叢集、Broker叢集、生產者叢集和消費者叢集;它們中的每一個都可以水平擴充套件而不存在單一的故障點。

  • NameServer Cluster(命名伺服器叢集):名稱伺服器提供輕量級的服務發現和路由。每個Name Server記錄完整的路由資訊,提供相應的讀寫服務,支援快速的儲存擴充套件。我們知道Kafka是依賴ZooKeeper來實現服務發現和路由的。

    • Broker管理,NameServer接受來自Broker叢集的註冊,並提供心跳機制來檢查代理是否處於活動狀態。
    • 路由管理,每個NameServer將儲存關於代理叢集的全部路由資訊和用於客戶端查詢的佇列資訊。
    • Producer和Conumser通過NameServer就可以知道整個Broker叢集的路由資訊,從而進行訊息的投遞和消費。
    • NameServer通常也是叢集的方式部署,各例項間相互不進行資訊通訊。Broker是向每一臺NameServer註冊自己的路由資訊,所以每一個NameServer例項上面都儲存一份完整的路由資訊。當某個NameServer因某種原因下線了,Broker仍然可以向其它NameServer同步其路由資訊,Producer,Consumer仍然可以動態感知Broker的路由的資訊。
    • RocketMQ客戶端(生產者/消費者)將從NameServer查詢佇列路由資訊,客戶端可以通過多種方式找到NameServer的地址,下面列出幾種
      • 程式設計方式,如producer.setNamesrvAddr("ip:port")。
      • Java選項,使用rocketmq.namesrv.addr。
      • 環境變數使用NAMESRV_ADDR。
      • HTTP Endpoint。
  • Broker Cluster(代理叢集):Broker是作為RocketMQ最核心訊息Server,Broker通過提供輕量級的TOPIC和QUEUE機制來負責訊息儲存。它們支援Push和Pull模型,包含容錯機制(2副本或3副本),並提供強大的填充峰值和按原始時間順序累積數千億條訊息的能力。此外,Broker提供災難恢復、豐富的度量統計資訊和警報機制,這些都是傳統訊息中介軟體系統所缺乏的;代理伺服器負責訊息儲存和傳遞、訊息查詢、HA保證等,Broker伺服器有幾個重要的子模組:

    • 遠端模組,Broker的入口,處理來自客戶機的請求。
    • 客戶端管理器,管理客戶端(生產者/消費者)並維護消費者的主題訂閱。
    • 儲存服務,提供簡單的api在物理磁碟中儲存或查詢訊息。
    • HA服務,在主Broker和從Broker之間提供資料同步功能。
    • 索引服務,根據指定的鍵為訊息構建索引,並提供快速的訊息查詢。

    image-20211130104900093

  • Producer Cluster(生產者叢集):生產者支援分散式部署;分散式生產者通過多種負載均衡模式向Broker叢集傳送訊息;傳送過程支援快速失敗和低延遲。

  • Consumer Cluster(消費者叢集):消費者也支援Push和Pull模型中的分散式部署;它還支援叢集使用和訊息廣播;它提供了實時訊息訂閱機制,可以滿足大多數使用者的需求。

說說RocketMQ核心概念?

image-20211130173457044

Broker 在實際部署過程中對應一臺伺服器,每個 Broker 可以儲存多個Topic的訊息,每個Topic的訊息也可以分片儲存於不同的 Broker。Message Queue 用於儲存訊息的實體地址,每個Topic中的訊息地址儲存於多個 Message Queue 中,ConsumerGroup 由多個Consumer 例項構成。

  • 訊息模型

    • Clustering:叢集消費模式下,相同Consumer Group的每個Consumer例項平均分攤訊息。
    • Broadcasting:廣播消費模式下,相同Consumer Group的每個Consumer例項都接收全量的訊息。
  • 生產者組:同一類Producer的集合,這類Producer傳送同一類訊息且傳送邏輯一致。如果傳送的是事務訊息且原始生產者在傳送之後崩潰,則Broker伺服器會聯絡同一生產者組的其他生產者例項以提交或回溯消費。

    • Producer(生產者):負責生產訊息,一般由業務系統負責生產訊息。一個訊息生產者會把業務應用系統裡產生的訊息傳送到broker伺服器。RocketMQ提供多種傳送方式,同步傳送、非同步傳送、順序傳送、單向傳送。同步和非同步方式均需要Broker返回確認資訊,單向傳送不需要。
  • 消費者組:同一類Consumer的集合,這類Consumer通常消費同一類訊息且消費邏輯一致。消費者組使得在訊息消費方面,實現負載均衡和容錯的目標變得非常容易。要注意的是,消費者組的消費者例項必須訂閱完全相同的Topic。RocketMQ 支援兩種訊息模式:叢集消費(Clustering)和廣播消費(Broadcasting)。

    • Consumer (消費者):負責消費訊息,一般是後臺系統負責非同步消費。一個訊息消費者會從Broker伺服器拉取訊息、並將其提供給應用程式。從使用者應用的角度而言提供了兩種消費形式:拉取式消費、推動式消費。
      • Pull:主動呼叫Consumer的拉訊息方法從Broker伺服器拉訊息、主動權由應用控制。一旦獲取了批量訊息,應用就會啟動消費過程。拉取型消費者主動從broker中拉取訊息消費,只要拉取到訊息,就會啟動消費過程,稱為主動型消費。
      • Push:Broker收到資料後會主動推送給消費端,該消費模式一般實時性較高。推送型消費者就是要註冊訊息的監聽器,監聽器是要使用者自行實現的。當訊息達到broker伺服器後,會觸發監聽器拉取訊息,然後啟動消費過程。但是從實際上看還是從broker中拉取訊息,稱為被動消費型。
      • push:消費端慢的話導致消費端緩衝區溢位。
      • pull:考慮拉的頻率,可能導致很多無效請求的RPC開銷影響整體網路效能。
  • Broker Server :訊息中轉角色,負責儲存訊息、轉發訊息。代理伺服器在RocketMQ系統中負責接收從生產者傳送來的訊息並儲存、同時為消費者的拉取請求作準備。代理伺服器也儲存訊息相關的後設資料,包括消費者組、消費進度偏移和主題和佇列訊息等。

  • TOPIC:主題,表示一類訊息的集合,每個主題包含若干條訊息,每條訊息只能屬於一個主題,是RocketMQ進行訊息訂閱的基本單位。生產者在其中傳遞訊息,消費者在其中提取訊息。一個Topic可能有0個、一個或多個生產者向它傳送訊息;從消費者的角度來看一個主題可以由零個、一個或多個消費者群體訂閱。類似地,一個消費者組可以訂閱一個或多個主題,只要該組的例項保持訂閱一致。

    • message queue:訊息佇列,一個Topic可以劃分成多個訊息佇列。Topic只是個邏輯上的概念,訊息佇列是訊息的物理管理單位,當傳送訊息的時候,Broker會輪詢包含該Topic的所有訊息佇列,然後將訊息發出去。有了訊息佇列,可以使得訊息的儲存可以分散式叢集化,具有了水平的擴充套件能力。
    • message:訊息系統所傳輸資訊的物理載體,生產和消費資料的最小單位,每條訊息必須屬於一個主題。RocketMQ中每個訊息擁有唯一的Message ID,且可以攜帶具有業務標識的Key。系統提供了通過Message ID和Key查詢訊息的功能。
      • message order:當使用DefaultMQPushConsumer時,可以決定有序或併發地使用訊息.
        • Orderly:有序地使用訊息意味著對於每個訊息佇列,訊息的使用順序與生產者傳送訊息的順序相同。如果您正在處理全域性順序是強制性的場景,請確保您使用的Topic只有一個訊息佇列;消費者通過同一個訊息佇列( Topic 分割槽,稱作 Message Queue) 收到的訊息是有順序的,不同訊息佇列收到的訊息則可能是無順序的。如果指定了有序消費,則訊息消費的最大併發性是消費組訂閱的訊息佇列的數量。
        • Concurrently:當併發地使用訊息時,訊息使用的最大併發性僅受為每個客戶端指定的執行緒池的限制;在此模式下不再保證訊息順序。
      • 嚴格順序訊息模式下,消費者收到的所有訊息均是有順序的。
    • tag:為訊息設定的標誌,用於同一主題下區分不同型別的訊息。來自同一業務單元的訊息,可以根據不同業務目的在同一主題下設定不同標籤。標籤能夠有效地保持程式碼的清晰度和連貫性,並優化RocketMQ提供的查詢系統。消費者可以根據Tag實現對不同子主題的不同消費邏輯,實現更好的擴充套件性。
    • offset:是指訊息佇列中的offset,可以認為就是下標,訊息佇列可看做陣列。offset是java long型,64位,理論上100年不會溢位,所以可以認為訊息佇列是一個長度無限的資料結構。
    • RocketMQ支援按照下面兩種維度(“按照Message Id查詢訊息”、“按照Message Key查詢訊息”)進行訊息查詢。

    image-20211203190926028

RocketMQ叢集的工作流程?

  • 啟動NameServer,NameServer起來後監聽埠,等待Broker、Producer、Consumer連上來,相當於一個路由控制中心。
  • Broker啟動,跟所有的NameServer保持長連線,定時傳送心跳包。心跳包中包含當前Broker資訊(IP+埠等)以及儲存所有Topic資訊。註冊成功後,NameServer叢集中就有Topic跟Broker的對映關係。
  • 收發訊息前,先建立Topic,建立Topic時需要指定該Topic要儲存在哪些Broker上,也可以在傳送訊息時自動建立Topic。
  • Producer傳送訊息,啟動時先跟NameServer叢集中的其中一臺建立長連線,並從NameServer中獲取當前傳送的Topic存在哪些Broker上,輪詢從佇列列表中選擇一個佇列,然後與佇列所在的Broker建立長連線從而向Broker發訊息。
  • Consumer跟Producer類似,跟其中一臺NameServer建立長連線,獲取當前訂閱Topic存在哪些Broker上,然後直接跟Broker建立連線通道,開始消費訊息。

RocketMQ訊息儲存設計?

RocketMQ的設計理念很大程度借鑑了kafka,RocketMQ訊息儲存是整個系統的核心,直接決定著吞吐效能和高可用性;RocketMQ儲存訊息是直接操作檔案,藉助java NIO的力量,使得I/O效能十分高。當訊息來的時候,順序寫入CommitLog。為了Consumer消費訊息的時候,能夠方便的根據topic查詢訊息,在CommitLog的基礎上衍生出了ConsumerQueue檔案,存放了某topic的訊息在CommitLog中的偏移位置。此外為了支援根據訊息key查詢訊息,RocketMQ的強大的支援訊息索引的特性靠的就是indexFile索引檔案。

image-20211203231117352

image-20211203223650074

  • CommitLog:訊息主體以及後設資料的儲存主體,儲存Producer端寫入的訊息主體內容,訊息內容不是定長的。單個檔案大小預設1G, 檔名長度為20位,左邊補零,剩餘為起始偏移量,比如00000000000000000000代表了第一個檔案,起始偏移量為0,檔案大小為1G=1073741824;當第一個檔案寫滿了,第二個檔案為00000000001073741824,起始偏移量為1073741824,以此類推。
    • CommitLog檔案的最大的一個特點就是訊息順序寫入日誌檔案,當檔案滿了,寫入下一個檔案;隨機讀寫,關於commitLog的檔案的落盤有兩種,一種是同步刷盤,一種是非同步刷盤,可通過 flushDiskType 進行配置。
    • CommitLog除了訊息本身,它記錄了訊息的方方面面的資訊,通過一條CommitLog可以還原出很多東西。例如訊息是何時、由哪個producer傳送的,被髮送到了哪個訊息佇列,屬於哪個topic,有哪些屬性等等。RokcetMQ儲存的訊息其實儲存的就是這個CommitLog記錄;可以將CommitLog記錄等同於訊息,而CommitLog指儲存訊息的檔案。
    • CommitLog類屬性很多,但是最重要的是mappedFileQueue屬性。訊息最終儲存在CommitLog裡,實際上CommitLog是一個邏輯上的概念。真正的檔案是一個個MappedFile,然後組成了mappedFileQueue。一個MappedFile最多能存放1G的CommitLog,這個大小在MessageStoreConfi類裡面定義了的。
    • MappedFile 中WriteBuffer使用的是堆外記憶體,MappedByteBuffer是直接將檔案對映到記憶體中,兩者的使用是互斥的。如果啟用了臨時緩衝池(預設不啟用),那麼就會使用WriteBuffer寫CommitLog,否則就是MappedByteBuffer寫CommitLog。
  • ConsumeQueue:訊息消費佇列,引入的目的主要是提高訊息消費的效能,由於RocketMQ是基於主題topic的訂閱模式,訊息消費是針對主題進行的,如果要遍歷commitlog檔案中根據topic檢索訊息是非常低效的。Consumer即可根據ConsumeQueue來查詢待消費的訊息。其中,ConsumeQueue(邏輯消費佇列)作為消費訊息的索引,儲存了指定Topic下的佇列訊息在CommitLog中的起始物理偏移量offset,訊息大小size和訊息Tag的HashCode值。consumequeue檔案可以看成是基於topic的commitlog索引檔案,故consumequeue資料夾的組織方式如下:topic/queue/file三層組織結構,具體儲存路徑為:$HOME/store/consumequeue/{topic}/{queueId}/{fileName}。同樣consumequeue檔案採取定長設計,每一個條目共20個位元組,分別為8位元組的commitlog物理偏移量、4位元組的訊息長度、8位元組tag hashcode,單個檔案由30W個條目組成,可以像陣列一樣隨機訪問每一個條目,每個ConsumeQueue檔案大小約5.72M;
  • IndexFile:IndexFile(索引檔案)提供了一種可以通過key或時間區間來查詢訊息的方法。Index檔案的儲存位置是:\(HOME \store\index\){fileName},檔名fileName是以建立時的時間戳命名的,固定的單個IndexFile檔案大小約為400M,一個IndexFile可以儲存 2000W個索引,IndexFile的底層儲存設計為在檔案系統中實現HashMap結構,故RocketMQ的索引檔案其底層實現為hash索引。

image-20211203225238579

在上面的RocketMQ的訊息儲存整體架構圖中可以看出,RocketMQ採用的是混合型的儲存結構,即為Broker單個例項下所有的佇列共用一個日誌資料檔案(即為CommitLog)來儲存。RocketMQ的混合型儲存結構(多個Topic的訊息實體內容都儲存於一個CommitLog中)針對Producer和Consumer分別採用了資料和索引部分相分離的儲存結構,Producer傳送訊息至Broker端,然後Broker端使用同步或者非同步的方式對訊息刷盤持久化,儲存至CommitLog中。只要訊息被刷盤持久化至磁碟檔案CommitLog中,那麼Producer傳送的訊息就不會丟失。正因為如此,Consumer也就肯定有機會去消費這條訊息。當無法拉取到訊息後,可以等下一次訊息拉取,同時服務端也支援長輪詢模式,如果一個訊息拉取請求未拉取到訊息,Broker允許等待30s的時間,只要這段時間內有新訊息到達,將直接返回給消費端。這裡,RocketMQ的具體做法是,使用Broker端的後臺服務執行緒—ReputMessageService不停地分發請求並非同步構建ConsumeQueue(邏輯消費佇列)和IndexFile(索引檔案)資料。

說說RocketMQ儲存底層實現?

  • MappedByteBuffer
    • RocketMQ主要通過MappedByteBuffer對檔案進行讀寫操作。其中,利用了NIO中的FileChannel模型將磁碟上的物理檔案直接對映到使用者態的記憶體地址中(這種Mmap的方式減少了傳統IO將磁碟檔案資料在作業系統核心地址空間的緩衝區和使用者應用程式地址空間的緩衝區之間來回進行拷貝的效能開銷),將對檔案的操作轉化為直接對記憶體地址進行操作,從而極大地提高了檔案的讀寫效率(正因為需要使用記憶體對映機制,故RocketMQ的檔案儲存都使用定長結構來儲存,方便一次將整個檔案對映至記憶體)。
  • PageCache
    • 是OS對檔案的快取,用於加速對檔案的讀寫。一般來說,程式對檔案進行順序讀寫的速度幾乎接近於記憶體的讀寫速度,主要原因就是由於OS使用PageCache機制對讀寫訪問操作進行了效能優化,將一部分的記憶體用作PageCache。對於資料的寫入,OS會先寫入至Cache內,隨後通過非同步的方式由pdflush核心執行緒將Cache內的資料刷盤至物理磁碟上。對於資料的讀取,如果一次讀取檔案時出現未命中PageCache的情況,OS從物理磁碟上訪問讀取檔案的同時,會順序對其他相鄰塊的資料檔案進行預讀取。
    • 在RocketMQ中,ConsumeQueue邏輯消費佇列儲存的資料較少,並且是順序讀取,在page cache機制的預讀取作用下,Consume Queue檔案的讀效能幾乎接近讀記憶體,即使在有訊息堆積情況下也不會影響效能。而對於CommitLog訊息儲存的日誌資料檔案來說,讀取訊息內容時候會產生較多的隨機訪問讀取,嚴重影響效能。如果選擇合適的系統IO排程演算法,比如設定排程演算法為“Deadline”(此時塊儲存採用SSD的話),隨機讀的效能也會有所提升。

說說RocketMQ檔案儲存模型層次結構?

image-20211203225815118

  • RocketMQ業務處理器層
    • Broker端對訊息進行讀取和寫入的業務邏輯入口,這一層主要包含了業務邏輯相關處理操作(根據解析RemotingCommand中的RequestCode來區分具體的業務操作型別,進而執行不同的業務處理流程),比如前置的檢查和校驗步驟、構造MessageExtBrokerInner物件、decode反序列化、構造Response返回物件等。
  • RocketMQ資料儲存元件層
    • 該層主要是RocketMQ的儲存核心類—DefaultMessageStore,其為RocketMQ訊息資料檔案的訪問入口,通過該類的“putMessage()”和“getMessage()”方法完成對CommitLog訊息儲存的日誌資料檔案進行讀寫操作(具體的讀寫訪問操作還是依賴下一層中CommitLog物件模型提供的方法);另外,在該元件初始化時候,還會啟動很多儲存相關的後臺服務執行緒,包括AllocateMappedFileService(MappedFile預分配服務執行緒)、ReputMessageService(回放儲存訊息服務執行緒)、HAService(Broker主從同步高可用服務執行緒)、StoreStatsService(訊息儲存統計服務執行緒)、IndexService(索引檔案服務執行緒)等。
  • RocketMQ儲存邏輯物件層
    • 該層主要包含了RocketMQ資料檔案儲存直接相關的三個模型類IndexFile、ConsumerQueue和CommitLog。IndexFile為索引資料檔案提供訪問服務,ConsumerQueue為邏輯訊息佇列提供訪問服務,CommitLog則為訊息儲存的日誌資料檔案提供訪問服務。這三個模型類也是構成了RocketMQ儲存層的整體結構(對於這三個模型類的深入分析將放在後續篇幅中)。
  • 封裝的檔案記憶體對映層
    • RocketMQ主要採用JDK NIO中的MappedByteBuffer和FileChannel兩種方式完成資料檔案的讀寫。其中,採用MappedByteBuffer這種記憶體對映磁碟檔案的方式完成對大檔案的讀寫,在RocketMQ中將該類封裝成MappedFile類。這裡限制的問題在上面已經講過;對於每類大檔案(IndexFile/ConsumerQueue/CommitLog),在儲存時分隔成多個固定大小的檔案(單個IndexFile檔案大小約為400M、單個ConsumerQueue檔案大小約5.72M、單個CommitLog檔案大小為1G),其中每個分隔檔案的檔名為前面所有檔案的位元組大小數+1,即為檔案的起始偏移量,從而實現了整個大檔案的串聯。這裡,每一種類的單個檔案均由MappedFile類提供讀寫操作服務(其中,MappedFile類提供了順序寫/隨機讀、記憶體資料刷盤、記憶體清理等和檔案相關的服務)。
  • 磁碟儲存層
    • 主要指的是部署RocketMQ伺服器所用的磁碟。這裡,需要考慮不同磁碟型別(如SSD或者普通的HDD)特性以及磁碟的效能引數(如IOPS、吞吐量和訪問時延等指標)對順序寫/隨機讀操作帶來的影響。

如何保證 RocketMQ 不丟失訊息?

一條訊息從生產到被消費,將會經歷生產階段、儲存階段、消費階段三個階段。

  • 生產階段,Producer 新建訊息,然後通過網路將訊息投遞給 MQ Broker。

    • 生產者(Producer) 通過網路傳送訊息給 Broker,當 Broker 收到之後,將會返回確認響應資訊給 Producer;所以生產者只要接收到返回的確認響應,就代表訊息在生產階段未丟失。

    • 返回訊息方式可以是同步也可以是非同步,但不管是同步還是非同步的方式,都會碰到網路問題導致傳送失敗的情況。針對這種情況,我們可以設定合理的重試次數,當出現網路問題,可以自動重試。

      // 同步傳送訊息重試次數,預設為 2
      mqProducer.setRetryTimesWhenSendFailed(3);
      // 非同步傳送訊息重試次數,預設為 2
      mqProducer.setRetryTimesWhenSendAsyncFailed(3);
      
  • 儲存階段,訊息將會儲存在 Broker 端磁碟中。

    • 預設情況下,訊息只要到了 Broker 端,將會優先儲存到記憶體中,然後立刻返回確認響應給生產者。隨後 Broker 定期批量的將一組訊息從記憶體非同步刷入磁碟。這種方式減少 I/O 次數,可以取得更好的效能,但是如果發生機器掉電,異常當機等情況,訊息還未及時刷入磁碟,就會出現丟失訊息的情況。

    • 若想保證 Broker 端不丟訊息,保證訊息的可靠性,我們需要將訊息儲存機制修改為同步刷盤方式,即訊息儲存磁碟成功,才會返回響應。若 Broker 未在同步刷盤時間內(預設為 5s)完成刷盤,將會返回 SendStatus.FLUSH_DISK_TIMEOUT 狀態給生產者。

    • 叢集部署:為了保證可用性,Broker 通常採用一主(master)多從(slave)部署方式。為了保證訊息不丟失,訊息還需要複製到 slave 節點。預設方式下,訊息寫入 master 成功,就可以返回確認響應給生產者,接著訊息將會非同步複製到 slave 節點。此時若 master 突然當機且不可恢復,那麼還未複製到 slave 的訊息將會丟失。為了進一步提高訊息的可靠性,我們可以採用同步的複製方式,master 節點將會同步等待 slave節點複製完成,才會返回確認響應。提高訊息的高可靠性,但是會降低效能,生產實踐中需要綜合選擇。

      ## master 節點配置
      flushDiskType = SYNC_FLUSH
      brokerRole=SYNC_MASTER
      ## slave 節點配置
      brokerRole=slave
      flushDiskType = SYNC_FLUSH
      
  • 消費階段, Consumer 將會從 Broker 拉取訊息。

    • 消費者從 broker 拉取訊息,然後執行相應的業務邏輯。一旦執行成功,將會返回 ConsumeConcurrentlyStatus.CONSUME_SUCCESS 狀態給 Broker。

      如果 Broker 未收到消費確認響應或收到其他狀態,消費者下次還會再次拉取到該條訊息,進行重試。這樣的方式有效避免了消費者消費過程發生異常,或者訊息在網路傳輸中丟失的情況。

說說RocketMQ同步非同步複製和刷盤?

  • 複製
    • 為了確保成功釋出的訊息不會丟失,RocketMQ提供了同步和非同步兩種複製模式獲得更強的永續性和更高的可用性。
    • 同步Broker要等到提交日誌被複制到從伺服器後才進行確認。
    • 相反,非同步Broker在主伺服器上處理訊息後立即返回。
  • 刷盤
    • 同步刷盤:在訊息達到Broker的記憶體之後,必須刷到commitLog日誌檔案中才算成功,然後返回Producer資料已經傳送成功。
    • 非同步刷盤:非同步刷盤是指訊息達到Broker記憶體後就返回Producer資料已經傳送成功,會喚醒一個執行緒去將資料持久化到CommitLog日誌檔案中。
      優缺點分析:同步刷盤保證了訊息不丟失,但是響應時間相對非同步刷盤要多出10%左右,適用於對訊息可靠性要求比較高的場景。非同步刷盤的吞吐量比較高,RT小,但是如果broker斷電了記憶體中的部分資料會丟失,適用於對吞吐量要求比較高的場景。

說說RocketMQ負載均衡?

RocketMQ中的負載均衡都在Client端完成,具體來說的話,主要可以分為Producer端傳送訊息時候的負載均衡和Consumer端訂閱訊息的負載均衡。

image-20211203183123762

nameServer儲存著Topic的路由資訊,路由記錄了broker叢集節點的通訊地址,broker的名稱以及讀寫佇列數量等資訊。寫佇列writeQueue表示生產者可以寫入的佇列數,如果不做配置預設為4,也就是queueId是0,1,2,3.broker收到訊息後根據queueId生成訊息佇列,生產者負載均衡的過程的實質就是選擇broker叢集和queueId的過程。讀佇列readQueue表示broker中可以供消費者讀取資訊的佇列個數,預設也是4個,也就是queueId也是0,1,2,3。消費者拿到路由資訊後會選擇queueId,從對應的broker中讀取資料消費

  • Producer的負載均衡
    • Producer端在傳送訊息的時候,會先根據Topic找到指定的TopicPublishInfo,在獲取了TopicPublishInfo路由資訊後,RocketMQ的客戶端在預設方式下selectOneMessageQueue()方法會從TopicPublishInfo中的messageQueueList中選擇一個佇列(MessageQueue)進行傳送訊息。具體的容錯策略均在MQFaultStrategy這個類中定義。這裡有一個sendLatencyFaultEnable開關變數,如果開啟,在隨機遞增取模的基礎上,再過濾掉not available的Broker代理。所謂的"latencyFaultTolerance",是指對之前失敗的,按一定的時間做退避。例如,如果上次請求的latency超過550Lms,就退避3000Lms;超過1000L,就退避60000L;如果關閉,採用隨機遞增取模的方式選擇一個佇列(MessageQueue)來傳送訊息,latencyFaultTolerance機制是實現訊息傳送高可用的核心關鍵所在。簡單的說選擇的標準:儘量不選剛剛選過的broker,儘量不選傳送上條訊息延遲過高或沒有響應的broker,也就是找到一個可用的
  • Consumer的負載均衡
    • 將MessageQueue中的訊息佇列分配到消費者組裡的具體消費者;Consumer在啟動的時候會例項化RebalanceImpl,這個類負責消費端的負載均衡。在Consumer例項的啟動流程中的啟動MQClientInstance例項部分,會完成負載均衡服務執行緒—RebalanceService的啟動(每隔20s執行一次)。通過檢視原始碼可以發現,RebalanceService執行緒的run()方法最終呼叫的是RebalanceImpl類的rebalanceByTopic()方法,該方法是實現Consumer端負載均衡的核心
    • 負載均衡演算法
      • 平均分配演算法
      • 環形演算法
      • 指定機房演算法
      • 就近機房演算法
      • 一致性雜湊演算法
      • 手動配置演算法

RocketMQ如何保證順序訊息?

  • 在預設的情況下訊息傳送會採取Round Robin輪詢方式把訊息傳送到不同的queue(分割槽佇列);而消費訊息的時候從多個queue上拉取訊息,這種情況傳送和消費是不能保證順序。但是如果控制傳送的順序訊息只依次傳送到同一個queue中,消費的時候只從這個queue上依次拉取,則就保證了順序。當傳送和消費參與的queue只有一個,則是全域性有序;如果多個queue參與,則為分割槽有序,即相對每個queue,訊息都是有序的。順序消費不能是併發的。
  • 怎麼保證訊息發到同一個queue裡?RocketMQ給我們提供了MessageQueueSelector介面,可以重寫裡面的介面,實現自己的演算法,比如判斷i%2==0,那就傳送訊息到queue1否則傳送到queue2。

RocketMQ如何實現訊息去重?

  • 這個得依賴於訊息的冪等性原則:就是使用者對於同一種操作發起的多次請求的結果是一樣的,不會因為操作了多次就產生不一樣的結果。只要保持冪等性,不管來多少條訊息,最後處理結果都一樣,需要Consumer端自行實現。
  • 在RocketMQ去重的方案:因為每個訊息都有一個MessageId, 保證每個訊息都有一個唯一鍵,可以是資料庫的主鍵或者唯一約束,也可以是Redis快取中的鍵,當消費一條訊息前,先檢查資料庫或快取中是否存在這個唯一鍵,如果存在就不再處理這條訊息,如果消費成功,要保證這個唯一鍵插入到去重表中。

說說RocketMQ分散式事務訊息?

image-20211203182018373

半訊息:是指暫時還不能被Consumer消費的訊息,Producer成功傳送到broker端的訊息,但是此訊息被標記為“暫不可投遞”狀態,只有等Producer端執行完本地事務後經過二次確認了之後,Consumer才能消費此條訊息。主要分為正常事務訊息的傳送及提交、事務訊息的補償流程兩大塊。RocketMQ事務訊息依賴半訊息,二次確認以及訊息回查機制。

  • 1、Producer向broker傳送半訊息
  • 2、Producer端收到響應,訊息傳送成功,此時訊息是半訊息,標記為“不可投遞”狀態,Consumer消費不了。
  • 3、Producer端執行本地事務。
  • 4、正常情況本地事務執行完成,Producer向Broker傳送Commit/Rollback,如果是Commit,Broker端將半訊息標記為正常訊息,Consumer可以消費,如果是Rollback,Broker丟棄此訊息。
  • 5、異常情況,Broker端遲遲等不到二次確認。在一定時間後,會查詢所有的半訊息,然後到Producer端查詢半訊息的執行情況。
  • 6、Producer端查詢本地事務的狀態
  • 7、根據事務的狀態提交commit/rollback到broker端。(5,6,7是訊息回查)

簡單歸納RocketMQ高效能原因?

  • 網路模型,RocketMQ 使用 Netty 框架實現高效能的網路傳輸,也遵循了Reactor多執行緒模型,同時又在這之上做了一些擴充套件和優化。而Netty高效能我們在前一篇文章也以學習過,這裡就不重複說了。image-20211203184624920

  • 順序寫、隨機讀、零拷貝。

  • 多主多從,建立topic時,多個message queue可以在多個broker上,master提供讀寫,從broker可以分擔讀訊息的壓力。

  • 同步複製和非同步複製。

  • 同步刷盤和非同步刷盤(PageCache)。

  • 同步和非同步傳送訊息。

  • 業務執行緒池隔離,RocketMQ 對 Broker 的執行緒池進行了精細的隔離。使得訊息的生產、消費、客戶端心跳、客戶端註冊等請求不會互相干擾。

  • 並行消費和批量消費。

最後:去哪兒網開源的QMQ訊息中介軟體也可以好好的研究,功能非常齊全,訊息中介軟體的應用是比較簡單的,更多應該思考和理解主流開源中介軟體Kafka、RocketMQ、QMQ、Palsar等的設計思想。

image-20211203173000232

相關文章