作者:鄧志文、王帆
01 為什么要容災?
在小米內部,我們使用 RocketMQ 來為各種在線業務提供消息隊列服務,比如商城訂單、短信通知甚至用來收集 IoT 設備的上報數據,可以說 RocketMQ 的可用性就是這些在線服務的生命線。作為軟件開發者,我們通常希望服務可以按照理想狀態去運行:在沒有Bug的前提下,系統可以提供正常的服務能力。
(資料圖片僅供參考)
但現實的運維經驗告訴我們這是不可能的,硬件故障是非常常見的問題,比如內存故障、磁盤故障等,甚至是機房相關的故障(專線故障、機房拉閘等)。因此我們需要對數據進行備份,使用多副本的方式來保證服務的高可用。Apache RocketMQ 設計上就支持多副本、多節點容災,比如 Master-Slave 架構、DLedger 部署模式。
在小米內部,因為是面向在線業務,服務的恢復速度至關重要,而基于 Raft 協議的 DLedger 模式可以實現秒級 RTO,因此我們在 2020 年初選用了 DLedger 架構作為基本的部署模式(在 5.0 中,主從模式也可以做到自動 failover)。支持機房災備需要增加額外的成本,下面我將用三個災備部署的實踐案例,講解小米如何在成本和可用性的取舍上去支持災備。
02 怎么去做容災?
單機房高可用
實際在使用中,有許多業務是不需要機房級別容災的,只要能夠做到單機房高可用即可。Apache RocketMQ 本身就是分布式的消息隊列服務,可以很好的做到同機房多節點高可用,下面主要分享下小米在權衡成本、可用性的前提下,如何去做部署架構的升級優化。
我們知道在 Raft 協議中,一般配置三個節點,利用機器冗余 + 自動選主切換來實現高可用的目標。因此在小米引入 RocketMQ 之初,單 Broker 組均部署三個 Broker 節點。同時為了保證集群中始終存在 Master 節點,我們一般會至少部署兩個 Broker 組,一個簡單的部署架構圖如下:
可以說是一個很基本的部署架構,在單個機房中,通過多副本、多Broker組做到了單機房容災。但不難發現,這樣做有一個很嚴重的問題:資源浪費。RocketMQ 的從節點只有在客戶端讀取較舊的數據時才會起到從讀的作用,其他時候都只是單純地作為副本運行,機器利用率只有33%,這是讓人無法忍受的。
出于成本上的考慮,我們需要重新思考現有的部署架構,如何才能利用起來從節點呢?一個很簡單的思路便是節點混布:在從節點也部署 Broker 進程,讓其可以作為 Master 來提供服務。比較巧合的是,社區當時也提出了 Broker Container 的概念,方案的原理是在 RocketMQ Broker 之上抽象一個 Container 角色,Container 用來管理 Broker 的增刪改查,以此來達到單臺服務主機上運行多個 Broker 的目的,具體架構圖如下所示:
可以看到,Container 作為進程運行,原本的 Broker 被抽象為 Container 的一部分,同樣的 3 臺機器上我們可以運行 9個 Broker 節點,組成三個 Broker組,每臺服務主機上存在一個 Master 節點,使用 Container 對等部署 Broker 之后,每臺服務主機都得到了利用,同樣的機器數,理論上可以提供三倍的性能。
Container 是一種很好的部署思想:主從節點對等部署進而充分利用所有的機器。我們嘗試直接使用該方案,但遇到了一些問題:
Container 本質上是一個進程。不管其內運行了多少個 Broker ,我們只要對其進行重啟操作,都會影響該 Container 內部 Broker 相關的所有 Broker 組,升級時會產生較為嚴重的影響;
Container 自己維護 Broker 的上下線,無法與小米內部部署工具結合使用。
因此 Container 并不適合小米內部,但受 Broker Container的啟發,我們提出了另一種與之類似的部署方案——單機多實例。所謂單機多實例,即單臺主機上部署多個 Broker 實例,服務主機就是我們的 Container,Broker 以進程的方式運行,這樣各個 Broker 之前不會相互影響,同時也可以和內部部署工具完美結合。一個簡單的部署架構如下所示:
至此,小米內部完成了 RocketMQ 部署架構的第一次升級,集群中的節點數直接減少了 2/3。在成本優化的前提上依然提供 99.95% 的可用性保障。
多機房容災 -Ⅰ
隨著業務的不斷接入,一些業務提出了機房災備的需求。機房故障的概率雖然極低,但是一旦出現,其帶來的影響是非常大的。比如機房故障導致 RocketMQ 不可用,那么作為流量入口,將會影響到所有的依賴業務。
在多機房容災上,我們結合內部其他服務的部署經驗,先提出了多集群多活的方式,即每個可用區部署一個集群,提供多個集群供業務容災,方案部署架構如下:
用戶視角看到的是三個獨立的集群,需要在相同的可用區部署客戶端去讀寫同機房的 RocketMQ 集群。舉個例子:可用區1的客戶端正常情況下訪問可用區1的 RocketMQ 集群Cluster-1,當Cluster-1故障時,用戶需要手動更改客戶端的連接地址來切換集群,進而將流量轉移到其他機房的集群中。用戶可以通過配置下發去熱更新連接地址,也可以修改配置重啟客戶端來切換,但這一切的操作前提都是:需要業務感知到 RocketMQ 集群故障,手動觸發才可以。
?優點
不用跨區同步數據,低延時(P99寫入10ms)高吞吐(單Broker組寫入TPS達100K)
部署架構簡單,穩定性高
?缺點
集群需預留災備buffer,確保故障時,存活集群可承載故障集群的全部流量
需要業務自己手動切換集群,不夠靈活
若消費存在堆積,故障集群的消息將可能不會被消費,恢復后可消費
?生產耗時
多機房容災 -Ⅱ
可以看到,業務如果選擇以上方式接入的話,需要做一定的適配工作,該方案適用于流量較大的業務接入。然而有一些業務希望可以低成本接入:不做適配,直接使用SDK接入,我們結合 DLedger 自動切換的特性,實驗性的部署了機房故障服務自動 failover 的 模式,部署架構如下所示:
用戶視角看到的就是一個獨立的 RocketMQ 集群,使用 SDK 正常接入即可,無需任何適配。機房故障時依賴 DLedger 自動切主做流量切換。
?優點
部署方便,充分利用 RocketMQ 的原生能力
自動選主,業務接入方便,無需業務手動切換流量
?缺點
跨機房部署,容易受網絡波動,集群抖動概率較大
跨機房部署,會增加寫入延時,從而降低集群吞吐能力
?生產耗時
多機房容災 - PLUS
目前看來 RocketMQ 服務已經在小米完成了很好的落地,日消息量也達到了千億規模,但我們仔細觀察以上兩個方案不難發現,雖然可以實現機房故障切換,但都有一定的缺點,簡要概況如下:
多機房容災 -Ⅰ:同機房請求,延時較低,但需業務手動切換集群
多機房容災 -Ⅱ:自動切流、可消費歷史數據,但對專線負載高,需三個Region才可部署
方案總是存在不夠完美的地方,但不論作為服務的開發者還是業務使用者,其實都希望可以在實現以下幾個目標的前提下做到災備:
1)低成本:雙Region可以完成部署;
2)低耗時:盡量同機房請求,減少網絡耗時;
3)自動切流:機房故障時,可自動將流量切到正常的機房內。
為了實現以上的需求,我們從 RocketMQ 自身的架構出發,希望能夠以最低的改造成本支持災備。我們發現客戶端都是根據 Namesrv 返回的元數據進行生產、消費,只要客戶端能夠在機房故障時,可以根據元數據自動將流量切走即可,因此我們將視角移到了客戶端,希望從客戶端上支持災備的功能。
RocketMQ 所有 Broker 都會將自己注冊到 Namesrv 上去,一旦某個 Broker 組故障,那么它的信息將會被從 Namesrv 中移除,客戶端也就無法再向這類 Broker 組發送、拉取消息。基于以上邏輯,只要我們將 Broker組部署在不同機房中,便可以做到機房級別的災備效果。部署架構如下:
我們以一個實際的例子來講解以上方案的可行性:Topic-A 在兩個可用區上均存在分區,SDK在使用時需要配置自己所在的region。
對于生產者來說,客戶端只會向位于相同可用區的分區發送消息。例如:位于可用區1的客戶端只會向可用區1發送消息,當可用區1故障時,由于在可用區1不存在可寫的分區,便會開始向可用區2發送消息,從而實現生產側的自動切流。消費者同樣需要配置 region ,所有的消費實例會先按照可用區分別去做 rebalance:分區會優先被相同可用區的消費者去分配消費。當可用區1故障時,由于生產者已經將流量切走,因此消費者不需要做特殊變更就做到了消費自動切流。
該方案對于業務來說是一個可選項,業務可自行決定是否需要開啟災備模式,因此較為靈活,可以說是結合了以往兩種機房災備方案的優點,但是仍有不足之處,比如故障集群在故障期間歷史消息不可被消費等,后續也會不斷的優化方案。
03 來做個總結吧!
本文介紹了四種部署模式,針對不同的業務需求提供不同的部署模式,總結如下:
目前以上方案在小米內部均有具體的業務場景,消息量約占總體的 90%,未來也會逐步將剩余流量相關集群全部升級為機房災備集群,從而提供 99.99% 的可用性服務能力。
關鍵詞:
責任編輯:Rex_23