1. 程式人生 > >RabbitMQ訊息通訊中介軟體中的那些概念

RabbitMQ訊息通訊中介軟體中的那些概念

本章主要內容

  • 瞭解rabbitmq的誕生
  • 環境設定與安裝
  • AMQP協議
  • 訊息通訊概念-----生產者與消費者
  • 訊息持久化

 

瞭解rabbitmq的誕生

        20世紀80年代,IBM、微軟等公司研發了商業級的MQ元件,但大多停留在金融行業等大型組織內部使用,因其價格昂貴,且不同供應商之間的MQ協議不能,不能直接相互通訊,很多中小型公司無法使用這項技術。2004年,JPMorgan Chase需要一個更好的訊息通訊解決方案,並開始和iMatix公司一起合作開發Advanced Message Queuing Protocol(AMQP,高階訊息佇列協議),它被設計成開放標準,以解決眾多的訊息佇列需求和拓撲結構問題。隨後,RabbitMq實現了AMPQ的特性,使其成為構建分散式應用的最完美的通訊匯流排不二之選。

       今天,RabbitMQ並不是開放訊息通訊的唯一選擇,其它還有像ActiveMQ\ZeroMQ\apache Qpid\kafka等都提供了不同的開源訊息佇列方案。相比之下RabbitMQ有什麼特點呢:

  • 除Qpid外,RabbitMQ是唯一實現了AMQP標準的代理伺服器
  • 正是使用了Erlang語言編寫的RabbitMQ,使得它配置叢集不可思議的簡單。
  • 更可靠,更能防止崩潰

 

環境設定與安裝

以window為例,先下載安裝Erlang

http://www.erlang.org下載最新的發行版,並配置環境變數

 

http://www.rabbitmq.com上下載最新的MQ安裝包(注意對應的系統),也同樣配置環境變數

 

 

下載安裝到對應的目錄下,執行:

rabbitmq-server啟動MQ,可以在瀏覽器中開啟對應的管理介面,預設使用者與密碼都是guest

 

至此,一個按預設配置的MQ就執行起來了。接下來,我們先了解一下rabbitmq的內部的構成元素有哪些,各起到什麼樣的作用。

 

 

訊息通訊的概念----生產者與消費者

        一般的通訊,例如B/S或C/S構架都會有客戶端與服務端的概念,客戶端傳送請求,服務端接收並處理請求。這種可以稱為快餐車模式,但Rabbitmq並不是這種模式,它更像是一種投遞服務模式,應用從rabbitmq中獲取的訊息並不是rabbit產生的,就像是你收到的快遞並不是快遞員生產的一樣。因此,可以把rabbit當作是一種投遞服務,類似快遞員。在客戶端與伺服器之間扮演路由器的角色。所以應用程式連線到rabbit時就必須做成決定,是傳送資料還是接收資料?從AMQP角度看,就是自己是一個生產者還是一個消費者。

生產者(producer)建立訊息,然後釋出到代理伺服器(rabbit),訊息包含兩部分:

  • 有效載荷(payload):就是你需要傳輸的資料,它可以是任何內容,一個字串或一個JSON格式的資料或者是二進位制流資料等等都可以。rabbitmq不關注這些
  • 標籤(label):它用來描述payload,並且rabbitmq用它來決定訊息的流向,把訊息傳送給感興趣的一方。

這是一種“發後即忘”的單向方式。從上面可知:生產者會建立訊息並設定標籤,然後交給rabbitmq代理伺服器。

消費者(consumer)連線到代理伺服器並且訂閱感興趣的訊息佇列,每當訊息到達此佇列時rabbitmq都會將其傳送給其中一個訂閱者,當訊息者接收到訊息時,它只得到訊息的一部分,即:有效載荷部分,而訊息標籤並沒有隨訊息一同投遞。甚至rabbit都不會告訴你生產者是誰。要想知道生產者是誰的唯一方式就是在訊息裡(有效載荷中)簽名(在信封(envelope)上簽名)。

       可以看出這個過程很簡單,就是生產者建立訊息,訊息者接收處理訊息。應用程式可以在這裡扮演生產者,同時也可以扮演訊息者接收其它應用程式傳送的訊息。那麼訊息是如何傳遞的呢?那就是通過通道傳遞的,什麼是通道?

通道(channel)

       無論是生產者還是消費者都必須先連線到rabbitmq代理伺服器,這個連線就是一個TCP連線,而通道就是建立在TCP連線內的虛擬連線。訊息都是通過這些不同的通道投遞完成的,每個通道都會被指派一個唯一的ID標識。我們有了TCP連線為什麼還是通道呢?主要原因就是對作業系統來說建立和銷燬TCP會話是非常高的開銷。假設只使用TCP連線,那麼每個執行緒都需要自行連線到rabbit。在高峰時期可能會有上千條這樣的連線,不僅造成TCP連線的浪費而且作業系統每秒建立的TCP連線也是有限的。因此,這將成為一個瓶頸。如果我們為所有執行緒只使用一條TCP連線以滿足效能方面的要求,但又能確保每個執行緒的私密性。這就是要引入通道概念的原因,執行緒啟動後,會在現成的連線上建立一條通道。所以,你可以每秒成百上千次地建立通道而不會影響作業系統。在一條TCP連線上建立多少條通道是沒有限制的。AMQP連線就像是一條光纜,通道就是光纖。

從總體上來說,訊息通訊,特別是AMQP,可以被當作加強版的傳輸層。使用通道,你能夠根據應用需要,儘可能多地建立並行的傳輸層,而不會被TCP連線約束所限制。當理解了這些概念後,你就可以把rabbitmq看作軟體的路由器了。

 

AMQP協議概念

AMQP訊息路由包含三個部分:

  • 交換器
  • 佇列
  • 繫結

 

佇列

       生產者把訊息傳送到交換器後,訊息最終到達隊列當中,它是AMQP訊息通訊的基礎模組。為訊息提供了處所,訊息在此等待消費。而對負載均衡來說,佇列也是一個絕佳的方案,只需要附加一堆消費者,並讓rabbitmq以迴圈的方式均勻地分配 發來的訊息。它是訊息的最後的終點。

      訊息者通過AMQP的basic.consume命令訂閱佇列,當訊息佇列中的訊息後,將會自動接收下一條訊息。如果消費者處理佇列訊息時需要在訊息一到達佇列後就自動接收處理,應該使用此命令訂閱。但如果只想從佇列中獲得單條訊息而不是持續訂閱的話可以使用basic.get命令獲取。有區別於basic.consume命令,不能使用迴圈basic.get代替它。get命令每次會訂閱訊息,獲得單條訊息,然後取消訂閱。所以效能方面考慮不使用迴圈。

        如果一個列表上沒有消費者,那麼訊息將會在佇列中等待。直到有至少有一位消費者訂閱。當有一個訂閱者時,訊息會立即傳送給此訂閱者。但如果有多個訂閱者呢,佇列中的訊息將如何傳送?當有多個消費者時,佇列收到的訊息將以迴圈的方式傳送給消費者。每條訊息只會傳送給一個訂閱的消費者。消費者接收到的每一條訊息都必須進行確認,必須通過AMQP的basic.ack命令顯式地向rabbitmq傳送一個確認,或者在訂閱到佇列時將auto_ack引數設定為true,此時一旦消費者接收訊息,rabbitmq會自動視其確認了訊息。當rabbitmq收到了確認接收訊息後,才安全地把訊息從佇列中刪除。消費者對訊息的確認和告訴生產者訊息已經被接收了這兩件事沒有關係。但如果由於某些原因消費者沒有確認會發生什麼樣的事情呢?rabbit將不會給此消費者傳送更多訊息了。我們可以利用這個功能,當處理的訊息非常耗時,可以延遲確認訊息直到訊息處理完成。可以防止rabbit持續不斷的訊息湧向你的應用導致過載。

      在rabbitmq2.0.0或更新的版本,增加了basic.reject命令,允許消費者拒絕訊息,如果把reject命令的requeue引數設定成true的話,rabbitmq會將訊息重新發送給下一個訂閱的消費者。如果設定成false的話,rabbitmq立即會把訊息從佇列中移除。

佇列建立

       生產者和消費者都 可以使用AMQP的queue.declare命令來建立佇列,如果佇列已經被宣告,將不能被重複宣告。宣告時可以指定佇列的名稱,若不指定名稱,則會返回一個隨機名稱。還有其它可設定引數:

  • exclusive,如果為true的話,佇列將變成私有的,此時只有你的應用程式才能消費佇列中的訊息。
  • auto-delete,當最後一個消費者取消訂閱的時候,佇列就會自動移除。如果你需要臨時佇列只為一個消費者服務的話,可以結合使用auto-delete和exclusive。當消費者斷開連線時,佇列就會被自動刪除。
  • passive,為true,如果佇列已經存在那麼queue.declare命令會成功返回,如果佇列不存在的話命令不會建立佇列而會返回一個錯誤。

如果生產者傳送的訊息路由到一個不存在的佇列,則rabbit會忽略它們,即訊息進入了“黑洞”不見了。

 

交換器(exchange)

       當你想要將訊息投遞到佇列時,通過把訊息傳送給交換器來完成。然後,根據確定的規則,rabbitmq將會決定訊息應該投遞到哪個佇列。這些規則被稱作路由鍵(routing key)佇列通過路由鍵繫結到交換器。空也是一種路由鍵。

rabbit會根據路由鍵將訊息從交換器路由到佇列,協議中定義了不同型別的交換器。每種型別就是一種投遞方式:

  • direct(point to point),如果路由鍵routing-key與繫結到佇列上的binding-key匹配的話就把訊息投遞到對應的佇列。
  • fanout(multicast),這種型別的交換器會將收到的訊息廣播到繫結的佇列上。當你傳送一條訊息到fanout交換器時,它會把訊息投遞給所有附加在此交換器上的佇列。
  • topic(publish-subscribe),它可以使得來自不同源頭的訊息能夠到達同一個佇列,“.”把路由鍵分為了幾部分,“*”匹配特定位置的任意文字。為了實現匹配所有規則,你可以使用“#”字元。
  • headers,允許匹配AMQP訊息的header而非路由鍵 。除此之外,headers交換器和direct交換器完全一致,但效能會差很多。所以不太實用。

 

多租戶模式:虛擬主機與隔離

      vhost是AMQP概念的基礎,在連線到rabbit時必須進行指定。由於rabbitmq包含了開箱即用的預設vhost:"/",因此使用起來非常簡單。每個rabbitmq伺服器都能建立多個虛擬訊息伺服器,其本質上是一個mini版本的rabbitmq伺服器。這樣很有用,它既能將同一rabbit的眾多客戶區分開來,又可以避免佇列和交換器的命名衝突。否則你可能不得不執行多個rabbit。相反,你可以只執行一個rabbit,然後按需要啟動或關閉vhost。rabbit中的許可權控制是以vhost為單位的。

當建立一個使用者時,使用者通常會被指派給至少一個vhost,並且只能訪問被指派vhost內的佇列、交換器和繫結。vhost之間是絕對隔離的,既保證了安全性,又確保了可移植性(當需要擴充套件的時候可以直接移植vhost到其它rabbit伺服器)。

 

持久化

持久化的物件有兩種

  • 交換器和佇列的持久化
    • 預設情況下,rabbit重啟後它們將會消失。其原因就在於durable屬性,若為true則rabbit會把交換器或佇列持久化到磁碟。
  • 訊息持久化
    • 若要持久化訊息,就要在訊息釋出前設定它的投遞模式(delivery mode)為持久化,而且前提條件必須是所到達的交換器和佇列也要持久化。

         rabbit確保訊息能夠恢復的做法是,將訊息寫入磁碟上的一個持久化日誌檔案。當釋出一條持久化性的訊息到交換器上時,rabbit會在訊息提交到日誌檔案後才傳送響應。但如果此訊息被路由到了一個非持久化的佇列上,它會自動從持久化日誌中移除並且無法從rabbit重啟中恢復。一旦消費了此持久化訊息rabbit會在持久化日誌中把這條訊息標記為等待垃圾回收。鑑於持久化訊息後,若發生宕機等情況訊息可以恢復的好處。但同時也要帶來效能上的問題,畢竟寫磁碟和操作記憶體的效率上相關還是很大的。那麼,如何考慮訊息是否需要被持久化呢?

       權衡取捨,需要先分析應用對效能的需求,如果需要單臺rabbit處理100000+/s的訊息,那麼持久化將是最大的一個瓶頸,需要考慮使用其它方式來確保訊息投遞的正確性,例如:使用更快速的儲存系統。或者另開一個通道當消費者消費了訊息後在規定的時間內通知生產者,若生產者未收到確認消費的資訊,將會重發,以確保訊息不會丟失。rabbit能幫助確保投遞,但這也不是萬無一失的。硬碟崩潰等原因,也有可能使得持久化訊息的丟失。最終確保訊息安全到達都將取決於你的策略。

       另一個與持久化相關的是AMQP的事務,把通道設定為事務模式後,傳送那些想要確認的訊息,可以傳送多條AMQP命令,這些命令是執行帶是忽略取決於第一條訊息傳送是否成功。如果成功,將會執行其它的命令,若投遞不成功將不會執行。雖然事務是AMQP0-9-1規範的一部分,但使用它幾乎將rabbit的效能吸乾。所以可以使用其它的方式來代替事務,例如:傳送方確認模式。

       與事務相仿,需要要先把通道設定成confirm模式,而且你只能通過重新建立信首來關閉此設定。一旦進入了confirm模式,所有在通道上釋出的訊息都會被指派一個ID號。當訊息被投遞完成後,通道會發送一個傳送方確認模式給生產者。這樣生產者就知曉訊息已經安全到達佇列中了。如果訊息和佇列都是持久化的,那麼會在寫入磁碟後才會發出確認。它是非同步的,如果rabbit發生投遞錯誤,將會發送一條nack未確認的訊息,生產者可以根據此訊息來決定是重新處理還是其它。此模式,由於沒有訊息回滾的概念,更加輕量級,同時也對rabbit伺服器的效能影響也可以忽略不計。

 

總結

本章主要是瞭解rabbitmq中的相關元素,及各元素之間是如何協同工作的,訊息是怎樣被安全投遞的等等。瞭解這些基本的概念後,就會對rabbitmq的整個工作流程有一個清楚的認識。在應用程式編碼時先幹什麼,後幹什麼,是否需要持久化等等也會有一個清晰的思路。