1. 程式人生 > >RabbitMQ下的生產消費者模式與訂閱釋出模式

RabbitMQ下的生產消費者模式與訂閱釋出模式

  所謂模式,就是在某種場景下,一類問題及其解決方案的總結歸納。生產消費者模式與訂閱釋出模式是使用訊息中介軟體時常用的兩種模式,用於功能解耦和分散式系統間的訊息通訊,以下面兩種場景為例:

  • 資料接入
      假設有一個使用者行為採集系統,負責從App端採集使用者點選行為資料。通常會將資料上報和資料處理分離開,即App端通過REST API上報資料,後端拿到資料後放入佇列中就立刻返回,而資料處理則另外使用Worker從佇列中取出資料來做,如下圖所示。


      這樣做的好處有:第一,功能分離,上報的API介面不關心資料處理功能,只負責接入資料;第二,資料緩衝,資料上報的速率是不可控的,取決於使用者使用頻率,採用該模式可以一定程度地緩衝資料;第三,易於擴充套件,在資料量大時,通過增加資料處理Worker來擴充套件,提高處理速率。這便是典型的生產消費者模式

    ,資料上報為生產者,資料處理為消費者。

  • 事件分發
      假設有一個電商系統,那麼,使用者“收藏”、“下單”、“付款”等行為都是非常重要的事件,通常後端服務在完成相應的功能處理外,還需要在這些事件點上做很多其他處理動作,比如傳送簡訊通知、記錄使用者積分等等。我們可以將這些額外的處理動作放到每個模組中,但這並不是優雅的實現,不利於功能解耦和程式碼維護。
      我們需要的是一個事件分發系統,在各個功能模組中將對應的事件釋出出來,由對其感興趣的處理者進行處理。這裡涉及兩個角色:A對B感興趣,A是處理者,B是事件,由事件處理器完成二者的繫結,並向訊息中心訂閱事件。服務模組是後端的業務邏輯服務,在不同的事件點發布事件,事件經過訊息中心分發給事件處理器對應的處理者。整個流程如下圖所示。這邊是典型的訂閱釋出模式

  可以看到,生產消費者模式與訂閱釋出模式都離不開訊息中介軟體來作為訊息中轉站,開源的訊息中介軟體有很多,各有優劣。本文將重點探討RabbitMQ的特性,以及如何實現上述的兩種場景。

RabbitMQ核心概念

  如果你只是想使用一下RabbitMQ,那麼參考官方教程修改一下就可以跑起來了,很簡單。如果你有一些概念上的疑惑,不妨與筆者一起來總結一下RabbitMQ的核心概念。

  • 通訊方式
      RabbitMQ是基於AMQP協議來實現的訊息中介軟體。AMQP,類似於HTTP協議,也是一個應用層的協議,網路層使用TCP來通訊。因此,RabbitMQ也是典型的C-S模型,準確地說是C-S-C模型,因為伴隨著RabbitMQ的使用,總是會有Producer與Consumer兩個Client和一個Broker Server。


      Client要與Server進行通訊,就必須先建立連線,RabbitMQ中有Connection與Channel兩個概念,前者就是一個TCP連線,後者是在這個連線上的虛擬概念,負責邏輯上的資料傳遞,因此,為了節省資源,一般在一個客戶端中建立一個Connection,每次使用時再分配一個Channel即可。

  • 訊息體
      Message是RabbitMQ中的訊息體概念。類似HTTP傳輸中,有header和body兩部分資料,Message中也有Attributes和Payload兩部分資料,前者是一些元資訊,後者是傳遞的訊息資料實體。

  • 訊息投遞
      Exchange、Queue與Routing Key三個概念是理解RabbitMQ訊息投遞的關鍵。RabbitMQ中一個核心的原則是,訊息不能直接投遞到Queue中。Producer只能將自己的訊息投遞到Exchange中,由Exchange按照routing_key投遞到對應的Queue中,具體的架構參見下圖。細細品味就會體會到這樣設計的精妙之處。


      那麼,具體實現時,如何完成這三者關係的繫結?總結起來是兩點:第一,在Consumer Worker中,宣告自己對哪個Exchange感興趣,並將自己的Queue繫結到自己感興趣的一組routing_key上,建立相應的對映關係;第二,在Producer中,將訊息投遞一個Exchange中,並指明它的routing_key。由此可見,Queue這個概念只是對Consumer可見,Producer並不關心訊息被投遞到哪個Queue中。
      看過RabbitMQ的”Hello World”教程的童鞋可能會發現在那裡面的圖中並沒有看到Exchange和routing_key的蹤跡,但這並不意味著RabbitMQ可以支援直接將訊息投遞到Queue中,而是在內部使用了預設的Exchange和routing_key了。預設情況下,RabbitMQ使用名稱為“amq.direct”的Direct Exchange,routing_key預設名字與Queue保持一致。
      搞清楚上述概念,就不難理解Exchange的四種類型了。Direct、Fanout、Topic、Headers,區別在於如何將訊息從Exchange投遞到Queue中。Direct使用具體的routing_key來投遞;Fanout則忽略routing_key,直接廣播給所有的Queue;Topic是使用模糊匹配來對一組routing_key進行投遞;Headers也是忽略routing_key,使用訊息中的Headers資訊來投遞。

  • 訊息可靠性
      不同於HTTP的同步訪問,RabbitMQ中,Producer並不知道訊息是否被可靠地投遞到了Consumer中處理。那麼,RabbitMQ是如何保證訊息的可靠投遞?主要是兩點:第一,訊息確認機制。Consumer處理完訊息後,需要傳送確認訊息給Broker Server,可以選擇“確認接收”、“丟棄”、“重新投遞”三種方式。如果Consumer在Broker Server收到確認訊息之前掛了,Broker Server便會重新投遞該訊息。第二,可以選擇資料持久化,這樣即使RabbitMQ重啟,也不會丟失訊息。

生產消費者模式

  搞清楚了RabbitMQ的核心概念,要針對特定的場景來設計使用方案就很簡單了,基本上就是上述RabbitMQ架構圖的變遷。讓我們先來看看文章開頭提到的“資料接入”場景,如何實現生產消費者模式。



  這裡增加了一下場景複雜度:對於上報的資料,如果是special的行為,需要優先處理。從上圖可以看到,資料上報端負責將資料投遞到RabbitMQ對應的Exchange,並指明routing_key是common還是special。資料處理端,可以根據情況啟多個Woker來消費資料,但至少需要兩個,一個用來處理common資料,一個用來處理special的資料。注意:當需要增加多個Worker來消費同一類資料時,需要保持Queue名字一致,比如上面的Common資料。

訂閱釋出模式

  再來看“事件分發”的場景,架構如下圖所示,使用event name/id來作為RabbitMQ的routing key的名字。Event Processor 01對event 01 和event 02感興趣,則在啟動Consumer Worker時,將自己的Queue繫結到這兩個routing key上即可,其他Event Processor也是如此,這樣便完成了事件的訂閱。當有事件釋出時,訊息便會按照event name/id被投遞到對應的Queue中。



  由此可見,在不同的應用中,變化的只是routing_key與Consumer Queue的繫結關係,在充分理解RabbitMQ的核心概念後處理這些應該也是得心應手了。