本系列是「RabbitMQ實戰:高效部署分散式訊息佇列」書籍的總結筆記。
前段時間總結完了「深入淺出MyBatis」系列,對MyBatis有了更全面和深入的瞭解,在掘金社群也收到了一些博友的喜歡,很高興。另外,短暫的陪產假就要結束了,小寶也二週了,下週二就要投入工作了,希望自己儘快調整過來,加油努力。
從本篇開始總結「RabbitMQ實戰」系列的閱讀筆記,RabbitMQ是一個開源的訊息代理和佇列伺服器,可以通過基本協議在完全不同的應用之間共享資料,可以將作業排隊以便讓分散式服務進行處理。
本篇介紹下訊息通訊,首先介紹基礎概念,將這些概念對映到AMQP協議,然後介紹訊息持久化、傳送方確認模式等訊息可靠性保證。
通過本篇介紹,你會了解到:
- 訊息通訊概念:消費者、生產者和代理
- AMQP元素:佇列、交換器、繫結
- 虛擬主機
- 訊息持久化
- 傳送方確認模式
訊息通訊概念
此部分的介紹,會牽涉到AMQP的元素,如果之前沒接觸過的,可以結合下面的「AMQP元素」進行理解。
訊息
訊息是傳輸的主體,訊息包括兩部分:有效載荷(payload)和標籤(label);有效載荷是要傳輸的資料,可以是任何內容,比如JSON串、二進位制、自定義的資料協議等;標籤描述了有效載荷,並且Rabbit用它來決定誰將獲得訊息的投遞。
可以與HTTP協議類比,HTTP訊息頭部描述了訊息體的型別、大小等,HTTP訊息體是要傳輸的資料,HTTP服務端通過訊息頭部決定如何處理請求和資料。
生產者和消費者
生產者建立訊息,然後傳送到代理伺服器(RabbitMQ Server),AMQP只會用標籤表述這條訊息(一個交換器名稱和可選的主題標記),Rabbit伺服器會根據標籤把訊息傳送給訂閱的消費者。
消費者消費訊息,它會訂閱到佇列(queue)上,每當有訊息到達RabbitMQ伺服器時,會傳送給消費者,消費者收到訊息時,會進行處理。
注意:消費者收到的訊息只包括有效載荷,所有不會知道是從哪裡發來的。
連線和通道
要想釋出或消費訊息,必須先與RabbitMQ Server建立一條TCP連線,建立TCP連線之後,要建立一條通道,通道是建立在真實TCP連線的虛擬連線。
AMQP命令都是通過通道傳送出去的,每條通道會被指派一個唯一的ID,為什麼不直接通過TCP連線傳送AMQP命令呢? 因為作業系統建立和銷燬TCP會話是很昂貴的,而且建立的連線數也有限。 通過引入通道,可以在連線上建立通道,而且通道是私密的,相互不受影響。
通道的概念還是有點抽象,後面專門寫一篇文章進行分析介紹,這裡簡單理解下吧。
AMQP元素
AMQP訊息路由有三部分組成:佇列、交換器和繫結,佇列是存放訊息的地方,交換器是決定不同的分發策略,繫結是佇列和交換器的橋樑,定義匹配規則。
生產者傳送訊息到交換器,交換器根據自身型別和繫結規則,將訊息存放在對應佇列中,然後將訊息傳送到監聽佇列的消費者。
如上圖:P為生產者,X為交換器,交換器型別為direct,根據不同的繫結規則(orange、black、green),分發給不同的佇列,C為消費者,從不同的佇列介紹訊息。
佇列
消費者通過兩種方式從特定的佇列接收訊息:
- basic.consume,這樣會將通道置為接收模式,直到取消對佇列的訂閱;
- basic.get,主動讓消費者接收佇列中的下一條訊息;
basic.get會影響效能,推薦使用basic.consume來實現高吞吐量,因為其處理過程是先訂閱訊息,獲取單條訊息,再取取消訂閱。
如果佇列擁有多個消費者時,佇列收到的訊息將以迴圈的方式發給消費者,即多個消費者平均消費這些訊息。
另外,消費者接收到的每一條訊息都要進行確認,必須通過basic.ack命令向rabbitmq服務端傳送一個確認。 也可以設定auto_ack為true,只要消費者接收到訊息,就自動視為確認,不過不建議這樣,因為接收到不代表業務邏輯處理成功。 服務端接收到確認後,會從佇列中刪除對應訊息。
還有一種場景,在接收到訊息後,如果不想處理,可以通過下面方式處理:
- 把消費者從RabbitMQ伺服器斷開連線,,這樣RabbitMQ會自動將訊息入隊併傳送給另外一個消費者;
- 如果不想傳送給其他消費者處理,就是想忽略這個訊息,可以傳送basic.reject命令;
最後來介紹下如何建立佇列,首先明確下是生成者還是消費者建立,關鍵點是:生產者能否承擔起丟失訊息,因為發出去的訊息如果路由到了不存在的佇列,Rabbit會忽略它們。所以,建議生成者和消費者都嘗試去建立佇列,可以通過設定queue.declare的passive選項設定為ture來判斷佇列是否存在,如果不存在會返回一個錯誤。
通過queue.declare命令來建立佇列,有一些選項說明下:
- exclusive:如果設定true的化,佇列將變成私有的,只有建立佇列的應用程式才能夠消費佇列訊息;
- auto-delete:當最後一個消費者取消訂閱的時候,佇列會自動移除;
- durable:是否要持久化;
queueDeclare(String queue,
boolean durable,
boolean exclusive,
Map<String, Object> arguments);
複製程式碼
交換器和繫結
交換器有四種型別:direct、fanout、topic、headers,其中headers匹配訊息的header而非路由鍵,不太實用,就不詳細介紹了。
第一種:direct交換器
direct交換器比較簡單,如果和路由鍵 完全匹配 的話,就會投遞到對應的佇列:
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
channel.queueBind(queueName, EXCHANGE_NAME, routingKey);
複製程式碼
伺服器預設包含一個空白字串名稱的預設路由器,當宣告一個佇列時,會自定繫結到預設交換器,並以佇列名稱作為路由鍵。
第二種:fanout交換器
fanout交換器,不處理路由鍵,只需要簡單的將佇列繫結到交換機上,為會每個消費者自動生成一個隨機佇列,所有的消費者都會收到所有訊息。
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
複製程式碼
第三種:topic交換器
topic交換器,將路由鍵和某模式進行匹配,此時佇列需要繫結要一個模式上。
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
channel.queueBind(queueName, EXCHANGE_NAME, "*.orange.*");
複製程式碼
關於模式,符號#匹配一個或多個詞,符號*匹配一個詞,因此kfs.#能夠匹配到kfs.session.message,但是audit.*只會匹配到audit.session。
虛擬主機
每個RabbitMQ伺服器都能建立虛擬訊息伺服器,稱為虛擬主機(vhost),每個RabbitMQ本質上是一個mini版的RabbitMQ伺服器,擁有自己的佇列、交換器、繫結,還有自己的許可權機制。
連線時,必須制定vhost,rabbitmq包含了預設的vhost:"/"。當建立一個使用者時,會被指派給至少一個vhsot,並且相互隔離。
vhost不能通過AMQP協議建立,需要使用rabbitmqctl工具建立。
訊息持久化和傳送方確認模式
如果沒有持久化,重啟rabbitmq後,佇列、交換器都會消失,RabbitMQ提供了持久化的功能,需要滿足以下三個條件:
- 交換器設定為持久化,通過durable屬性;
- 佇列設定為持久化,通過durable屬性;
- 訊息投遞模式delivery設定為2;
當釋出一條持久化訊息到持久化交換器上時,rabbit會在訊息提交到日誌檔案後才會傳送響應,所有會損失效能,所以,只對重要資料持久化即可。
考慮這種情況:由於釋出訊息後,不返回任何資訊給生產者,如何只對伺服器已經持久化到硬碟了呢,可能在傳輸過程中丟失,或者持久化前伺服器當機,導致訊息丟失。
RabbitMQ通過「傳送方確認模式」來解決上面的問題。首先,需要將通道設定成confirm模式,這樣所有在通道上釋出的訊息都會被指派一個唯一的ID號,一旦訊息被投遞到所有匹配的佇列或持久化到磁碟,會傳送一個確認訊息給生產者。
通過本篇的介紹,對Rabbit的訊息模型有了整體瞭解,下一篇會寫個DEMO,並介紹下執行和管理RabbitMQ。
歡迎掃描下方二維碼,關注我的個人微信公眾號 ~