RabbitMQ概念詳解

burpee發表於2020-10-22

https://blog.csdn.net/bestmy/article/details/84304964

 

一.AMQP協議

1. AMQP協議簡介

AMQP,即Advanced Message Queuing Protocol,一個提供統一訊息服務的應用層標準高階訊息佇列協議,是應用層協議的一個開放標準,為面向訊息的中介軟體設計。基於此協議的客戶端與訊息中介軟體可傳遞訊息,並不受客戶端/中介軟體不同產品,不同的開發語言等條件的限制。Erlang中的實現有 RabbitMQ等。

2. 功能範圍

儲存轉發(多個訊息傳送者,單個訊息接收者)。
分散式事務(多個訊息傳送者,多個訊息接收者)。
釋出訂閱(多個訊息傳送者,多個訊息接收者)。
基於內容的路由(多個訊息傳送者,多個訊息接收者)。
檔案傳輸佇列(多個訊息傳送者,多個訊息接收者)。
點對點連線(單個訊息傳送者,單個訊息接收者)。

3. 技術術語

AMQP模型(AMQP Model):一個由關鍵實體和語義表示的邏輯框架,遵從AMQP規範的伺服器必須提供這些實體和語義。為了實現本規範中定義的語義,客戶端可以傳送命令來控制AMQP伺服器。
連線(Connection):一個網路連線,比如TCP/IP套接字連線。
會話(Session):端點之間的命名對話。在一個會話上下文中,保證“恰好傳遞一次”。
通道(Channel):多路複用連線中的一條獨立的雙向資料流通道。為會話提供物理傳輸介質。
客戶端(Client):AMQP連線或者會話的發起者。AMQP是非對稱的,客戶端生產和消費訊息,伺服器儲存和路由這些訊息。
伺服器(Server):接受客戶端連線,實現AMQP訊息佇列和路由功能的程式。也稱為“訊息代理”。
端點(Peer):AMQP對話的任意一方。一個AMQP連線包括兩個端點(一個是客戶端,一個是伺服器)。
搭檔(Partner):當描述兩個端點之間的互動過程時,使用術語“搭檔”來表示“另一個”端點的簡記法。比如我們定義端點A和端點B,當它們進行通訊時,端點B是端點A的搭檔,端點A是端點B的搭檔。
片段集(Assembly):段的有序集合,形成一個邏輯工作單元。
段(Segment):幀的有序集合,形成片段集中一個完整子單元。
幀(Frame):AMQP傳輸的一個原子單元。一個幀是一個段中的任意分片。
控制(Control):單向指令,AMQP規範假設這些指令的傳輸是不可靠的。
命令(Command):需要確認的指令,AMQP規範規定這些指令的傳輸是可靠的。
異常(Exception):在執行一個或者多個命令時可能發生的錯誤狀態。
類(Class):一批用來描述某種特定功能的AMQP命令或者控制。
訊息頭(Header):描述訊息資料屬性的一種特殊段。
訊息體(Body):包含應用程式資料的一種特殊段。訊息體段對於伺服器來說完全透明——伺服器不能檢視或者修改訊息體。
訊息內容(Content):包含在訊息體段中的的訊息資料。
交換器(Exchange):伺服器中的實體,用來接收生產者傳送的訊息並將這些訊息路由給伺服器中的佇列。
交換器型別(Exchange Type):基於不同路由語義的交換器類。
訊息佇列(Message Queue):一個命名實體,用來儲存訊息直到傳送給消費者。
繫結器(Binding):訊息佇列和交換器之間的關聯。
繫結器關鍵字(Binding Key):繫結的名稱。一些交換器型別可能使用這個名稱作為定義繫結器路由行為的模式。
路由關鍵字(Routing Key):一個訊息頭,交換器可以用這個訊息頭決定如何路由某條訊息。
持久儲存(Durable):一種伺服器資源,當伺服器重啟時,儲存的訊息資料不會丟失。
臨時儲存(Transient):一種伺服器資源,當伺服器重啟時,儲存的訊息資料會丟失。
持久化(Persistent):伺服器將訊息儲存在可靠磁碟儲存中,當伺服器重啟時,訊息不會丟失。
非持久化(Non-Persistent):伺服器將訊息儲存在記憶體中,當伺服器重啟時,訊息可能丟失。
消費者(Consumer):一個從訊息佇列中請求訊息的客戶端應用程式。
生產者(Producer):一個向交換器釋出訊息的客戶端應用程式。
虛擬主機(Virtual Host):一批交換器、訊息佇列和相關物件。虛擬主機是共享相同的身份認證和加密環境的獨立伺服器域。客戶端應用程式在登入到伺服器之後,可以選擇一個虛擬主機。

二.RabbitMQ

RabbitMQ模擬網站:http://tryrabbitmq.com/

1. RabbitMQ基礎概念

通常我們談到訊息佇列服務, 會有三個概念: 發訊息者、訊息佇列、收訊息者。RabbitMQ 在這個基本概念之上, 多做了一層抽象, 在發訊息者和佇列之間, 加入了交換器 (Exchange)。這樣發訊息者和訊息佇列就沒有直接聯絡,轉而變成發訊息者把訊息發給交換器,交換器根據排程策略再把訊息轉發給訊息佇列。
訊息生產者並沒有直接將訊息傳送給訊息佇列,而是通過建立與Exchange的Channel,將訊息傳送給Exchange。Exchange根據路由規則,將訊息轉發給指定的訊息佇列。訊息佇列儲存訊息,等待消費者取出訊息。消費者通過建立與訊息佇列相連的Channel,從訊息佇列中獲取訊息。
在這裡插入圖片描述
1.Channel(通道):多路複用連線中的一條獨立的雙向資料流通道。通道是建立在真實的TCP連線內的虛擬連線,複用TCP連線的通道。
2.Producer(訊息的生產者):向訊息佇列釋出訊息的客戶端應用程式。
3.Consumer(訊息的消費者):從訊息佇列取得訊息的客戶端應用程式。
4.Message(訊息):訊息由訊息頭和訊息體組成。訊息體是不透明的,而訊息頭則由一系列的可選屬性組成,這些屬性包括routing-key(路由鍵)、priority(訊息優先權)、delivery-mode(是否永續性儲存)等。
5.Routing Key(路由鍵):訊息頭的一個屬性,用於標記訊息的路由規則,決定了交換機的轉發路徑。最大長度255 位元組。
6.Queue(訊息佇列):儲存訊息的一種資料結構,用來儲存訊息,直到訊息傳送給消費者。它是訊息的容器,也是訊息的終點。一個訊息可投入一個或多個佇列。訊息一直在佇列裡面,等待消費者連線到這個佇列將訊息取走。需要注意,當多個消費者訂閱同一個Queue,這時Queue中的訊息會被平均分攤給多個消費者進行處理,而不是每個消費者都收到所有的訊息並處理,每一條訊息只能被一個訂閱者接收。
7.Exchange(交換器|路由器):提供Producer到Queue之間的匹配,接收生產者傳送的訊息並將這些訊息按照路由規則轉發到訊息佇列。交換器用於轉發訊息,它不會儲存訊息 ,如果沒有 Queue繫結到 Exchange 的話,它會直接丟棄掉 Producer 傳送過來的訊息。交換器有四種訊息排程策略(下面會介紹),分別是fanout, direct, topic, headers。
8.Binding(繫結):用於建立Exchange和Queue之間的關聯。一個繫結就是基於Binding Key將Exchange和Queue連線起來的路由規則,所以可以將交換器理解成一個由Binding構成的路由表。
6.Binding Key(繫結鍵):Exchange與Queue的繫結關係,用於匹配Routing Key。最大長度255 位元組。
7.Broker:RabbitMQ Server,伺服器實體。

2. Exchange訊息排程策略

排程策略是指Exchange在收到生產者傳送的訊息後依據什麼規則把訊息轉發到一個或多個佇列中儲存。排程策略與三個因素相關:Exchange Type(Exchange的型別),Binding Key(Exchange和Queue的繫結關係),訊息的標記資訊(Routing Key和headers)。
Exchange根據訊息的Routing Key和Exchange繫結Queue的Binding Key分配訊息。生產者在將訊息傳送給Exchange的時候,一般會指定一個Routing Key,來指定這個訊息的路由規則,而這個Routing Key需要與Exchange Type及Binding Key聯合使用才能最終生效。
在Exchange Type與Binding Key固定的情況下(一般這些內容都是固定配置好的),我們的生產者就可以在傳送訊息給Exchange時,通過指定Routing Key來決定訊息流向哪裡。

1. Fanout (訂閱模式|廣播模式)

在這裡插入圖片描述
交換器會把所有傳送到該交換器的訊息路由到所有與該交換器繫結的訊息佇列中。訂閱模式
與Binding Key和Routing Key無關,交換器將接受到的訊息分發給有繫結關係的所有訊息佇列佇列(不論Binding Key和Routing Key是什麼)。類似於子網廣播,子網內的每臺主機都獲得了一份複製的訊息。Fanout交換機轉發訊息是最快的。
在這裡插入圖片描述

2. Direct(路由模式)

在這裡插入圖片描述
精確匹配:當訊息的Routing Key與 Exchange和Queue 之間的Binding Key完全匹配,如果匹配成功,將訊息分發到該Queue。只有當Routing Key和Binding Key完全匹配的時候,訊息佇列才可以獲取訊息。Direct是Exchange的預設模式。
RabbitMQ預設提供了一個Exchange,名字是空字串,型別是Direct,繫結到所有的Queue(每一個Queue和這個無名Exchange之間的Binding Key是Queue的名字)。所以,有時候我們感覺不需要交換器也可以傳送和接收訊息,但是實際上是使用了RabbitMQ預設提供的Exchange。
在這裡插入圖片描述

3.Topic (萬用字元模式)

在這裡插入圖片描述
按照正規表示式模糊匹配:用訊息的Routing Key與 Exchange和Queue 之間的Binding Key進行模糊匹配,如果匹配成功,將訊息分發到該Queue。
Routing Key是一個句點號“. ”分隔的字串(我們將被句點號“. ”分隔開的每一段獨立的字串稱為一個單詞)。Binding Key與Routing Key一樣也是句點號“. ”分隔的字串。Binding Key中可以存在兩種特殊字元“ * ”與“#”,用於做模糊匹配,其中“*”用於匹配一個單詞,“#”用於匹配多個單詞(可以是零個)。
在這裡插入圖片描述

4. Headers(鍵值對模式)

Headers不依賴於Routing Key與Binding Key的匹配規則來轉發訊息,交換器的路由規則是通過訊息頭的Headers屬性來進行匹配轉發的,類似HTTP請求的Headers。
在繫結Queue與Exchange時指定一組鍵值對,鍵值對的Hash結構中要求攜帶一個鍵“x-match”,這個鍵的Value可以是any或all,代表訊息攜帶的Hash是需要全部匹配(all),還是僅匹配一個鍵(any)。
當訊息傳送到Exchange時,交換器會取到該訊息的headers,對比其中的鍵值對是否完全匹配Queue與Exchange繫結時指定的鍵值對;如果完全匹配則訊息會路由到該Queue,否則不會路由到該Queue。Headers交換機的優勢是匹配的規則不被限定為字串(String),而是Object型別。
在這裡插入圖片描述

3.RPC

在這裡插入圖片描述
MQ本身是基於非同步的訊息處理,前面的示例中所有的生產者(P)將訊息傳送到RabbitMQ後不會知道消費者(C)處理成功或者失敗,甚至連有沒有消費者來處理這條訊息都不知道。但實際的應用場景中,我們很可能需要一些同步處理,需要同步等待服務端將我的訊息處理完成後再進行下一步處理。這相當於RPC(Remote Procedure Call,遠端過程呼叫)。
在這裡插入圖片描述
RabbitMQ中實現RPC的機制是:
一. 生產者傳送請求(訊息)時,在訊息的屬性(MessageProperties,在AMQP協議中定義了14個屬性,這些屬性會隨著訊息一起傳送)中設定兩個屬性值replyTo(一個Queue名稱,用於告訴消費者處理完成後將通知我的訊息傳送到這個Queue中)和correlationId(此次請求的標識號,消費者處理完成後需要將此屬性返還,生產者將根據這個id瞭解哪條請求被成功執行了或執行失敗)。
二. 消費者收到訊息並處理。
三. 消費者處理完訊息後,將生成一條應答訊息到replyTo指定的Queue,同時帶上correlationId屬性。
四. 生產者之前已訂閱replyTo指定的Queue,從中收到伺服器的應答訊息後,根據其中的correlationId屬性分析哪條請求被執行了,根據執行結果進行後續業務處理。

4. 訊息確認:Message acknowledgment

在實際應用中,可能會發生消費者收到Queue中的訊息,但沒有處理完成就當機(或出現其他意外)的情況,這種情況下就可能會導致訊息丟失。為了避免這種情況發生,我們可以要求消費者在消費完訊息後傳送一個回執給RabbitMQ,RabbitMQ收到訊息回執(Message acknowledgment)後才將該訊息從Queue中移除;如果RabbitMQ沒有收到回執並檢測到消費者的RabbitMQ連線斷開,則RabbitMQ會將該訊息傳送給其他消費者(如果存在多個消費者)進行處理。這裡不存在Timeout概念,一個消費者處理訊息時間再長也不會導致該訊息被髮送給其他消費者,除非它的RabbitMQ連線斷開。
這裡會產生另外一個問題,如果我們的開發人員在處理完業務邏輯後,忘記傳送回執給RabbitMQ,這將會導致嚴重的問題,Queue中堆積的訊息會越來越多,消費者重啟後會重複消費這些訊息並重復執行業務邏輯。
如果我們採用no-ack的方式進行確認,也就是說,每次Consumer接到資料後,而不管是否處理完成,RabbitMQ會立即把這個Message標記為完成,然後從queue中刪除了。

5.訊息持久化:Message durability

如果我們希望即使在RabbitMQ服務重啟的情況下,也不會丟失訊息,我們可以將Queue與Message都設定為可持久化的(durable),這樣可以保證絕大部分情況下我們的RabbitMQ訊息不會丟失。但依然解決不了小概率丟失事件的發生(比如RabbitMQ伺服器已經接收到生產者的訊息,但還沒來得及持久化該訊息時RabbitMQ伺服器就斷電了),如果我們需要對這種小概率事件也要管理起來,那麼我們要用到事務(下面再講)。

6.分發機制

我們在應用程式使用訊息系統時,一般情況下生產者往佇列裡插入資料時速度是比較快的,但是消費者消費資料往往涉及到一些業務邏輯處理導致速度跟不上生產者生產資料。因此如果一個生產者對應一個消費者的話,很容易導致很多訊息堆積在佇列裡。這時,就得使用工作佇列了。一個佇列有多個消費者同時消費資料。
工作佇列有兩種分發資料的方式:輪詢分發(Round-robin)和 公平分發(Fair dispatch)。輪詢分發:佇列給每一個消費者傳送數量一樣的資料。公平分發:消費者設定每次從佇列中取一條資料,並且消費完後手動應答,繼續從佇列取下一個資料。

①.輪詢分發:Round-robin dispatching

如果工作佇列中有兩個消費者,兩個消費者得到的資料量一樣的,並不會因為兩個消費者處理資料速度不一樣使得兩個消費者取得不一樣數量的資料。但是這種分發方式存在著一些隱患,消費者雖然得到了訊息,但是如果消費者沒能成功處理業務邏輯,在RabbitMQ中也不存在這條訊息。就會出現訊息丟失並且業務邏輯沒能成功處理的情況。

②.公平分發:Fair dispatch

消費者設定每次從佇列裡取一條資料,並且關閉自動回覆機制,每次取完一條資料後,手動回覆並繼續取下一條資料。與輪詢分發不同的是,當每個消費都設定了每次只會從佇列取一條資料時,並且關閉自動應答,在每次處理完資料後手動給佇列傳送確認收到資料。這樣佇列就會公平給每個訊息費者傳送資料,消費一條再發第二條,而且可以在管理介面中看到資料是一條條隨著消費者消費完從而減少的,並不是一下子全部分發完了。採用公平分發方式就不會出現訊息丟失並且業務邏輯沒能成功處理的情況。

7. 事務

對事務的支援是AMQP協議的一個重要特性。假設當生產者將一個持久化訊息傳送給伺服器時,因為consume命令本身沒有任何Response返回,所以即使伺服器崩潰,沒有持久化該訊息,生產者也無法獲知該訊息已經丟失。如果此時使用事務,即通過txSelect()開啟一個事務,然後傳送訊息給伺服器,然後通過txCommit()提交該事務,即可以保證,如果txCommit()提交了,則該訊息一定會持久化,如果txCommit()還未提交即伺服器崩潰,則該訊息不會伺服器接收。當然Rabbit MQ也提供了txRollback()命令用於回滾某一個事務。

8. Confirm機制

使用事務固然可以保證只有提交的事務,才會被伺服器執行。但是這樣同時也將客戶端與訊息伺服器同步起來,這背離了訊息佇列解耦的本質。Rabbit MQ提供了一個更加輕量級的機制來保證生產者可以感知伺服器訊息是否已被路由到正確的佇列中——Confirm。如果設定channel為confirm狀態,則通過該channel傳送的訊息都會被分配一個唯一的ID,然後一旦該訊息被正確的路由到匹配的佇列中後,伺服器會返回給生產者一個Confirm,該Confirm包含該訊息的ID,這樣生產者就會知道該訊息已被正確分發。對於持久化訊息,只有該訊息被持久化後,才會返回Confirm。Confirm機制的最大優點在於非同步,生產者在傳送訊息以後,即可繼續執行其他任務。而伺服器返回Confirm後,會觸發生產者的回撥函式,生產者在回撥函式中處理Confirm資訊。如果訊息伺服器發生異常,導致該訊息丟失,會返回給生產者一個nack,表示訊息已經丟失,這樣生產者就可以通過重發訊息,保證訊息不丟失。Confirm機制在效能上要比事務優越很多。但是Confirm機制,無法進行回滾,就是一旦伺服器崩潰,生產者無法得到Confirm資訊,生產者其實本身也不知道該訊息是否已經被持久化,只有繼續重發來保證訊息不丟失,但是如果原先已經持久化的訊息,並不會被回滾,這樣佇列中就會存在兩條相同的訊息,系統需要支援去重。

9.Alternate Exchange(代替交換器)

Alternate Exchange是Rabbitmq自己擴充套件的功能,不是AMQP協議定義的。
建立Exchange指定該Exchange的Alternate Exchange,傳送訊息的時候如果Exchange沒有成功把訊息路由到佇列中去,這就會將此訊息路由到Alternate Exchange屬性指定的Exchange上了。需要在建立Exchange時新增alternate-exchange屬性。如果Alternate Exchange也沒能成功把訊息路由到佇列中去,這個訊息就會丟失。可以觸發publish confirm機制,表示這個訊息沒有確認。
建立交換器時需要指定如下屬性
Map<String,Object> argsMap = new HashMap<>();
argsMap.put(“alternate-exchange”,“Alternate Exchange Name”);

10.TTL(生存時間)

RabbitMQ允許您為訊息和佇列設定TTL(生存時間)。 可以使用可選的佇列引數或策略完成(推薦使用後一個選項)。 可以為單個佇列,一組佇列或單個訊息應用訊息TTL。

設定訊息的過期時間
MessageProperties messageProperties = new MessageProperties();
messageProperties.setExpiration(“30000”);

設定佇列中訊息的過期時間
在宣告一個佇列時,可以指定佇列中訊息的過期時間,需要新增x-message-ttl屬性。
Map<String, Object> arguments = new HashMap<>();
arguments.put(“x-message-ttl”,30000);
如果同時制定了Message TTL,Queue TTL,則時間短的生效。

11.Queue Length Limit(佇列長度限制)

可以設定佇列中訊息數量的限制,如果測試佇列中最多隻有5個訊息,當第六條訊息傳送過來的時候,會刪除最早的那條訊息。佇列中永遠只有5條訊息。
使用程式碼宣告含有x-max-length和x-max-length-bytes屬性的佇列
Max length(x-max-length) 用來控制佇列中訊息的數量。
如果超出數量,則先到達的訊息將會被刪除掉。

Max length bytes(x-max-length-bytes) 用來控制佇列中訊息總的大小。
如果超過總大小,則最先到達的訊息將會被刪除,直到總大小不超過x-max-length-byte為止。

Map<String, Object> arguments = new HashMap<>();
//表示佇列中最多存放三條訊息
arguments.put(“x-max-length”,3);
Map<String, Object> arguments = new HashMap<>();
//佇列中訊息總的空間大小
arguments.put(“x-max-length-bytes”,10);

12.Dead Letter Exchange(死信交換器)

在佇列上指定一個Exchange,則在該佇列上發生如下情況,
1.訊息被拒絕(basic.reject or basic.nack),且requeue=false
2.訊息過期而被刪除(TTL)
3.訊息數量超過佇列最大限制而被刪除
4.訊息總大小超過佇列最大限制而被刪除

就會把該訊息轉發到指定的這個exchange
需要定義了x-dead-letter-exchange屬性,同時也可以指定一個可選的x-dead-letter-routing-key,表示預設的routing-key,如果沒有指定,則使用訊息原來的routeing-key進行轉發

當定義佇列時指定了x-dead-letter-exchange(x-dead-letter-routing-key視情況而定),並且消費端執行拒絕策略的時候將訊息路由到指定的Exchange中去。
我們知道還有二種情況會造成訊息轉發到死信佇列。
一種是訊息過期而被刪除,可以使用這個方式使的rabbitmq實現延遲佇列的作用。還有一種就是訊息數量超過佇列最大限制而被刪除或者訊息總大小超過佇列最大限制而被刪除

13.priority queue(優先順序佇列)

宣告佇列時需要指定x-max-priority屬性,並設定一個優先順序數值

訊息優先順序屬性
MessageProperties messageProperties = new MessageProperties();
messageProperties.setPriority(priority);

如果設定的優先順序小於等於佇列設定的x-max-priority屬性,優先順序有效。
如果設定的優先順序大於佇列設定的x-max-priority屬性,則優先順序失效。

建立優先順序佇列,需要增加x-max-priority引數,指定一個數字。表示最大的優先順序,建議優先順序設定為1~10之間。
傳送訊息的時候,需要設定priority屬性,最好不要超過上面指定的最大的優先順序。
如果生產端傳送很慢,消費者訊息很快,則有可能不會嚴格的按照優先順序來進行消費。
第一,如果傳送的訊息的優先順序屬性小於設定的佇列屬性x-max-priority值,則按優先順序的高低進行消費,數字越高則優先順序越高。
第二,如果傳送的訊息的優先順序屬性都大於設定的佇列屬性x-max-priority值,則設定的優先順序失效,按照入佇列的順序進行消費。
第三,如果消費端一直進行監聽,而傳送端一條條的傳送訊息,優先順序屬性也會失效。

RabbitMQ不能保證訊息的嚴格的順序消費。

14.延遲佇列

顧名思義,延遲佇列就是進入該佇列的訊息會被延遲消費的佇列。而一般的佇列,訊息一旦入隊了之後就會被消費者馬上消費。
延遲佇列多用於需要延遲工作的場景。最常見的是以下兩種場景:
①延遲消費。比如:
使用者生成訂單之後,需要過一段時間校驗訂單的支付狀態,如果訂單仍未支付則需要及時地關閉訂單。
使用者註冊成功之後,需要過一段時間比如一週後校驗使用者的使用情況,如果發現使用者活躍度較低,則傳送郵件或者簡訊來提醒使用者使用。
②延遲重試。比如消費者從佇列裡消費訊息時失敗了,但是想要延遲一段時間後自動重試。

我們可以利用RabbitMQ的兩個特性,一個是Time-To-Live Extensions,另一個是Dead Letter Exchanges。實現延遲佇列。

Time-To-Live Extensions
RabbitMQ允許我們為訊息或者佇列設定TTL(time to live),也就是過期時間。TTL表明了一條訊息可在佇列中存活的最大時間,單位為毫秒。也就是說,當某條訊息被設定了TTL或者當某條訊息進入了設定了TTL的佇列時,這條訊息會在經過TTL秒後“死亡”,成為Dead Letter。如果既配置了訊息的TTL,又配置了佇列的TTL,那麼較小的那個值會被取用。

Dead Letter Exchange
剛才提到了,被設定了TTL的訊息在過期後會成為Dead Letter。其實在RabbitMQ中,一共有三種訊息的“死亡”形式:
1.訊息被拒絕。通過呼叫basic.reject或者basic.nack並且設定的requeue引數為false。
2.訊息因為設定了TTL而過期。
3.訊息進入了一條已經達到最大長度的佇列。
如果佇列設定了Dead Letter Exchange(DLX),那麼這些Dead Letter就會被重新publish到Dead Letter Exchange,通過Dead Letter Exchange路由到其他佇列。

相關文章