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