20.Spring-Boot中RabbitMQ的使用之概念原理理解(重要)
RabbitMQ 即一個訊息佇列,主要是用來實現應用程式的非同步和解耦,同時也能起到訊息緩衝,訊息分發的作用。 AMQP,即Advanced Message Queuing Protocol,一個提供統一訊息服務的應用層標準高階訊息佇列協議,是應用層協議的一個開放標準,為面向訊息的中介軟體設計。AMQP從一開始就設計成為開放標準,以解決眾多的訊息佇列需求和拓撲結構問題。
Rabbit,兔子的意思,畢竟兔子的行動非常迅速的動物而且繁殖起來也是非常瘋狂的,把它用於分散式軟體命名在合適不過了。
在今天,RabbitMQ並不是開放訊息通訊的唯一選擇。像ActiveMQ,ZeroMQ和Apache Qpid都提供了不同的開源訊息佇列方案。為什麼要選擇RabbitMQ:
除了Qpid之外,RabbitMQ是唯一實現了AMQP標準的代理伺服器。
正式由於Erlang,RabbitMQ叢集不可意思的簡單。
每個人的經歷不同,但我們發現RabbitMQ比競爭對手更可靠,更能放置崩潰。
大致流程:
從生產者到消費者的訊息流
(1)對於生產者
生產者(producer)建立訊息,然後釋出給(傳送)到代理伺服器(RabbitMQ)。什麼是訊息呢?訊息包含兩個部分:有效負載(payload)和標籤(label)。有效負載就是想要傳輸的資料。它可以是任何內容,JSON資料XML資料或者TXT資料。RabbitMQ不會在意這些。標籤就更有趣,它描述了有效負載,並且RabbitMQ用它來決定誰將獲取訊息的拷貝。不同於TCP協議得是,當你明確指定傳送方和接收方時,AMQP只是用標籤表述
這條訊息,然後會把訊息交給Rabbit,Rabbit會根據標籤把訊息傳送給感興趣的接收方。這種通訊方式是一種“發後即忘”的單向方式。
(2)對於消費者
對於消費者,它連線到代理伺服器上,訂閱到佇列(queue)上,把訊息佇列想象成一個具有名字的郵箱。每當訊息到達特定的郵箱時,RabbitMQ會將其傳送給其中一個訂閱的/監聽的消費者。當消費者接收到訊息時,它只得到訊息的一部分:有效負載。在訊息的路由過程中,訊息的標籤並沒有隨有效負載一同傳遞。RabbitMQ甚至不會告訴你誰是生產者/傳送訊息。就好比你拿起信件,卻發現所有的信封都是空白的。想要知道這條訊息是否是你某個朋友zhangsan發來的唯一方式就是在信裡簽了名。同時,如果需要明白知道誰生產的AMQP訊息的話,就要看生產者是否把訊息的傳送方放入有效負載中。
(3)總結:
整個過程很簡單:生產者建立訊息,消費者收到訊息。你的應用程式可以使生產者,也可以是消費者,向其他應用程式傳送訊息。或者作為一個消費者,接收訊息。不過在此之前它必須建立一條通道(channel)。
通道
首先要連線到Rabbit,才能消費或者釋出訊息。你在應用程式和Rabbit代理伺服器之間建立一條TCP連線。
一旦TCP連線開啟,應用程式就可以建立一條AMQP通道。通道是建立在”真實的“TCP連線內的虛擬連線。
AMQP命令都是通過通道傳送出去的。每條通道都會被指派一個唯一ID(AMQP庫被幫你記住)。不論是釋出訊息、訂閱佇列或者接受訊息。這些動作都是通過通道完成的。
為什麼需要通道?為什麼不直接通過TCP連線傳送AMQP命令。重要原因在於對作業系統來說建立和銷燬TCP會話是非常昂貴的開銷。
假設你只進行TCP連線,那麼每個執行緒都需要自行連線到Rabbit。也就是說高峰期有每秒成百上千條連線。這不僅造成TCP連線的巨大浪費,而且作業系統每秒也就只能建立很少數量的連線。
一條TCP連線上建立多條通道是沒有限制。把它想象成一束光纖就可以了。每條電纜中的光纖都可以傳輸(就像一條通道)。一條電纜中有許多光纖束,允許所有連線的執行緒通過多條光纖同時傳輸和接收。TCP連線就像電纜,而AMQP通道就像一條條獨立光纖束。
佇列
從概念上來講:AMQP訊息路由必須有三部分:交換器,佇列和繫結。生產者把訊息釋出到交換器上;訊息最終到達佇列,並被消費者接收;繫結決定了訊息如何從路由器路由到特定的佇列。
AMQP棧:交換器,繫結,佇列
佇列就是如同具有名字的郵箱。訊息達到佇列中等待消費者。消費者通過以下兩種方式從特定的佇列中接收訊息:
(1)通過訂閱模式。這樣做會將通道置為接收模式,直到取消佇列的訂閱為止。訂閱了訊息後,消費者在消費(或者拒絕)最近接收的那條訊息後,就能從佇列中自動接收下一條訊息。
(2)也可從佇列中獲取單條訊息不持續訂閱。
如果至少有一個消費者訂閱了佇列的話,訊息就會立即傳送給這些訂閱的消費者。但是如果訊息到達了無人訂閱佇列呢?在這種情況下,訊息會在佇列中等待。一旦有消費者訂閱到該佇列,那麼佇列上的訊息就會發送給消費者。
當有多個消費者訂閱了同一個佇列時,訊息時如何分發的。
當Rabbit佇列擁有多個消費者時,佇列接收到訊息將以迴圈的方式傳送給消費者。每個訊息只會傳送給一個訂閱的消費者。假設有 send_queue佇列,消費者zhangsan和lisi訂閱到send_queue佇列。當訊息達到send_queue佇列時,訊息投遞方式如下:
【1】訊息Message_A到達佇列send_queue。
【2】RabbitMQ 把訊息Message_A傳送給zhangsan
【3】zhangsan確認收到訊息Message_A
【4】RabbitMQ把訊息Message_A中send_queue刪除
【5】訊息Message_B到達佇列send_queue。
【6】RabbitMQ 把訊息Message_B傳送給lisi
【7】lisi確認收到訊息Message_B
【8】RabbitMQ把訊息Message_B中send_queue刪除
從上面過程可知,消費者對訊息進行了確認。消費者接收到每一條訊息都必須進行確認。如果消費者收到一條訊息,然後確認之前 從Rabbit斷開連線(或者從佇列上取消訂閱),RabbitMQ會認為訊息沒有分發,然後重新分發給下一個訂閱者。如果你的應用程式崩潰, 這樣做可以確保訊息會被髮送給另外一個消費者進行處理。
總結
佇列是AMQP訊息的通訊的基礎模組
- 為訊息提供處所,訊息在此等待消費。
- 對於負載均衡來說,隊裡是絕佳方案。只需附加一堆消費者,並讓RabbitMQ以迴圈的方式均勻的分配發來的訊息。
- 佇列時Rabbit中訊息的最後的終點。
交換器和繫結
前面已經介紹了消費者如何從佇列中獲取訊息。現在的問題是,訊息如何到達佇列。那就需要AMQP的交換器和綁定了。當你想將訊息投遞到佇列時,你通過把訊息傳送給交換器來完成。然後,根據確定的規則,RabbitMQ將會決定訊息該投遞到那個佇列。這些規則就被稱為路由鍵(routing key).佇列通過路由建繫結到交換器。當你把訊息傳送給代理伺服器時,訊息將擁有一個路由鍵,即便是空RabbitMQ也會將其繫結使用的路由鍵進行匹配。如果匹配的話,那麼訊息將會投遞到該佇列。
伺服器會根據路由鍵將訊息從交換器路由到佇列,但是它是如何處理投遞到多個佇列的情況的。協議中定義的不同型別交換器發揮了作用。一共有四種:direct,fanout,topic,headers.
- direct交換器
如果路由鍵匹配的話,訊息就被投遞到對應的佇列。即”先匹配, 再投送”. 即在繫結時設定一個 routing_key, 訊息的routing_key 匹配時, 才會被交換器投送到繫結的佇列中去.
direct交換器訊息流
伺服器必須實現direct型別交換器,包含一個空白字串名稱的預設交換器。當宣告一個佇列時,他會自動化繫結到預設的交換器,並以佇列名作為路由鍵。(這也是預設的交換器)
- fanout交換器
這種型別交換器將收到的訊息廣播到繫結的佇列上。訊息通訊模式很簡單:當你傳送一條訊息到fanout交換器,它會把訊息投遞給所有附加在此交換上的佇列。舉例來說:一個web應用可能需要在使用者上傳新圖片時,使用者相簿必須清除快取,同時使用者應該得到相應的積分獎勵。可以將兩個佇列繫結到圖片上傳伺服器上。一個用於清除快取,一個用於增加積分。從這種場景你可以瞭解到,使用交換器、繫結和佇列比直接指定佇列傳送訊息要有優勢。
fanout交換器訊息流
- topic交換器
topic交換器轉發訊息主要是根據萬用字元。 在這種交換器下,佇列和交換器的繫結會定義一種路由模式,那麼,萬用字元就要在這種路由模式和路由鍵之間匹配後交換機才能轉發訊息。
在這種交換機模式下:
路由鍵必須是一串字元,用句號(.) 隔開,比如說 agreements.us,或者 agreements.eu.stockholm 等。
路由模式必須包含一個 星號(*),主要用於匹配路由鍵指定位置的一個單詞,比如說,一個路由模式是這樣子:agreements..b.*,那麼就只能匹配路由鍵是這樣子的:第一個單詞是 agreements,第四個單詞是 b。
井號(#)就表示相當於一個或者多個單詞,例如一個匹配模式是agreements.eu.berlin.#,那麼,以agreements.eu.berlin開頭的路由鍵都是可以的。
具體程式碼傳送的時候還是一樣,第一個引數表示交換機,第二個引數表示routing key,第三個引數即訊息。如下:
rabbitTemplate.convertAndSend("testTopicExchange","key1.a.c.key2", " this is RabbitMQ!");
topic 和 direct 類似, 只是匹配上支援了”模式”, 在”點分”的 routing_key 形式中, 可以使用兩個萬用字元:
*表示一個詞.
#表示零個或多個詞.
topice交換器訊息流
- headers交換器
headers 也是根據規則匹配, 相較於 direct 和 topic 固定地使用 routing_key , headers 則是一個自定義匹配規則的型別. 在佇列與交換器繫結時, 會設定一組鍵值對規則, 訊息中也包括一組鍵值對( headers 屬性), 當這些鍵值對有一對, 或全部匹配時, 訊息被投送到對應佇列.
微信公眾號:
JAVA程式猿成長之路
分享學習資源,學習方法,記錄程式設計師生活。