RabbitMQ實戰:可用性分析和實現

情情說發表於2018-04-12

本系列是「RabbitMQ實戰:高效部署分散式訊息佇列」書籍的總結筆記。

上一篇介紹了各種場景下的最佳實踐,大部分場景可以使用「發後即忘」的模式,不需要響應,如果需要響應,可以使用RabbitMQ的RPC模型。

RabbitMQ以非同步的方式解耦系統間的關係,呼叫者將業務請求傳送到Rabbit伺服器,就可以返回了,Rabbit會確保請求被正確處理,即使遇到網路異常、Rabbit伺服器崩潰、整個機房斷電等特殊場景,針對這些場景,Rabbit提供了各種機制確保其可用性。

本篇通過總結可能出現的特殊場景,對Rabbit提供的可用性保證進行分析,學習它的實現方式,你會了解到:

  • 總結異常場景
  • 叢集並處理失敗
  • 連線丟失和故障轉移
  • 主/備方式
  • 跨機房複製

推廣下我的個人公眾號「情情說」,第一時間分享我的工作、學習和生活,如果對你有幫助,希望可以關注下。

異常場景

在實際工作中,有很大一部分時間用在解決各種異常情況,比如針對使用者輸入的驗證,JDK中提供的各種異常類,網路異常等,這些相對來說比較好解決。

Rabbit服務作為呼叫者和處理者的橋樑,至關重要,如果因為網路異常、單臺伺服器崩潰、機房癱瘓等原因導致Rabbit服務不可用,會影響所有依賴的業務系統。

網路異常

處理者和服務端是通過長連線互動的,這樣可以將訊息實時推送,網路異常可能會導致長連線斷開,如果客戶端無法感知,處理者將接收不到任何訊息,這種情況稱為「連線丟失」。

通過捕獲連線異常,進行重連,可以解決這種問題,另外,Rabbit客戶端進行了封裝,很容易處理這種問題。

伺服器崩潰

如果只有一臺伺服器服務,伺服器崩潰將導致服務不可用,一般會使用叢集將多個伺服器看成一個整體對外提供服務,這樣,單臺伺服器崩潰不會影響整體的服務。

使用叢集后,就要考慮一些問題:

  • 客戶端連線到哪臺伺服器是隨機的,而一個佇列只會在某個伺服器中,所以,每臺伺服器都要儲存佇列後設資料(類似索引),並且可從其他伺服器獲取實際的佇列資料;
  • 伺服器崩潰,會導致非持久化的佇列、交換器丟失,客戶端端重連後,要再次進行建立,但未消費的訊息將無法恢復;
  • 如果佇列、交換器、訊息等是持久化的,如何進行恢復呢,Rabbit提供了幾種方式進行處理,後面會詳細介紹;
  • 訂閱者也需要重新建立連線,進行監聽;
機房癱瘓

如果考慮機房癱瘓,就要建多個資料中心,RabbitMQ提供了一種機制,可以方便地在不同資料中心的Rabbit間複製訊息。

叢集並處理失敗

RabbitMQ最優秀的功能之一就是其內建叢集,主要用於完成2個目標:

  • 允許消費者和生產者在Rabbit節點崩潰的情況下繼續執行;
  • 通過新增更多的節點線性擴充套件訊息通訊的吞吐量;
叢集架構

RabbitMQ會始終記錄四種型別的內部後設資料(類似索引):

  • 佇列後設資料:佇列名稱和它的屬性;
  • 交換器後設資料:交換器名稱、型別和屬性;
  • 繫結後設資料:一張簡單的表格展示瞭如何將訊息路由到佇列;
  • vhost後設資料:為vhost內的佇列、交換器和繫結提供名稱空間和安全屬性;

當引入叢集時,就需要追蹤新的後設資料型別:叢集節點位置,以及節點與已記錄的其他型別後設資料的關係。

不是每個節點都有所有佇列的完全拷貝,如果在叢集中建立佇列,只會在單個節點上建立完整的佇列資訊(後設資料、狀態、內容),所有其他節點只知道佇列的後設資料和指向該佇列的節點指標。

如果節點崩潰了,附加在佇列上的消費者也就無法接收新的訊息了。可以讓消費者重連到叢集並重新建立佇列,這種做法僅當佇列沒設定持久化時才可行,這是為了確保當失敗的節點恢復後加入叢集,節點上的佇列訊息不會丟失。

為什麼不將佇列內容和狀態複製到所有節點:第一,儲存空間,如果每個叢集節點都擁有所有佇列的完全拷貝,新增新節點不會帶來更多儲存空間;第二,效能,訊息的釋出者需要將訊息複製到每一個叢集節點,對於持久化訊息,網路和磁碟複製都會增加。

而交換器只是一張查詢表,而非實際的訊息路由器,因此將交換器在整個叢集中進行復制會更加簡單

可以把每個佇列想象成節點上執行的程式,每個程式擁有自己的程式ID,交換器只是路由模式列表和匹配訊息應發往的佇列程式ID列表。

叢集架構中的交換器和佇列

每個Rabbit節點,要麼是記憶體節點,要麼是磁碟節點,單節點系統只執行磁碟型別的節點,在叢集中,可以選擇配置部分節點為記憶體節點。

在叢集中宣告佇列、交換器或繫結的時候,這些操作直到所有叢集節點都成功提交後設資料變更後才返回。

RabbitMQ只要求叢集中至少有一個磁碟節點,如果只有一個磁碟節點,剛好又崩潰了,叢集可以繼續路由訊息,但不能建立佇列、交換器、繫結、新增使用者、更改許可權等操作。所以,建議設定兩個磁碟節點,當記憶體節點重啟後,會連線到預先配置的磁碟節點,下載當前叢集後設資料拷貝,所以要將所有磁碟節點告訴記憶體節點。

映象佇列

前面提到,佇列只會在叢集中的一個節點,節點崩潰後,佇列訊息就會丟失,RabbitMQ2.6版本之後,提供了映象佇列,一旦主佇列不可用,從佇列將被選舉為新的主佇列。

對於映象佇列,除了將訊息按照路由繫結規則投遞到合適的佇列,也會將訊息投遞到映象佇列的從拷貝。

對於傳送方確認訊息,Rabbit會在所有佇列和佇列的從拷貝安全地接收到訊息時,才會通知傳送方。

另外,使用映象佇列時,有一個問題:如果主拷貝節點傳送故障,從佇列會選舉Wie主佇列,所有該佇列的消費者需要重新附加並監聽新的佇列主拷貝。對於通過故障節點進行連線的消費者,可以通過丟失到節點的TCP連線檢測到,但對於那些通過節點附加到映象佇列且正常執行的消費者將無法檢測到。

Rabbit通過給消費者傳送一個消費者取消通知,告知不再附加在佇列主拷貝了,需要重新連線。

連線丟失和故障轉移

這一小節主要討論消費者如何檢測連線丟失,並進行重連操作。

處理到叢集的重連有多重策略,比較好的一種方式是使用負載均衡,不僅可以減少應用程式處理節點故障程式碼的複雜性,又能確保在叢集中連線的平均分配。

關於負載均衡,網上介紹的比較多了,這裡就不再過多介紹了,主要看看如何感知故障,並進行重連操作。

感知故障比較簡單,當長連線斷開時,會丟擲異常,捕獲對應的異常即可。

當叢集節點出現故障時,應用程式需要考慮:下一個該連向哪裡?這個工作已經交由負載均衡器決定。

關於重連處理,要考慮:

  • 如果重連到新的伺服器,通道以及其上的所有消費迴圈都會失效,需要對他們進行重建;
  • 當進行重連時,所有的佇列、繫結有可能都不存在了,需要重新構造佇列和繫結。

主/備方式

當對可用性要求特別高時,不允許訊息丟失,需要將佇列、交換器、訊息設定成持久化,如果一個節點崩潰了,在恢復之前,將無法轉發訊息,因為預設的群集架構不允許在叢集其他節點建立佇列,防止故障節點恢復後,歷史訊息丟失。

可以通過構建主/備機的獨立RabbitMQ,也就是warren模式,解決這個問題。一個warren是指一對主/備獨立伺服器,並前置一套負載均衡器來處理故障轉移。

主伺服器和備用伺服器之間沒有協作,只有當主伺服器崩潰時,備用伺服器才會處理訊息。可以保證,主節點故障後,通過備用節點重新建立佇列、交換器繼續服務,故障節點恢復後,可以繼續消費主節點未消費的訊息。

跨機房複製

在只有一個資料中心的時候,RabbitMQ叢集對於提升訊息通訊效能來說是很棒的方案,但需要把訊息從一個程式路由到另一個城市的時候,就比較麻煩了,可以通過Shovel解決。

Shovel是RabbitMQ的一個外掛,可以使你能夠定義RabbitMQ上的佇列和另一個RabbitMQ上的交換器之間的複製關係。說白了就是生產者和消費者離得比較遠。

通過在機房1建立一個新的佇列,用於接收網站釋出的訊息,然後讓shovel消費這些訊息並重新將訊息通過WAN連線釋出到機房2上的交換器。

這樣對於使用者來說,只要釋出到機房1的佇列即可返回,減少了響應時間。機房1可以持續將訊息釋出到機房2上。

Shovel處理過程

通過上面的介紹可以看到,保證高可用需要做很多工作,可以根據業務對可用性的要求,選擇不同的架構方式。

下一篇重點介紹RabbitMQ管理介面和監控。

歡迎掃描下方二維碼,關注我的個人微信公眾號 ~

情情說

相關文章