RabbitMQ設計原理解析

程式設計一生發表於2021-10-27

背景

RabbitMQ現在用的也比較多,但是沒有過去那麼多啦。現在很多的流行或者常用技術或者思路都是從過去的思路中演變而來的。瞭解一些過去的技術,對有些人來說可能會產生眾裡尋他千百度的頓悟,加深對技術的理解,更好的應用於工作中去。

本篇整體採用從淺到深的邏輯結構來描述。

 

入門部分

什麼是MQ

MQ全稱是Message Queue,訊息的佇列。因為是佇列,所以遵循FIFO先進先出原則。因為存放的是訊息,所以是一種跨程式的通訊機制。

為什麼使用MQ

流量削峰

這個跟很火的小吃店門口的排隊原理是一樣的。實時呼叫就好像是大家蜂擁而至,如果系統處理能力不夠,就會讓店家手忙腳亂,說不定會在冰激凌上澆上可樂。排隊能保證有條不紊,代價是整體處理速度會慢些。

非同步處理

當A呼叫B,B可能要花很長一段時間來完成。這時候一般有三種方式來非同步處理。A呼叫B,B返回A說收到呼叫請求了。同步請求已經完成,但B的執行才剛開始。這時候,第一種方式是A每隔一段時間來查詢一次,看B是否執行完,這是拉的方式;第二種方式是A提供一個回撥地址,B執行完之後回撥A,這是推的方式;第三種就是使用MQ,A使用MQ給B發訊息,B處理完再回一個訊息,好處是上面提到的同時可以流量削峰。

應用解耦

MQ實現了邏輯解耦+物理解耦。邏輯上,將請求和結果處理分開了;物理上,系統只用與MQ通訊。聽起來,MQ要優雅很多,但是上面提到非同步處理的三種方式的前兩種,現在也多很常見。那是因為MQ是有代價的,那就是需要一套MQ設施。做開放平臺,使用者之間的唯一設施就是網際網路,這時候更依賴雙方的協議約定,所以前兩種非同步處理方式不會被MQ取代。

MQ的分類

ActiveMQ是早期的MQ,倚老賣老一下,我那個年代用過,目前優勢已經不太明顯了。

Kafka號稱是大資料的殺手鐗,以百萬級TPS吞吐量名聲大噪。時效是ms級別,分散式的可用性高。消費者採用拉的方式獲取訊息,訊息有序,通過控制可以保證訊息僅被消費一次。但是單機超過64個分割槽,load會明顯飆高;實時性取決於輪詢時間間隔,關鍵是有可能丟訊息,不適合訂單業務中使用。

RocketMQ是國貨,用Java語言實現,在設計時參考了Kafka,單機吞吐量達到十萬級別,分散式架構可用性高,訊息可以0丟失,擴充套件性高。但是支援的客戶端成熟的也就是Java,核心程式碼沒有實現JMS,遷移需要修改大量程式碼。

RabbitMQ是erlang開發的,吞吐量達到萬級別,穩定、健壯、跨平臺,支援多種語言,企業間通訊中常用。

JMS支援

RabbitMQ不支援JMS協議。這個很好理解。因為JMS是Java訊息服務,提供了訊息傳遞的Java標準API。而RabbitMQ是Erlang寫的,對Java的支援會弱一些。但是RabiitMQ實現了AMQP標準協議。AMQP只是統一了資料交換的標準格式,與語言無關。

 

核心部分

核心概念

所有的MQ都由生產者、消費者和broker(佇列)三部分組成。但是不同的實現,根據核心思想不同,內部結構也各有特色。

比如銀行系統中常用的跨銀行間通訊的MQ,相當於兩組MQ拼起來的。

普通MQ

 

 

 

跨企業MQ

這樣做的好處是任何一端網路出現問題,都可以暫存訊息,等待網路恢復,不丟失訊息。訊息的重試放在broker端,減少了應用端的複雜度。為什麼這裡舉例時提到銀行間使用呢,因為使用這種模式的MQ,最重要的是有錢。因為想達到理想效果,要拉專線,並使用高配機器。

RabbitMQ和Kafka是一樣的

再回來考慮普通MQ的場景,如果這個MQ是RabbitMQ。元件細化一下是這樣:

這張圖上來看,其實RabbitMQ和Kafka是一樣的。來看Kafka的:

表面上來看,RabbitMQ的伺服器(Broker)端由Exchange和Queue兩部分組成。Exchange是交換機,交換機是做路由的。Kafka生產者發到Broker也需要路由啊,來決定路由到哪個Partition(也就是佇列)中去。只不過Kafka的路由模式很固定,就是先找到哪個topic,然後使用負載均衡的策略找到一個Partition來投遞訊息。Kafka是用了邏輯概念topic簡化了exchange路由,所以Kafka的路由功能也很單一。

表面上,RabbitMQ的生產者和消費者與服務端都是Channel通道來相連。Channel是複用連線來進行通訊的,Kafka也是需要的,只是它內部幫我們把這些與核心功能關係不大的都自己內建實現了。而RabbitMQ暴露給使用者,提供了更高的靈活性。

上面的兩段如果我沒有講明白,也沒有關係。只要知道更年輕的Kafka沒有Exchange和Channel的概念是類似於採取了約定大於配置的方式提供的服務。

核心功能

RabbitMQ的核心實際上就是AMQP的核心:MessageQueue、Exchange和Binding。

MessageQueue就是訊息佇列,一個佇列裡的一條訊息,也就是同一個message ID對應的訊息,不管有多少個消費者來分攤壓力,也只能被消費一次。訊息佇列和消費者之間有ack機制,訊息一旦確認安全送達,RabbitMQ服務端就可以安全刪除訊息了。

Binding是MessageQueue與Exchange之間的連線,Exchange只能給Binding的MessageQueue傳送訊息。

Exchange有四種型別:fanout、topic、direct和header。本質上就是有一堆MessageQueue,一個訊息是要被複制幾份,發到哪幾個Binding的訊息佇列去。Exchange給定了規則:fanout是對每個訊息佇列複製一份傳送;direct意思是隻髮指定的一份,不復制;topic是傳送萬用字元匹配的幾份;header可以指定一些其他的過濾條件傳送。訊息從生產者傳送到exchange之後也有ack機制來保證訊息的可靠傳輸。

Kafka只有topic的概念。這是因為Kafka的設計上訊息只用存一份,通過遊標,傳送後不立即刪除訊息。多個消費者組可以互不影響的消費。這是Kafka的一大改進。

內部原理

大家面試時有沒有被問過:Kafka怎麼保證訊息能且僅能收到一次?這是個埋坑題,是與面試官鬥智鬥勇的開始。什麼冪等、事務、流式EOS呀,其實呢,Kafka本身是不保證僅且僅收到一次的,所以這些實現方法都不優雅。

RabbitMQ通過AMQP事務機制,還有上面已經提過的ack也就是confirm兩種可選方式保證訊息被收到。

但是最為優雅的實現是IBM的Websphere MQ。因為這是收費的,所以研究的人不多。它通過訊息序列號保證訊息不丟失、不重傳。

通道為每條訊息的傳送分配一個序列號,它會自動累積增值。訊息序列號由傳送通道分配,是通道的一個永久屬性,每當傳送一條訊息,訊息序列號就加一。通道的相關屬性SEQWRAP標識序號的最大值,預設為999,999,999。序列號越界後自動歸零,從頭開始。

正常情況下,通道兩端的訊息序列號或者相等或相差為一。雙方對前面的某一條或一批訊息是否傳送成功理解不一致。在解決了不確定的訊息後,可以用MQSC命令通過重置訊息序號將雙方調整到一致。一旦連線斷開後,通道重連時雙方會將訊息序號同步。 

 

推薦閱讀

常用邏輯結構

實戰併發-使用分散式快取和有限狀態機

應用角度看kafka的術語和功能

穩定性五件套-限流的原理和實現

相關文章