RabbitMQ之認知

什麼是MQ?

消息總線(Message Queue),是一種跨進程、異步的通信機制,用於上下游傳遞消息。由消息系統來確保消息的可靠傳遞。

 

MQ是幹什麼用的?

應用解耦、異步、流量削鋒、數據分發、錯峯流控、日誌收集等等...

MQ衡量標準

服務性能、數據存儲、集羣架構

ActiveMQ

ActiveMQ是apache出品,最流行的,能力強勁的開源消息總線,並且它一個完全支持JMS規範的消息中間件。其豐富的API、多種集羣構建模式使得它成爲業界老牌消息中間件,在中小型企業中應用廣泛。

是其性能稍差,在面對高併發的情況下,會出現消息阻塞、堆積、延遲等問題。

默認採用了基於內存的kahaDB進行存儲,如果需要保證消息的可靠性,也可以選擇關係行數據庫進行存儲。

集羣架構模式如下:

Master-Slave模式:通過zookeeper對主從進行管理,正常情況下,從節點不會提供服務。當主節點出現問題後,zookeeper會高效的將主節點下掉,從節點來提供服務。

NetWork模式:兩套主從Master-Slave節點。由網絡聯通,將其變爲分佈式的集羣架構。

Kafka

Kafka是LinkedIn開源的分佈式發佈-訂閱消息系統,目前歸屬於Apache頂級項目。Kafka主要特點就是基於Pull的模式來處理消息消費追求高吞吐量,一開始的目的就是用於日誌收集和傳輸。0.8版本開始支持複製,不支持事務,對消息的重複、丟失、錯誤沒有嚴格要求,適合產生大量數據的互聯網服務的數據收集業務。能夠支持廉價的服務器上以每秒100k條數據的吞吐量。(有ack機制,可以保證不丟失,不能保證不重複。)

高效的讀寫基於操作系統低層的Page Cache。僅僅使用內存管理,不存在內存和磁盤之間的IO操作。

集羣架構模式如下:

通過replicate進行節點間數據的複製,儘量保證數據的可靠性。

RocketMQ

RocketMQ是阿里開源的消息中間件,目前也已經孵化爲Apache頂級項目,它是純Java開發,具有高吞吐量、高可靠性、適合大規模分佈式系統應用的特點。RocketMQ思路起源於Kafka,它對消息的可靠傳輸以及事務性做了優化,目前在阿里集團被廣泛應用於交易、充值、流計算、消息推送、日誌流式處理、binlog分發等場景。

在2.0版本,RocketMQ集羣也是通過Zookeeper進行管理。在3.0之後,放棄Zookeeper,使用NameServer進行集羣的管理和協調。

能夠保障消息的順序消費,提供了豐富的消息拉取等處理模式,消費者可以高效進行水平擴展,能夠承載上億級別數據量級。

可以支持多種集羣架構模式:Master-Slave模式、雙Master-Slave模式、多主多從模式等等。

支持多種刷盤策略:同步雙寫、異步複製。藉助了零拷貝等技術。

集羣架構模式如下:


 

 

技術背景知識介紹

AMQP高級消息隊列協議

AMQP(Advanced Message Queuing Protocol)高級消息隊列協議:高級消息隊列協議。它是應用層協議的一個開放標準,爲面向消息的中間件設計,基於此協議的客戶端與消息中間件可傳遞消息,並不受產品、開發語言燈條件的限制。

AMQP中消息的路由過程和JMS存在一些差別。AMQP中增加了Exchange和Binging的角色。生產者把消息發佈到Exchange上,消息最終到達隊列並被消費者接收,而Binding決定交換器的消息應該發送到哪個隊列。

Erlang語言

Erlang語言最初用於交換機領域的架構模式,這樣使得RabbitMQ在Broker之間進行數據交互的性能非常優秀(Erlang有着和原生Socket一樣的延遲)。

RabbitMQ

RabbitMQ是一個開源的消息代理和隊列服務器,用來通過普通協議在不同的應用之間共享數據(跨平臺跨語言)。RabbitMQ是使用Erlang語言編寫,並且基於AMQP協議實現。

RabbitMQ的優勢:

  • 可靠性(Reliablity):使用了一些機制來保證可靠性,比如持久化、傳輸確認、發佈確認。
  • 靈活的路由(Flexible Routing):在消息進入隊列之前,通過Exchange來路由消息。對於典型的路由功能,Rabbit已經提供了一些內置的Exchange來實現。針對更復雜的路由功能,可以將多個Exchange綁定在一起,也通過插件機制實現自己的Exchange。
  • 消息集羣(Clustering):多個RabbitMQ服務器可以組成一個集羣,形成一個邏輯Broker。
  • 高可用(Highly Avaliable Queues):隊列可以在集羣中的機器上進行鏡像,使得在部分節點出問題的情況下隊列仍然可用。
  • 多種協議(Multi-protocol):支持多種消息隊列協議,如STOMP、MQTT等。
  • 多種語言客戶端(Many Clients):幾乎支持所有常用語言,比如Java、.NET、Ruby等。
  • 管理界面(Management UI):提供了易用的用戶界面,使得用戶可以監控和管理消息Broker的許多方面。
  • 跟蹤機制(Tracing):如果消息異常,RabbitMQ提供了消息的跟蹤機制,使用者可以找出發生了什麼。
  • 插件機制(Plugin System):提供了許多插件,來從多方面進行擴展,也可以編輯自己的插件

RabbitMQ的整體架構


RabbitMQ的消息流轉


 

RabbitMQ各組件功能

  • Broker:標識消息隊列服務器實體.
  • Virtual Host:虛擬主機。標識一批交換機、消息隊列和相關對象。虛擬主機是共享相同的身份認證和加密環境的獨立服務器域。每個vhost本質上就是一個mini版的RabbitMQ服務器,擁有自己的隊列、交換器、綁定和權限機制。vhost是AMQP概念的基礎,必須在鏈接時指定,RabbitMQ默認的vhost是 /。
  • Exchange:交換器,用來接收生產者發送的消息並將這些消息路由給服務器中的隊列。
  • Queue:消息隊列,用來保存消息直到發送給消費者。它是消息的容器,也是消息的終點。一個消息可投入一個或多個隊列。消息一直在隊列裏面,等待消費者連接到這個隊列將其取走。
  • Banding:綁定,用於消息隊列和交換機之間的關聯。一個綁定就是基於路由鍵將交換機和消息隊列連接起來的路由規則,所以可以將交換器理解成一個由綁定構成的路由表。
  • Channel:信道,多路複用連接中的一條獨立的雙向數據流通道。信道是建立在真實的TCP連接內地虛擬鏈接,AMQP命令都是通過信道發出去的,不管是發佈消息、訂閱隊列還是接收消息,這些動作都是通過信道完成。因爲對於操作系統來說,建立和銷燬TCP都是非常昂貴的開銷,所以引入了信道的概念,以複用一條TCP連接。
  • Connection:網絡連接,比如一個TCP連接。
  • Publisher:消息的生產者,也是一個向交換器發佈消息的客戶端應用程序。
  • Consumer:消息的消費者,表示一個從一個消息隊列中取得消息的客戶端應用程序。
  • Message:消息,消息是不具名的,它是由消息頭和消息體組成。消息體是不透明的,而消息頭則是由一系列的可選屬性組成,這些屬性包括routing-key(路由鍵)、priority(優先級)、delivery-mode(消息可能需要持久性存儲[消息的路由模式])等。

RabbitMQ的多種Exchange類型

Exchange分發消息時,根據類型的不同分發策略有區別。目前共四種類型:direct、fanout、topic、headers(headers匹配AMQP消息的header而不是路由鍵(Routing-key),此外headers交換器和direct交換器完全一致,但是性能差了很多,目前幾乎用不到了。所以直接看另外三種類型。)。

direct

消息中的路由鍵(routing key)如果和Binding中的binding key一致,交換器就將消息發到對應的隊列中。路由鍵與隊列名完全匹配。

fanout

每個發到fanout類型交換器的消息都會分到所有綁定的隊列上去。fanout交換器不處理該路由鍵,只是簡單的將隊列綁定到交換器上,每個發送到交換器的消息都會被轉發到與該交換器綁定的所有隊列上。很像子網廣播,每臺子網內的主機都獲得了一份複製的消息。fanout類型轉發消息是最快的。

 

topic

topic交換器通過模式匹配分配消息的路由鍵屬性,將路由鍵和某個模式進行匹配,此時隊列需要綁定到一個模式上。它將路由鍵(routing-key)和綁定鍵(bingding-key)的字符串切分成單詞,這些單詞之間用點隔開。它同樣也會識別兩個通配符:"#"和"*"。#匹配0個或多個單詞,匹配不多不少一個單詞。

 

TTL

 

TTL(Time To Live):生存時間。RabbitMQ支持消息的過期時間,一共兩種。

  • 在消息發送時可以進行指定。通過配置消息體的properties,可以指定當前消息的過期時間。
  • 在創建Exchange時可進行指定。從進入消息隊列開始計算,只要超過了隊列的超時時間配置,那麼消息會自動清除。

 

死信隊列DLX

 

死信隊列(DLX Dead-Letter-Exchange):利用DLX,當消息在一個隊列中變成死信(dead message)之後,它能被重新publish到另一個Exchange,這個Exchange就是DLX。

DLX也是一個正常的Exchange,和一般的Exchange沒有區別,它能在任何的隊列上被指定,實際上就是設置某個隊列的屬性。

當這個隊列中有死信時,RabbitMQ就會自動的將這個消息重新發布到設置的Exchange上去,進而被路由到另一個隊列。

可以監聽這個隊列中消息做相應的處理,這個特性可以彌補RabbitMQ3.0之前支持的immediate參數的功能。

消息變成死信的幾種情況:

  • 消息被拒絕(basic.reject/basic.nack)並且requeue=false
  • 消息TTL過期
  • 隊列達到最大長度

死信隊列設置:需要設置死信隊列的exchange和queue,然後通過routing key進行綁定。只不過我們需要在隊列加上一個參數即可

 

只需要通過監聽該死信隊列即可處理死信消息。還可以通過死信隊列完成延時隊列。

消費端ACK與NACK

消費端 進行消費的時候,如果由於業務異常可以進行日誌的記錄,然後進行補償。由於服務器宕機等嚴重問題,我們需要手動進行ACK保障消費端消費成功。

消費端重回隊列是爲了對沒有成功處理消息,把消息重新返回到Broker。一般來說,實際應用中都會關閉重回隊列,也就是設置爲false。

生產者Confirm機制

  • 消息的確認,是指生產者投遞消息後,如果Broker收到消息,則會給我們生產者一個應答。
  • 生產者進行接受應答,用來確認這條消息是否正常的發送到了Broker,這種方式也是消息的可靠性投遞的核心保障!

如何實現Confirm確認消息?

1、在channel上開啓確認模式:channel.confirmSelect()
2、在channel上開啓監聽:addConfirmListener,監聽成功和失敗的處理結果,根據具體的結果對消息進行重新發送或記錄日誌處理等後續操作。

Return消息機制

Return Listener用於處理一些不可路由的消息

我們的消息生產者,通過指定一個Exchange和Routing,把消息送達到某一個隊列中去,然後我們的消費者監聽隊列進行消息的消費處理操作。

但是在某些情況下,如果我們在發送消息的時候,當前的exchange不存在或者指定的路由key路由不到,這個時候我們需要監聽這種不可達消息,就需要使用到Returrn Listener。

基礎API中有個關鍵的配置項Mandatory:如果爲true,監聽器會收到路由不可達的消息,然後進行處理。如果爲false,broker端會自動刪除該消息。

通過chennel.addReturnListener(ReturnListener rl)傳入已經重寫過handleReturn方法的ReturnListener。

消費端自定義監聽(推模式和拉模式pull/push)

  • 一般通過while循環進行consumer.nextDelivery()方法進行獲取下一條消息進行那個消費。(通過while將拉模式模擬成推模式,但是死循環會耗費CPU資源。)
  • 通過自定義Consumer,實現更加方便、可讀性更強、解耦性更強的方式。(現默認使用的模式,直接訂閱到queue上,如果有數據,就等待mq推送過來)

如何保證冪等性

 

  • 唯一ID+指紋碼機制,利用數據庫主鍵去重

  • 利用redis的原子性去實現

如何保證可靠性?

什麼是生產端的可靠性投遞?

  • 保證消息的成功發出
  • 保障MQ節點的成功接受
  • 發送端收到MQ節點(Broker)確認應答
  • 完善消息的補償機制

解決方案

消息落庫,對消息狀態進行變更。

消息的延遲投遞,做二次確認,回調檢查。


 

消費端如何限流

當海量消息瞬間推送過來,單個客戶端無法同時處理那麼多數據,嚴重會導致系統宕機。這時,需要削峯。

RabbitMQ提供了一種qos(服務質量保證)功能。即 在非自動確認消息的前提下(非ACK),如果一定數目的消息(通過基於consume或者channel設置qos的值)未被確認前,不進行消費新的消息。

Channel模式和Connection模式

參考:https://www.jianshu.com/p/2c2a7cfdd38a

Connection和Channel是spring-amqp中的概念,並非rabbitmq中的概念,官方文檔對Connection和Channel有這樣的描述:

HANNEL模式:程序運行期間ConnectionFactory會維護着一個Connection,所有的操作都會使用這個Connection,但一個Connection中可以有多個Channel,操作rabbitmq之前都必須先獲取到一個Channel,否則就會阻塞(可以通過setChannelCheckoutTimeout()設置等待時間),這些Channel會被緩存(緩存的數量可以通過setChannelCacheSize()設置);

CONNECTION模式:這個模式下允許創建多個Connection,會緩存一定數量的Connection,每個Connection中同樣會緩存一些Channel,除了可以有多個Connection,其它都跟CHANNEL模式一樣。

關於CONNECTION模式中,可以存在多個Connection的使用場景,官方文檔的描述:


 

setChannelCacheSize:設置每個Connection中(注意是每個Connection)可以緩存的Channel數量,注意只是緩存的Channel數量,不是Channel的數量上限,操作rabbitmq之前(send/receive message等)要先獲取到一個Channel,獲取Channel時會先從緩存中找閒置的Channel,如果沒有則創建新的Channel,當Channel數量大於緩存數量時,多出來沒法放進緩存的會被關閉。

注意,改變這個值不會影響已經存在的Connection,隻影響之後創建的Connection。
有時會出現connection closed錯誤。rabbitTemplate作者對於這種問題的解決方案,他給的方案很簡單,單純的增加connection數:


setChannelCheckoutTimeout:當這個值大於0時,channelCacheSize不僅是緩存數量,同時也會變成數量上限,從緩存獲取不到可用的Channel時,不會創建新的Channel,會等待這個值設置的毫秒數,到時間仍然獲取不到可用的Channel會拋出AmqpTimeoutException異常。

同時,在CONNECTION模式,這個值也會影響獲取Connection的等待時間,超時獲取不到Connection也會拋出AmqpTimeoutException異常。

RabbitMQ集羣

RabbitMQ最優秀的功能之一就是內建集羣,這個功能涉及的目的是允許消費者和生產者在節點崩潰的情況下繼續運行,以及通過添加更多的節點來線性擴展消息通信吞吐量。RabbitMQ內部利用Erlang提供的分佈式通信框架OTP來滿足上述需求,使客戶端在失去一個RabbitMQ節點連接的情況下,還是能夠重新連接到集羣中的其他節點繼續勝場、消費信息。
 

RabbitMQ會始終記錄以下四中類型的內部元數據:

  • 隊列元數據:包括隊列名稱和他們的屬性,比如是否可持久化,是否可持久化,是否自動刪除。
  • 交換器元數據:交換器名稱、類型、屬性。
  • 綁定元數據:內部是一張表格,記錄如何將消息路由到隊列。
  • vhost元數據:爲vhost內部的隊列、交換器、綁定提供命名空間和安全屬性。
在單一節點中,RabbitMQ會將所有這些信息存儲在內存中,同時將標記爲可持久化的隊列、交換器、 綁定存儲在硬盤上。存到硬盤上可以確保隊列和交換器在節點重啓後能夠重建。 而在集羣模式下,同樣也提供了兩種選擇:存到硬盤上(獨立節點的默認配置),存在內存中。
 
如果在集羣中創建隊列,集羣只會在單個節點而不是所有節點上創建完整的隊列信息(元數據、狀態、內容)。結果是隻有隊列的所有者節點知道有關隊列的所有信息,因此當集羣節點崩潰時,該節點的隊列和綁定就消失了,並且任何匹配該隊列的綁定的新消息也丟失了。還好RabbitMQ 2.6.0之後提供了鏡像隊列以避免集羣節點故障導致的隊列內容不可用。
RabbitMQ 集羣中可以共享 user、vhost、exchange等,所有的數據和狀態都是必須在所有節點上覆制的,例外就是上面所說的消息隊列。RabbitMQ 節點可以動態的加入到集羣中。
 

當在集羣中聲明隊列、交換器、綁定的時候,這些操作會直到所有集羣節點都成功提交元數據變更後才返回。集羣中有內存節點和磁盤節點兩種類型,內存節點雖然不寫入磁盤,但是它的執行比磁盤節點要好。內存節點可以提供出色的性能,磁盤節點能保障配置信息在節點重啓後仍然可用,那集羣中如何平衡這兩者呢?

RabbitMQ 只要求集羣中至少有一個磁盤節點,所有其他節點可以是內存節點,當節點加入或離開集羣時,它們必須要將該變更通知到至少一個磁盤節點。如果只有一個磁盤節點,剛好又是該節點崩潰了,那麼集羣可以繼續路由消息,但不能創建隊列、創建交換器、創建綁定、添加用戶、更改權限、添加或刪除集羣節點。換句話說集羣中的唯一磁盤節點崩潰的話,集羣仍然可以運行,但直到該節點恢復,否則無法更改任何東西。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章