rocketMQ一

圣辉發表於2024-05-07

參考:

圖靈課堂:https://vip.tulingxueyuan.cn

MQ簡介

MQ:MessageQueue,訊息佇列。是在網際網路中使用非常廣泛的一系列服務中介軟體。 這個詞可以分兩個部分來看,

一是Message:訊息。訊息是在不同程序之間傳遞的資料。這些程序可以部署在同一臺機器上,也可以分佈在不同機器上。

二是Queue:佇列。佇列原意是指一種具有FIFO(先進先出)特性的資料結構,是用來快取資料的。

對於訊息中介軟體產品來說,能不能保證FIFO特性,尚值得考量。但是,所有訊息佇列都是需要具備儲存訊息,讓訊息排隊的能力。

​廣義上來說,只要能夠實現訊息跨程序傳輸以及佇列資料快取,就可以稱之為訊息佇列。例如我們常用的QQ、微信、阿里旺旺等就都具備了這樣的功能。只不過他們對接的使用物件是人,而我們這裡討論的MQ產品需要對接的使用物件是應用程式。

實際上應該說是訊息中介軟體,佇列是太侷限化了。

訊息中介軟體的作用

訊息中介軟體的三大作用:非同步,削峰,解耦。

非同步:就是縮短請求鏈路,這個縮短是相對的縮短,實際上真正的鏈路還是那樣,只是將一些需要序列的操作經過訊息中介軟體來連線,然後就直接返回這個請求。這樣可以大幅提升客戶端的體驗。就是將一些不太重要的操作序列執行。

削峰:就是當請求突然暴增的時候,可以透過訊息中介軟體來進行一層防護,因為中介軟體有儲存訊息的功能,可以將這些訊息儲存起來,讓服務端去慢慢消費。讓請求變得平緩。

解耦:就是將服務進行拆分,可以很容易進行增減服務,而不用對原來的服務進行大幅度的改動,提高系統的可用性。頻繁的釋出重啟很影響使用者的體驗。

RocketMQ產品特點

1、RocketMQ介紹

rocketMQ是阿里巴巴集團開發的,後面捐獻給了apache,現在是apache的頂級專案;其產生環境是因為金融高併發環境下,要保證其高可用,高穩定,高效能。綜合當時市面上別的訊息中介軟體實現機制,很難同時滿足這些需求,那麼就自己內部開發了一個訊息中介軟體。rocketMQ是完全Java語言開發的,對Java程式設計師很友好,裡面有很多JVM最佳化的場景,其原始碼很值得去學習。對比Kafka來說,其topic的數量是沒有限制的,理論上可以有很多。

2、rocketMQ的特點

當今網際網路MQ產品眾多,其中,影響力和使用範圍最大的當數Apache Kafka、RabbitMQ、Apache RocketMQ以及Apache Plusar。這幾大產品雖然都是典型的MQ產品,但是由於設計和實現上的一些差異,造成他們適合於不同的細分場景。

型別優點缺點
Apache Kafka 吞吐量非常大,效能非常好,叢集高可用。 會有丟資料的可能,功能比較單一 日誌分析、大資料採集
RabbitMQ 訊息可靠性高,功能全面。 erlang語言不好定製。吞吐量比較低。 企業內部小規模服務呼叫
Apache Pulsar 基於Bookeeper構建,訊息可靠性非常高。 周邊生態還有差距,目前使用的公司比較少。 企業內部大規模服務呼叫
Apache RocketMQ 高吞吐、高效能、高可用。功能全面。客戶端協議豐富。使用java語言開發,方便定製。 服務載入比較慢。 幾乎全場景,特別適合金融場景

RocketMQ的吞吐量雖然比Kafka低一些,但是其穩定性更好。訊息完整性也更好。

而RocketMQ最大的優勢就是他天生就為金融網際網路而生。他的訊息可靠性相比Kafka也有了很大的提升,而訊息吞吐量相比RabbitMQ也有很大的提升。另外,RocketMQ的高階功能也越來越全面,廣播消費、延遲佇列、死信佇列等等高階功能一應俱全,甚至某些業務功能比如事務訊息,已經呈現出領先潮流的趨勢。

3、rocketMQ的版本

當前最新的版本是5.x,這是一個著眼於雲原生的新版本,給 RocketMQ 帶來了非常多很亮眼的新特性。但是目前來看,企業中用得還比較少。因此,我們這裡採用的還是更為穩定的4.9.5版本。

注:在2020年下半年,RocketMQ新推出了5.0的大版本,這對於RocketMQ來說,是一個里程碑式的大版本。在這個大版本中,RocketMQ對整體功能做了一次大的升級。增加了很多非常有用的新特性,也對已有功能重新做了升級。

​比如在具體功能方面:

在4.x版本中,對於定時訊息,只能設定幾個固定的延遲級別,而5.0版本中,已經可以指定具體的傳送時間了。

在客戶端語言方面,4.x版本,RocketMQ原生只支援基於Netty框架的Java客戶端。而在5.0版本中,增加了對Grpc協議的支援,這基本上就解除了對客戶端語言的限制。

在服務端架構方面,4.x版本只支援固定角色的普通叢集和可以動態切換角色的Dledger叢集,而在5.0版本中,增加了Dledger Controller混合叢集模式,即可以混合使用Dledger的叢集機制以及 Broker 本地的檔案管理機制。

​但是隨著功能強大,同時也意味著問題會很多,使用起來也會更加複雜一些。讓子彈再飛一會兒。

4、rocketMQ的安裝

接下來,RocketMQ建議的執行環境需要至少12G的記憶體,這是生產環境比較理想的資源配置。

但是,學習階段,如果你的伺服器沒有這麼大的記憶體空間,那麼就需要做一下調整。進入bin目錄,對其中的runserver.sh和runbroker.sh兩個指令碼進行一下修改。

​使用vi runserver.sh指令,編輯這個指令碼,找到下面的一行配置,調整Java程序的記憶體大小。

JAVA_OPT="${JAVA_OPT} -server -Xms512m -Xmx512m -Xmn256m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"

接下來,同樣調整runbroker.sh中的記憶體大小。

JAVA_OPT="${JAVA_OPT} -server -Xms8g -Xmx8g"
修改為:
JAVA_OPT="${JAVA_OPT} -server -Xms1g -Xmx1g"

生產環境不建議調整。

​ 調整完成後,就可以啟動RocketMQ服務了。 RocketMQ服務基於Java開發,所以需要提前安裝JDK。JDK建議採用1.8版本即可。

Java環境安裝略過。

​ RocketMQ的後端服務分為nameserver和broker兩個服務,接下來我們先將這兩個服務啟動起來。

namesever是必須要先啟動的,因為這個類似一個註冊中心,所有的broker都會和和所有的namesever節點相通訊,可以獲取到broker的狀態。然後生產者和消費者是去和namesever互動,拿到broker節點的位置,進行訊息的傳送消費等。

第一步:啟動nameserver服務。

cd /app/rocketmq/rocketmq-all-4.9.5-bin-release
nohup bin/mqnamesrv &

接下來,可以透過jsp指令進行驗證。使用jps指令後,可以看到有一個NamesrvStartup的程序執行,也表示nameserver服務啟動完成。

第二步:啟動broker服務。

​ 啟動broker服務之前,要做一個小小的配置。進入RocketMQ安裝目錄下的conf目錄,修改broker.conf檔案,在檔案最後面加入一個配置:

autoCreateTopicEnable=true

這個選項是為了便於進行後續實驗。他的作用是允許 broker 端自動建立新的 Topic。

另外,如果你的伺服器配置了多張網路卡,比如阿里雲,騰訊雲這樣的雲伺服器,他們通常有內網網路卡和外網網路卡兩張網路卡,那麼需要增加配置brokerIP1屬性,指向伺服器的外網IP 地址,這樣才能確保從其他伺服器上訪問到RocketMQ 服務。

另外,如果是測試學習階段,可以將防火牆關閉。

然後也可以用之前的方式啟動broker服務。啟動broker服務的指令是mqbroker

cd /app/rocketmq/rocketmq-all-4.9.5-bin-release  nohup bin/mqbroker &

啟動完成後,同樣檢查nohup.out日誌檔案,有如下一條關鍵日誌,就表示broker服務啟動正常了。

The broker[xxxxx] boot success. serializeType=JSON 

注:

1、在實際服務部署時,通常會將RocketMQ的部署地址新增到環境變數當中。例如使用vi ~/.bash_profile指令,新增以下內容。

export ROCKETMQ_HOME=/app/rocketmq/rocketmq-all-4.9.5-bin-releasePATH=$ROCKETMQ_HOME/bin:$PATHexport PATH

這樣就不必每次進入RocketMQ的安裝目錄了。直接可以使用mqnamesrv 和mqbroker指令。

2、停止RocketMQ服務可以透過mqshutdown指令進行

mqshutdown namesrv # 關閉nameserver服務

mqshutdown broker # 關閉broker服務

關閉服務,千萬不能隨便使用kill 埠號,這樣可能會導致資料丟失。

​ 同樣使用jps指令可以檢查服務的啟動狀態。使用jsp指令後,可以看到一個名為BrokerStartup的程序,則表示broker服務啟動完成。

搭建Maven客戶端專案

​ 之前的步驟實際上是在伺服器上快速驗證RocketMQ的服務狀態,接下來我們動手搭建一個RocketMQ的客戶端應用,在實際應用中整合使用RocketMQ。

第一步:建立一個標準的maven專案,在pom.xml中引入以下核心依賴

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

第二步:就可以直接建立一個簡單的訊息生產者

public class Producer {
    public static void main(String[] args) throws MQClientException, InterruptedException {
        //初始化一個訊息生產者
        DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");
        // 指定nameserver地址
        producer.setNamesrvAddr("xx.xx.xx.xx:埠");
        // 啟動訊息生產者服務
        producer.start();
        for (int i = 0; i < 2; i++) {
            try {
                // 建立訊息。訊息由Topic,Tag和body三個屬性組成,其中Body就是訊息內容
                Message msg = new Message("TopicTest","TagA",("Hello RocketMQ " +i).getBytes(RemotingHelper.DEFAULT_CHARSET));
                //傳送訊息,獲取傳送結果
                SendResult sendResult = producer.send(msg);
                System.out.printf("%s%n", sendResult);
            } catch (Exception e) {
                e.printStackTrace();
                Thread.sleep(1000);
            }
        }
        //訊息傳送完後,停止訊息生產者服務。
        producer.shutdown();
    }
}

執行其中的main方法,就會往RocketMQ中傳送兩條訊息。在這個實現過程中,需要注意一下的是對於生產者,需要指定對應的nameserver服務的地址,這個地址需要指向你自己的伺服器。

第三步:建立一個訊息消費者接收RocketMQ中的訊息。

public class Consumer {
    public static void main(String[] args) throws InterruptedException, MQClientException {
        //構建一個訊息消費者
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_4");
        //指定nameserver地址
       consumer.setNamesrvAddr("192.168.232.128:9876");
       consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
        // 訂閱一個感興趣的話題,這個話題需要與訊息的topic一致
        consumer.subscribe("TopicTest", "*");
        // 註冊一個訊息回撥函式,消費到訊息後就會觸發回撥。
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,ConsumeConcurrentlyContext context) {
    msgs.forEach(messageExt -> {
                    try {
                        System.out.println("收到訊息:"+new String(messageExt.getBody(), RemotingHelper.DEFAULT_CHARSET));
                    } catch (UnsupportedEncodingException e) {}
                });
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        //啟動消費者服務
        consumer.start();
        System.out.print("Consumer Started");
    }
}
 

​ 執行其中的main方法後,就可以啟動一個RocketMQ消費者,接收之前發到RocketMQ上的訊息,並將訊息內容列印出來。在這個實現過程中,需要重點關注的有兩點。

一是對於消費者,同樣需要指定nameserver的地址。

二是消費者需要在RocketMQ中訂閱具體的Topic,只有傳送到這個Topic上的訊息才會被這個消費者接收到。

示例程式碼不用強行記憶, 在RocketMQ的原始碼包中有個example模組,其中就有這些示例程式碼。

​這樣,透過幾個簡單的步驟,我們就完成了RocketMQ的應用整合。從這個過程中可以看到,RocketMQ的使用是比較簡單的。但是這並不意味著這幾個簡單的步驟就足夠搭建一個生產級別的RocketMQ服務。rocketMQ的叢集搭建也是比較麻煩的。裡面有很多的點需要去注意。

4、升級分散式叢集

​之前我們用一臺Linux伺服器,快速搭建起了一整套RocketMQ的服務。但是很明顯,這樣搭建的服務是無法放到生產環境上去用的。

一旦nameserver服務或者broker服務出現了問題,整個RocketMQ就無法正常工作。而且更嚴重的是,如果伺服器出現了問題,比如磁碟壞了,那麼儲存在磁碟上的資料就會丟失。

這時RocketMQ暫存到磁碟上的訊息也會跟著丟失,這個問題就非常嚴重了。因此,我們需要搭建一個分散式的RocketMQ服務叢集,來防止單點故障問題。

RocketMQ的分散式叢集基於主從架構搭建。在多個伺服器組成的叢集中,指定一部分節點作為Master節點,負責響應客戶端的請求。

指令另一部分節點作為Slave節點,負責備份Master節點上的資料,這樣,當Master節點出現故障時,在Slave節點上可以保留有資料備份,至少保證資料不會丟失。

rocketMQ普通叢集架構,其slave節點可以對外提供讀請求嗎?

RocketMQ的普通叢集架構中,slave節點主要負責接收讀請求,並且可以接受來自master節點同步過來的資料,以保持與master節點資料的一致性。在主從架構中,資料是從master節點同步到slave節點,而從節點(slave)則只處理讀請求,不進行寫操作。這意味著slave節點確實可以對外提供讀請求,但不支援寫入操作。

此外,RocketMQ的叢集部署方式包括多Master多Slave模式,其中每個Master節點配備一個Slave節點,採用非同步複製方式,這種配置下,Slave節點的主要功能是讀取和快取Master節點上的資料,以提高系統的讀取效能和可用性。因此,可以確認,在RocketMQ的普通叢集架構中,slave節點確實可以對外提供讀請求。

Master節點

負責接收事務請求

可對外提供讀請求

同步資料給Slave節點

Slave節點

接收讀請求

不進行寫操作,僅讀取資料

保持與Master節點的資料一致

叢集模式

單Master模式: 風險較大,不可靠

多Master模式: 配置簡單,效能高,但可能有少量訊息丟失

多Master多Slave模式-非同步複製: 實時性高,主備間切換對應用透明

叢集部署方式

單Master模式: 不建議線上環境使用

多Master模式: 每個Master配一個Slave,採用非同步複製方式

資料同步機制

Slave主動向Master傳送請求通知主節點進行資料同步

總結

在RocketMQ的普通叢集架構中,普通情況下, Slave節點不直接對外提供讀請求。它主要負責從Master節點同步過來的資料,並保持與Master節點的資料一致。因此,從技術角度講, Slave節點不直接對外提供讀請求。

​整個叢集方案如下圖所示:

接下來我們準備三臺相同的Linux伺服器,搭建一下RocketMQ的分散式叢集。為了更清晰的描述這三臺伺服器上的操作,我們給每個伺服器指定一個機器名。

第一步:部署nameServer服務。

​ nameServer服務不需要做特別的配置,按照之前的步驟,在三臺伺服器上都分別部署nameServer服務即可。

第二步:對Broker服務進行叢集配置。

​ 這裡需要修改RocketMQ的配置檔案,對broker服務做一些叢集相關的引數部署。這些配置檔案並不需要我們手動進行建立,在RocketMQ執行包的conf目錄下,提供了多種叢集的部署配置檔案模板。

  • 2m-noslave: 2主無從的叢集參考配置。這種叢集存在單點故障。

  • 2m-2s-async和2m-2s-sync: 2主2從的叢集參考配置。其中async和sync表示主節點與從節點之間是同步同步還是非同步同步。關於這兩個概念,會在後續章節詳細介紹

  • dledger: 具備主從切換功能的高可用叢集。叢集中的節點會基於Raft協議隨機選舉出一個Leader,其作用類似於Master節點。其他的節點都是follower,其作用類似於Slave節點。

我們這次採用2m-2s-async的方式搭建叢集,需要在worker2和worker3上修改這個資料夾下的配置檔案。

1> 配置第一組broker-a服務

​ 在worker2機器上配置broker-a的MASTER服務,需要修改conf/2m-2s-async/broker-a.properties。示例配置如下:

#所屬叢集名字,名字一樣的節點就在同一個叢集內
brokerClusterName=rocketmq-cluster
#broker名字,名字一樣的節點就是一組主從節點。
brokerName=broker-a
#brokerid,0就表示是Master,>0的都是表示 Slave
brokerId=0
#nameServer地址,分號分割
namesrvAddr=worker1:9876;worker2:9876;worker3:9876
#是否允許 Broker 自動建立Topic,建議線下開啟,線上關閉
autoCreateTopicEnable=true
deleteWhen=04
fileReservedTime=120
#儲存路徑
storePathRootDir=/app/rocketmq/store
storePathCommitLog=/app/rocketmq/store/commitlog
storePathConsumeQueue=/app/rocketmq/store/consumequeue
storePathIndex=/app/rocketmq/store/index
storeCheckpoint=/app/rocketmq/store/checkpoint
abortFile=/app/rocketmq/store/abort
#Broker 的角色
brokerRole=ASYNC_MASTER
flushDiskType=ASYNC_FLUSH
#Broker 對外服務的監聽埠
listenPort=10911
這裡對幾個需要重點關注的屬性,做下簡單介紹:
  • brokerClusterName: 叢集名。RocketMQ會將同一個區域網下所有brokerClusterName相同的服務自動組成一個叢集,這個叢集可以作為一個整體對外提供服務

  • brokerName: Broker服務名。同一個RocketMQ叢集當中,brokerName相同的多個服務會有一套相同的資料副本。同一個RocketMQ叢集中,是可以將訊息分散儲存到多個不同的brokerName服務上的。

  • brokerId: RocketMQ中對每個服務的唯一標識。RocketMQ對brokerId定義了一套簡單的規則,master節點需要固定配置為0,負責響應客戶端的請求。slave節點配置成其他任意數字,負責備份master上的訊息。

  • brokerRole: 服務的角色。這個屬性有三個可選項:ASYNC_MASTER,SYNC_MASTER和SLAVE。其中,ASYNC_MASTER和SYNC_MASTER表示當前節點是master節點,目前暫時不用關心他們的區別。SLAVE則表示從節點。

  • namesrvAddr: nameserver服務的地址。nameserver服務預設佔用9876埠。多個nameserver地址用;隔開。

​ 接下來在worekr3上配置broker-a的SLAVE服務。需要修改conf/2m-2s-async/broker-a-s.properties。示例配置如下:

#所屬叢集名字,名字一樣的節點就在同一個叢集內
brokerClusterName=rocketmq-cluster
#broker名字,名字一樣的節點就是一組主從節點。
brokerName=broker-a
#brokerid,0就表示是Master,>0的都是表示 Slave
brokerId=1
#nameServer地址,分號分割
namesrvAddr=worker1:9876;worker2:9876;worker3:9876
#是否允許 Broker 自動建立Topic,建議線下開啟,線上關閉
autoCreateTopicEnable=true
deleteWhen=04
fileReservedTime=120
#儲存路徑
storePathRootDir=/app/rocketmq/storeSlave
storePathCommitLog=/app/rocketmq/storeSlave/commitlog
storePathConsumeQueue=/app/rocketmq/storeSlave/consumequeue
storePathIndex=/app/rocketmq/storeSlave/index
storeCheckpoint=/app/rocketmq/storeSlave/checkpoint
abortFile=/app/rocketmq/storeSlave/abort
#Broker 的角色
brokerRole=SLAVE
flushDiskType=ASYNC_FLUSH
#Broker 對外服務的監聽埠
listenPort=11011

其中關鍵是brokerClusterName和brokerName兩個引數需要與worker2上對應的broker-a.properties配置匹配。brokerId配置0以為的數字。
然後brokerRole配置為SLAVE。

​ 這樣,第一組broker服務就配置好了。

然後按照這個配置去配第二個。

...

這樣就完成了2主2從叢集的配置。配置過程彙總有幾個需要注意的配置項:

  • store開頭的一系列配置:表示RocketMQ的存檔檔案地址。在同一個機器上需要部署多個Broker服務時,不同服務的儲存目錄不能相同。

  • listenPort:表示Broker對外提供服務的埠。這個埠預設是10911。在同一個機器上部署多個Broker服務時,不同服務佔用的埠也不能相同。

  • 如果你使用的是多網路卡的伺服器,比如阿里雲上的雲伺服器,那麼就需要在配置檔案中增加配置一個brokerIP1屬性,指向所在機器的外網網路卡地址。

第三步:啟動Broker服務

​ 叢集配置完成後,需要啟動Broker服務。與之前啟動broker服務稍有不同,啟動時需要增加-c引數,指向我們修改的配置檔案。

​ 在worker2上啟動broker-a的master服務和broker-b的slave服務:

cd /app/rocketmq/rocketmq-all-4.9.5-bin-release
nohup bin/mqbroker -c ./conf/2m-2s-async/broker-a.properties &
nohup bin/mqbroker -c ./conf/2m-2s-async/broker-b-s.properties &

​ 在worker3上啟動broker-b的master服務和broker-a的slave服務:

cd /app/rocketmq/rocketmq-all-4.9.5-bin-release
nohup bin/mqbroker -c ./conf/2m-2s-async/broker-b.properties &
nohup bin/mqbroker -c ./conf/2m-2s-async/broker-a-s.properties &

第四步:檢查叢集服務狀態

​ 對於服務的啟動狀態,我們依然可以用之前介紹的jps指令以及nohup.out日誌檔案進行跟蹤。不過,在RocketMQ的bin目錄下,也提供了mqadmin指令,可以透過命令列的方式管理RocketMQ叢集。

​ 例如下面的指令可以檢視叢集broker叢集狀態。透過這個指令可以及時瞭解叢集的執行狀態。

[oper@worker1 bin]$ cd /app/rocketmq/rocketmq-all-4.9.5-bin-release/bin
[oper@worker1 bin]$ mqadmin clusterList
RocketMQLog:WARN No appenders could be found for logger (io.netty.util.internal.InternalThreadLocalMap).
RocketMQLog:WARN Please initialize the logger system properly.
#Cluster Name     #Broker Name            #BID  #Addr                  #Version                #InTPS(LOAD)       #OutTPS(LOAD) #PCWait(ms) #Hour #SPACE
rocketmq-cluster  broker-a                0     192.168.232.129:10911  V4_9_1                   0.00(0,0ms)         0.00(0,0ms)          0 3425.28 0.3594
rocketmq-cluster  broker-a                1     192.168.232.130:11011  V4_9_1                   0.00(0,0ms)         0.00(0,0ms)          0 3425.28 0.3607
rocketmq-cluster  broker-b                0     192.168.232.130:10911  V4_9_1                   0.00(0,0ms)         0.00(0,0ms)          0 3425.27 0.3607
rocketmq-cluster  broker-b                1     192.168.232.129:11011  V4_9_1                   0.00(0,0ms)         0.00(0,0ms)          0 3425.27 0.3594

注:執行這個指令需要在機器上配置了NAMESRV環境變數

​ mqadmin指令還提供了非常豐富的管理功能。你可以嘗試直接使用mqadmin指令,就會列出mqadmin支援的所有管理指令。如果對某一個指令不會使用,還可以使用mqadmin help 指令檢視幫助。

​ 另外,之前搭建的dashboard也是叢集服務狀態的很好的工具。只需要在之前搭建Dashboard時建立的配置檔案中增加指定nameserver地址即可。

rocketmq: 
  config: 
    namesrvAddrs: 
      - worker1:9876 
      - worker2:9876
      - worker3:9876

啟動完成後,在叢集選單頁就可以看到叢集的執行情況

在RocketMQ的這種主從架構的叢集下,客戶端傳送的訊息會分散儲存到broker-a和broker-b兩個服務上,然後每個服務都配有slave服務,可以備份對應master服務上的訊息,這樣就可以防止單點故障造成的訊息丟失問題。

5、升級高可用叢集

​主從架構的RocketMQ叢集,由於給每個broker服務配置了一個或多個slave備份服務,可以保證當broker服務出現問題時,broker上的訊息不會丟失。但是,這種主從架構的叢集卻也有一個不足的地方,那就是不具備服務高可用。

​這裡所說的服務高可用,並不是並不是指整個RocketMQ叢集就不能對外提供服務了,而是指叢集中的訊息就不完整了。實際上,當RocketMQ叢集中的broker當機後,整個叢集會自動進行broker狀態感知。後續客戶端的各種請求,依然可以轉發到其他正常的broker上。只不過,原本儲存在當前broker上的訊息,就無法正常讀取了,需要等到當前broker服務重啟後,才能重新被訊息消費者讀取。

​當一個broker上的服務當機後,我們可以從對應的slave服務上找到broker上所有的訊息。但是很可惜,主從架構中各個服務的角色都是固定了的,slave服務雖然擁有全部的資料,但是它沒辦法升級成為master服務去響應客戶端的請求,依然只是傻傻等待master服務重啟後,繼續做它的資料備份工作。也就是說當master當機,是無法進行寫入操作的,只能提供讀操作。因為不涉及到主從切換,也就沒有訊息丟失的情況。無法寫入新的資料,老的資料slave上都有。

​這時,我們自然就希望這個slave服務可以升級成為master服務,繼續響應客戶端的各種請求,這樣整個叢集的訊息服務就不會有任何中斷。而RocketMQ提供的Dledger叢集,就是具備角色自動轉換功能的高可用叢集。

​整個叢集結構如下圖所示:

在Dledger叢集中,就不再單獨指定各個broker的服務,而是由這些broker服務自行進行選舉,產生一個Leader角色的服務,響應客戶端的各種請求。而其他的broker服務,就作為Follower角色,負責對Leader上的資料進行備份。當然,Follower所要負責的事情,比主從架構中的SLAVE角色會要複雜一點,因為這種節點選舉是在後端不斷進行的,他們需要隨時做好升級成Leader的準備。

​Dledger叢集的選舉是透過Raft協議進行的,Raft協議是一種多數同意機制。也就是每次選舉需要有叢集中超過半數的節點確認,才能形成整個叢集的共同決定。同時,這也意味著在Dledger叢集中,只要有超過半數的節點能夠正常工作,那麼整個叢集就能正常工作。因此,在部署Dledger叢集時,通常都是部署奇數臺服務,這樣可以讓叢集的容錯性達到最大。

​接下來,我們就用之前準備的3臺伺服器,搭建一個3個節點的Dledger叢集。在這個叢集中,只需要有2臺Broker服務正常執行,這個叢集就能正常工作。

第一步:部署nameserver

​這一步和之前部署主從叢集沒有區別,不需要做過多的配置,直接在三臺伺服器上啟動nameserver服務即可。

​實際上,如果你是從上一個主從架構開始搭建起來的話,那麼nameserver叢集都不需要重新啟動,nameserver會自動感知到broker的變化。

第二步:對Broker服務進行叢集配置。

​ 對於Dledger叢集的配置,RocketMQ依然貼心的給出了完整的示例,不需要強行記憶。

​ 在conf/dledger目錄下,RocketMQ預設給出了三個配置檔案,這三個配置檔案可以在單機情況下直接部署成一個具有三個broker服務的Dledger叢集,我們只需要按照這個配置進行修改即可。

注:在RocketMQ執行包的bin/dledger目錄下,RocketMQ還提供了一個fast-try.sh指令碼。這個指令碼會指定conf/deldger目錄下的配置檔案,直接啟動有三個broker服務的Dledger叢集。每個叢集指定的記憶體大小佔用1G。

​ 接下來我們可以在三臺機器的conf/dledger目錄下,都建立一個broker.conf檔案,對每個broker服務進行配置。

...

這裡對幾個需要重點關注的配置項,做下介紹:

  • enableDLegerCommitLog: 是否啟動Dledger。true表示啟動

  • namesrvAddr: 指定nameserver地址

  • dLedgerGroup: Dledger Raft Group的名字,建議跟brokerName保持一致。

  • dLedgerPeers: Dledger Group內各個服務節點的地址及埠資訊。同一個Group內的各個節點配置必須要保持一致。

  • dLedgerSelfId: Dledger節點ID,必須屬於dLedgerPeers中的一個。同一個Group內的各個節點必須不能重複。

  • sendMessageThreadPoolNums:dLedger內部傳送訊息的執行緒數,建議配置成cpu核心數。

  • store開頭的一系列配置: 這些是配置dLedger叢集的訊息存檔目錄。如果你是從主從架構升級成為dLedger架構,那麼這個地址可以指向之前搭建住主從架構的地址。dLedger叢集會相容主從架構叢集的訊息格式,只不過主從架構的訊息無法享受dLedger叢集的兩階段同步功能。

第三步:啟動broker服務

​ 和啟動主從架構的broker服務一樣,我們只需要在啟動broker服務時,指定配置檔案即可。在三臺伺服器上分別執行以下指令,啟動broker服務。

cd /app/rocketmq/rocketmq-all-4.9.5-bin-release/
nohup bin/mqbroker -c conf/dledger/broker.conf &

第四步:檢查叢集服務狀態

​我們可以在Dashboard控制檯的叢集選單頁看到Dledger叢集的執行狀況。

從整個配置過程中可以看到,我們並沒有指定每個節點的角色,而Dledger叢集就自動將192.168.232.129也就是worker2上的broker服務選舉成了master。

​接下來如果你有興趣,可以自己嘗試下停止woker2上的broker服務,再重新觀察叢集的執行狀況。RocketMQ會在發現worker2服務當機後,很快的選舉產生新的master節點。但具體選舉出worker1還是worker3作為master,則是隨機的。

​但是,如果你嘗試繼續停止worker1或worker3上的broker服務,那麼叢集中當機的broker服務就超過了半數,也就是兩臺。這時這個Dledger叢集就選舉不出master節點,也就無法正常工作了。

關於Dledger叢集的一些補充

​Dledger叢集機制是RocketMQ自4.5版本開始支援的一個重要特性。他其實是由OpenMessage組織帶入RocketMQ的一個系列框架。他是一個為高可用、高效能、高可靠的分散式儲存系統提供基礎支援的元件。他做的事情主要有兩個,一是在叢集中選舉產生master節點。RocketMQ叢集需要用這個master節點響應客戶端的各種請求。二是在各種複雜的分散式場景下,保證CommitLog日誌檔案在叢集中的強一致性。

以下是ChatGPT對於Dledger的功能描述

RocketMQ是一款分散式訊息佇列系統,主要用於處理大量資料的實時傳輸和處理。在RocketMQ中,DLedger是一個為高可用、高效能、高可靠的分散式儲存系統提供基礎支援的元件。DLedger叢集主要具有以下功能:

  1. 資料複製:DLedger叢集透過raft協議來保證資料的一致性。在叢集中,每個節點都維護一個相同的資料副本,以確保當某個節點出現故障時,資料不會丟失。

  2. 容錯性:DLedger叢集具有很高的容錯性。即使叢集中的部分節點發生故障,只要叢集中有大多數節點(即超過半數)仍在正常工作,整個叢集將繼續提供服務。

  3. 高可用性:DLedger叢集透過負載均衡和熱備份等機制,確保在節點故障時能夠快速切換到其他正常節點,提高整個系統的可用性。

  4. 分散式鎖:DLedger叢集提供分散式鎖功能,可以解決分散式系統中的資源爭用問題,實現跨節點的資源同步。

  5. 強一致性:DLedger叢集透過使用Raft一致性協議,確保在多個副本節點之間同步資料,保證資料的強一致性。

  6. 高效能:DLedger叢集支援水平擴充套件,可以透過增加節點來提高系統的吞吐量和儲存能力,以滿足不斷增長的業務需求。

  7. 易於管理:DLedger叢集提供了一系列管理和監控功能,使運維人員可以更方便地掌握系統的執行狀況,及時發現和解決問題。

總之,RocketMQ的DLedger叢集具有高可用、高效能、高可靠等特點,為分散式訊息佇列系統提供了堅實的基礎。

​ 其背後的核心就是Raft協議。這是一種強大的分散式選舉演算法,其核心是隻要叢集中超過半數的節點作出的共同決議,就認為是叢集最終的共同決議。

​ Raft協議透過投票機制保持資料一致性。詳細的細節,我們這裡不做過多討論,只是給你介紹一下Raft協議一個很強大的地方,就是他解決了分散式叢集中的腦裂問題。

​ 關於腦裂問題,這是在叢集選舉過程中一個出現機率不高,但是讓很多人頭疼的問題。在分散式叢集內,有可能會由於網路波動或者其他一些不穩定因素,造成叢集內節點之間短時間通訊不暢通。這時就容易在叢集內形成多個包含多個節點的小集合。這些集合就會獨立進行選舉,各自產生新的Master節點。當網路恢復暢通後,叢集中就有了多個Master節點。當叢集中出現多個Master節點後,其他節點就不知道要聽從誰的指令了,從而造成叢集整體工作癱瘓。也就是俗話說的“一山不容二虎”。腦裂問題在以Zookeeper為代表的早前一代分散式一致性產品中,是一個非常頭疼的問題。而Raft協議對於腦裂問題,會採用隨機休眠的機制,徹底解決腦裂問題。RocketMQ是Raft協議的一個重要的成功示例。Kafka也在之後基於Raft協議,自行實現了Kraft叢集機制。

​ 同樣,附上ChatGPT對於腦裂問題的介紹,供你參考:

問題:Dledger叢集如何防止叢集腦裂問題?

DLedger叢集透過使用Raft協議來防止叢集腦裂(split-brain)問題。腦裂問題是指在分散式系統中,由於網路分割槽或其他原因導致叢集被分割成兩個或多個子叢集,各自獨立執行且無法感知到其他子叢集的存在。這可能導致資料不一致和錯誤決策。Raft協議採用了一系列措施來避免腦裂問題的發生:

  1. 選舉機制:Raft協議的基礎是選舉出一個領導者(Leader),其他節點(Follower)都從領導者獲取資料。選舉過程要求候選人必須獲得叢集中大多數節點的支援才能成為領導者。這確保了叢集中只能有一個領導者,從而避免了腦裂問題。
  2. 任期(Term):Raft協議為每個選舉週期設定了一個遞增的任期編號。任期編號用於標識當前的領導者,確保舊的領導者不會再次被選為領導者。如果一個節點發現自己的任期小於其他節點,那麼它會停止當前的工作並更新自己的任期。
  3. 心跳機制:領導者會定期向其他節點傳送心跳訊息,以保持與Follower節點的連線。當一個節點長時間未收到領導者的心跳時,它會認為當前領導者失效,並啟動新一輪選舉。這確保了當領導者出現故障時,系統能夠快速地選出新的領導者。
  4. 日誌複製:領導者負責將資料更新(日誌條目)複製到其他節點。Follower節點只有在收到領導者的日誌條目並將其寫入本地日誌後,才會響應客戶端的請求。這確保了在發生腦裂情況下,不會出現多個節點試圖同時修改同一份資料的情況。

透過以上措施,DLedger叢集利用Raft協議避免了腦裂問題的發生,保證了系統的高可用性和資料一致性。

​ 注:Dledger叢集由於會接管RocketMQ原生的檔案寫入,所以,Dledger叢集的檔案寫入速度比RocketMQ的原生寫入方式是要慢一點的。這會對RocketMQ的效能產生一些影響。所以,當前版本的Dledger叢集在企業中用得並不是太多。5.0版本對Dledger叢集抽出了一種Dledger Controller模式,也就是隻用Dledger叢集的選舉功能,而不用他的Commit檔案寫入功能,這樣效能可以得到一定的提升。

Dledger進行master和slave主從切換的時候是不會丟訊息的,是保證了節點之間資料的強一致性。透過兩階段提交的方式。切換的過程中會令併發性稍微降低。當叢集中的總節點少於半數的時候就不會進行主從切換了,選舉master也是透過半數同意才能升級為master的。如果低於了半數的節點,那麼剩下的就是slave,無法進行寫服務,只能讀取。
如果不用Dledger怎麼保證高可用?運維層面可以實時監控執行的執行緒,當出現問題停止的時候可以立刻重啟,恢復服務的正常執行。
JDK9之後預設的垃圾回收器就是G1回收器;8只是簡單的支援這個G1,預設還是CMS。G1主要是在大記憶體情況下適用。
早期版本nameserver使用的CMS;broker使用的G1GC。

四、總結RocketMQ的執行架構

​ 透過之前的一系列實驗,相信你對RocketMQ的執行機制有了一個大概的瞭解。接下來我們結合一下之前實驗的過程,來理解一下RocketMQ的執行架構。

​下圖是RocketMQ執行時的整體架構:

各個元件的作用:

1、nameServer 命名服務

nameServer不依賴於任何其他的服務,自己獨立就能啟動。並且,不管是broker還是客戶端,都需要明確指定nameServer的服務地址。

以一臺電腦為例,nameServer可以理解為是整個RocketMQ的CPU,整個RocketMQ叢集都要在CPU的協調下才能正常工作。

2、broker 核心服務

broker是RocketMQ中最為嬌貴的一個元件。RockeMQ提供了各種各樣的重要設計來保護broker的安全。同時broker也是RocketMQ中配置最為繁瑣的部分。

同樣以電腦為例,broker就是整個RocketMQ中的硬碟、顯示卡這一類的核心硬體。RocketMQ最核心的訊息儲存、傳遞、查詢等功能都要由broker提供。

3、client 客戶端

​Client包括訊息生產者和訊息消費者。

同樣以電腦為例,Client可以認為是RocketMQ中的鍵盤、滑鼠、顯示器這類的輸入輸出裝置。滑鼠、鍵盤輸入的資料需要傳輸到硬碟、顯示卡等硬體才能進行處理。但是鍵盤、滑鼠是不能直接將資料輸入到硬碟、顯示卡的,這就需要CPU進行協調。透過CPU,滑鼠、鍵盤就可以將輸入的資料最終傳輸到核心的硬體裝置中。經過硬體裝置處理完成後,再透過CPU協調,顯示器這樣的輸出裝置就能最終從核心硬體裝置中獲取到輸出的資料。

相關文章