背景
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命令通過重置消息序號將雙方調整到一致。一旦連接斷開后,通道重連時雙方會將消息序號同步。
推薦閱讀
