什麼是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
topic
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確認消息?
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)確認應答
- 完善消息的補償機制
解決方案
消息落庫,對消息狀態進行變更。
消息的延遲投遞,做二次確認,回調檢查。
消費端如何限流
當海量消息瞬間推送過來,單個客戶端無法同時處理那麼多數據,嚴重會導致系統宕機。這時,需要削峯。
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會始終記錄以下四中類型的內部元數據:
- 隊列元數據:包括隊列名稱和他們的屬性,比如是否可持久化,是否可持久化,是否自動刪除。
- 交換器元數據:交換器名稱、類型、屬性。
- 綁定元數據:內部是一張表格,記錄如何將消息路由到隊列。
- vhost元數據:爲vhost內部的隊列、交換器、綁定提供命名空間和安全屬性。
當在集羣中聲明隊列、交換器、綁定的時候,這些操作會直到所有集羣節點都成功提交元數據變更後才返回。集羣中有內存節點和磁盤節點兩種類型,內存節點雖然不寫入磁盤,但是它的執行比磁盤節點要好。內存節點可以提供出色的性能,磁盤節點能保障配置信息在節點重啓後仍然可用,那集羣中如何平衡這兩者呢?
RabbitMQ 只要求集羣中至少有一個磁盤節點,所有其他節點可以是內存節點,當節點加入或離開集羣時,它們必須要將該變更通知到至少一個磁盤節點。如果只有一個磁盤節點,剛好又是該節點崩潰了,那麼集羣可以繼續路由消息,但不能創建隊列、創建交換器、創建綁定、添加用戶、更改權限、添加或刪除集羣節點。換句話說集羣中的唯一磁盤節點崩潰的話,集羣仍然可以運行,但直到該節點恢復,否則無法更改任何東西。