微信公衆號:跟着老萬學java
關注可瞭解更多的開發技巧。問題或建議,請公衆號留言;
什麼叫消息隊列
消息(Message)是指在應用間傳送的數據。消息可以非常簡單,比如只包含文本字符串,也可以更復雜,可能包含嵌入對象。
消息隊列(Message Queue)是一種應用間的通信方式,消息發送後可以立即返回,由消息系統來確保消息的可靠傳遞。消息發佈者只管把消息發佈到 MQ 中而不用管誰來取,消息使用者只管從 MQ 中取消息而不管是誰發佈的。這樣發佈者和使用者都不用知道對方的存在,解除了上下游調用的依賴關係,實現異步和解耦。
消息隊列的作用
1、異步
2、解耦
3、削峯
4、廣播訂閱
5、RPC調用
6、順序消費
7、分佈式事務支持
RabbitMQ說明
RabbitMQ的官網是http://www.rabbitmq.com
RabbitMQ 是一個由 Erlang 語言開發的 AMQP 的開源實現。
AMQP :Advanced Message Queue,高級消息隊列協議。它是應用層協議的一個開放標準,爲面向消息的中間件設計,基於此協議的客戶端與消息中間件可傳遞消息,並不受產品、開發語言等條件的限制。
RabbitMQ 即一個消息隊列,主要是用來實現應用程序的異步和解耦,同時也能起到消息緩衝,消息分發的作用。
中間件最標準的用法是生產者生產消息傳送到隊列,消費者從隊列中拿取消息並處理,生產者不用關心是誰來消費,消費者不用關心誰在生產消息,從而達到解耦的目的。在分佈式的系統中,消息隊列也會被用在很多其它的方面,比如:分佈式事務的支持,RPC的調用等等。
RabbitMQ 特點
RabbitMQ 最初起源於金融系統,用於在分佈式系統中存儲轉發消息,在易用性、擴展性、高可用性等方面表現不俗。具體特點包括:
可靠性(Reliability)
RabbitMQ 使用一些機制來保證可靠性,如持久化、傳輸確認、發佈確認。
靈活的路由(Flexible Routing)
在消息進入隊列之前,通過 Exchange 來路由消息的。對於典型的路由功能,RabbitMQ 已經提供了一些內置的 Exchange 來實現。針對更復雜的路由功能,可以將多個 Exchange 綁定在一起,也通過插件機制實現自己的 Exchange 。
消息集羣(Clustering)
多個 RabbitMQ 服務器可以組成一個集羣,形成一個邏輯 Broker 。
雖然支持集羣,但是並不支持消息分片,可通過插件將消息通過hash計算分發到多個隊列。而kafka是通過對單個topic進行分區來實現的。
高可用(Highly Available Queues)
隊列可以在集羣中的機器上進行鏡像,使得在部分節點出問題的情況下隊列仍然可用。
多種協議(Multi-protocol)
RabbitMQ 支持多種消息隊列協議,比如 STOMP、MQTT 等等。
多語言客戶端(Many Clients)
RabbitMQ 幾乎支持所有常用語言,比如 Java、.NET、Ruby 等等。
管理界面(Management UI)
RabbitMQ 提供了一個易用的用戶界面,使得用戶可以監控和管理消息 Broker 的許多方面。
跟蹤機制(Tracing)
如果消息異常,RabbitMQ 提供了消息跟蹤機制,使用者可以找出發生了什麼。
插件機制(Plugin System)
RabbitMQ 提供了許多插件,來從多方面進行擴展,也可以編寫自己的插件。
RabbitMQ 基本概念
上面只是最簡單抽象的描述,具體到 RabbitMQ 則有更詳細的概念需要解釋。上面介紹過 RabbitMQ 是 AMQP 協議的一個開源實現,所以其內部實際上也是 AMQP 中的基本概念:
通常我們談到隊列服務, 會有三個概念: 發消息者、隊列、收消息者,RabbitMQ 在這個基本概念之上, 多做了一層抽象, 在發消息者和 隊列之間, 加入了交換器 (Exchange). 這樣發消息者和隊列就沒有直接聯繫, 轉而變成發消息者把消息給交換器, 交換器根據調度策略再把消息發給隊列。
Message
消息,由消息頭和消息體組成。消息體是不透明的,而消息頭則由一系列的可選屬性組成,這些屬性包括routing-key(路由鍵)、priority(相對於其他消息的優先權)、delivery-mode(指出該消息可能需要持久性存儲)等。
Publisher
消息的生產者,也是一個向交換器發佈消息的客戶端應用程序。
Exchange
交換器,用來接收生產者發送的消息並將這些消息路由給服務器中的隊列。
Binding
綁定,用於消息隊列和交換器之間的關聯。一個綁定就是基於路由鍵將交換器和消息隊列連接起來的路由規則,所以可以將交換器理解成一個由綁定構成的路由表。
Queue
消息隊列,用來保存消息直到發送給消費者。它是消息的容器,也是消息的終點。一個消息可投入一個或多個隊列。消息一直在隊列裏面,等待消費者連接到這個隊列將其取走。
Connection
網絡連接,比如一個TCP連接。
Channel
信道,多路複用連接中的一條獨立的雙向數據流通道。信道是建立在真實的TCP連接內地虛擬連接,AMQP 命令都是通過信道發出去的,不管是發佈消息、訂閱隊列還是接收消息,這些動作都是通過信道完成。因爲對於操作系統來說建立和銷燬 TCP 都是非常昂貴的開銷,所以引入了信道的概念,以複用一條 TCP 連接。
Consumer
消息的消費者,表示一個從消息隊列中取得消息的客戶端應用程序。
Virtual Host
虛擬主機,表示一批交換器、消息隊列和相關對象。虛擬主機是共享相同的身份認證和加密環境的獨立服務器域。每個 vhost 本質上就是一個 mini 版的 RabbitMQ 服務器,擁有自己的隊列、交換器、綁定和權限機制。vhost 是 AMQP 概念的基礎,必須在連接時指定,RabbitMQ 默認的 vhost 是 / 。
爲什麼需要多個虛擬主機呢?
很簡單,RabbitMQ當中,用戶只能在虛擬主機的粒度進行權限控制。 因此,如果需要禁止A組訪問B組的交換機/隊列/綁定,必須爲A和B分別創建一個虛擬主機。
簡單的來說,就是一個分組的概念。每個用戶操作自己權限下的虛擬主機,相互之間互不影響。
Broker
表示消息隊列服務器實體。
重點說明一下
- RabbitMQ的發送消息不是面向隊列的,而是面向交換器Exchange,交換器根據接收消息時指定的路由鍵Routing Key和交換器自身的類型以及和隊列之間建立綁定關係使用的Binding Key來決定消息發送到哪些具體的隊列中。
- 注意區分Routing Key和Binding Key的作用和使用場景的區別,Routing Key是生產者發送消息時指定的,用來確定路由規則;Binding Key是交換器和隊列綁定時用到的。
3、一條消息具體發送到什麼隊列,是由Routing Key,交換器Exchange,Binding Key共同決定的。
核心流程
生產者發送消息流程
- 生產者連接到RabbitMQ Broker,建立一個連接(Connection),開啓一個信道(Channel)
- 生產者聲明一個交換器,並設置相關屬性,比如交換器類型,是否持久化等
- 生產者聲明一個隊列並設置相關屬性,比如是否排他,是否持久化,是否自動刪除等。
- 生產者通過路由鍵將交換器和隊列綁定起來
- 生產者發送消息至RabbitMQ Broker,其中包含路由鍵,交換器等信息。
- 相應的交換器根據接收到的路由鍵查找相匹配的隊列。
- 如果找到,則將從生產者發送過來的消息存入相應的隊列中。
- 如果沒有找到,則根據生產者配置的屬性選擇丟棄還是回退給生產者
- 關閉信道。
- 關閉連接。
簡單代碼
消費者消費消息流程
- 消費者連接到RabbitMQ Broker,建立一個連接(Connection),開啓一個信道(Channel)。
- 消費者向RabbitMQ Broker請求消費相應隊列中的消息,可能會設置相應的回調函數,以及做一些準備工作。
- 等待RabbitMQ Broker迴應並投入相應隊列中的消息,消費者接收消息。
- 消費者確認(ack)接收到消息
- RabbitMQ從隊列中刪除相應已經被確認的消息
- 關閉信道。
- 關閉連接
簡單代碼
四種路由
Exchange分發消息時根據類型的不同分發策略有區別,目前常用的四種類型:
direct、fanout、topic、headers 。
headers 匹配 AMQP 消息的 header 而不是路由鍵,此外 headers 交換器和 direct 交換器完全一致,但性能差很多,目前幾乎用不到了,所以直接看另外三種類型:
direct
direct類型的Exchange路由規則也很簡單,它會把消息路由到那些binding key與routing key完全匹配的Queue中。
以上圖的配置爲例,我們以routingKey=”error”發送消息到Exchange,則消息會路由到Queue1(amqp.gen-S9b…,這是由RabbitMQ自動生成的Queue名稱)和Queue2(amqp.gen-Agl…);如果我們以routingKey=”info”或routingKey=”warning”來發送消息,則消息只會路由到Queue2。如果我們以其他routingKey發送消息,則消息不會路由到這兩個Queue中。
topic
前面講到direct類型的Exchange路由規則是完全匹配binding key與routing key,但這種嚴格的匹配方式在很多情況下不能滿足實際業務需求。topic類型的Exchange在匹配規則上進行了擴展,它與direct類型的Exchage相似,也是將消息路由到binding key與routing key相匹配的Queue中,但這裏的匹配規則有些不同,它約定:
routing key爲一個句點號“.”分隔的字符串(我們將被句點號“. ”分隔開的每一段獨立的字符串稱爲一個單詞),如“stock.usd.nyse”、“nyse.vmw”、“quick.orange.rabbit” binding key與routing key一樣也是句點號“. ”分隔的字符串
binding key中可以存在兩種特殊字符“*”與“#”,用於做模糊匹配,其中“*”用於匹配一個單詞,“#”用於匹配多個單詞(可以是零個)
以上圖中的配置爲例,routingKey=”quick.orange.rabbit”的消息會同時路由到Q1與Q2,routingKey=”lazy.orange.fox”的消息會路由到Q1,routingKey=”lazy.brown.fox”的消息會路由到Q2,routingKey=”lazy.pink.rabbit”的消息會路由到Q2(只會投遞給Q2一次,雖然這個routingKey與Q2的兩個bindingKey都匹配);routingKey=”quick.brown.fox”、routingKey=”orange”、routingKey=”quick.orange.male.rabbit”的消息將會被丟棄,因爲它們沒有匹配任何bindingKey
fanout
fanout類型的Exchange路由規則非常簡單,它會把所有發送到該Exchange的消息路由到所有與它綁定的Queue中。
上圖中,生產者(P)發送到Exchange(X)的所有消息都會路由到圖中的兩個Queue,並最終被兩個消費者(C1與C2)消費。
fanout路由下會忽略binding key與routing key的匹配關係。
headers
headers類型的Exchange不依賴於routing key與binding key的匹配規則來路由消息,而是根據發送的消息內容中的headers屬性進行匹配。
在綁定Queue與Exchange時指定一組鍵值對;當消息發送到Exchange時,RabbitMQ會取到該消息的headers(也是一個鍵值對的形式),對比其中的鍵值對是否完全匹配Queue與Exchange綁定時指定的鍵值對;如果完全匹配則消息會路由到該Queue,否則不會路由到該Queue。
該類型的Exchange沒有用到過(不過也應該很有用武之地),所以不做介紹。
RPC調用
MQ本身是基於異步的消息處理,前面的示例中所有的生產者(P)將消息發送到RabbitMQ後不會知道消費者(C)處理成功或者失敗(甚至連有沒有消費者來處理這條消息都不知道)。
但實際的應用場景中,我們很可能需要一些同步處理,需要同步等待服務端將我的消息處理完成後再進行下一步處理。這相當於RPC(Remote Procedure Call,遠程過程調用)。
在RabbitMQ中也支持RPC。
RabbitMQ中實現RPC的機制是:
客戶端發送請求(消息)時,在消息的屬性(MessageProperties,在AMQP協議中定義了14中properties,這些屬性會隨着消息一起發送)中設置兩個值replyTo(一個Queue名稱,用於告訴服務器處理完成後將通知我的消息發送到這個Queue中)和correlationId(此次請求的標識號,服務器處理完成後需要將此屬性返還,客戶端將根據這個id瞭解哪條請求被成功執行了或執行失敗)。
服務器端收到消息並處理服務器端處理完消息後,將生成一條應答消息到replyTo指定的Queue,同時帶上correlationId屬性客戶端之前已訂閱replyTo指定的Queue,從中收到服務器的應答消息後,根據其中的correlationId屬性分析哪條請求被執行了,根據執行結果進行後續業務處理。
總結
1、主要介紹消息隊列的概念
2、消息隊列的作用和使用場景
3、rabbitmq的特點
4、rabbitmq基本概念和內部結構
5、rabbitmq經典的四種路由機制
6、rabbitmq的RPC調用實現原理