那么,seqsvr最核心的點(diǎn)是什么呢?每個(gè)uid的sequence申請(qǐng)要遞增不回退。這里我們發(fā)現(xiàn),如果seqsvr滿足這么一個(gè)約束:任意時(shí)刻任意uid有且僅有一臺(tái)AllocSvr提供服務(wù),就可以比較容易地實(shí)現(xiàn)sequence遞增不回退的要求。

圖5. 兩臺(tái)AllocSvr服務(wù)同個(gè)uid造成sequence回退。Client讀取到的sequence序列為101、201、102
但也由于這個(gè)約束,多臺(tái)AllocSvr同時(shí)服務(wù)同一個(gè)號(hào)段的多主機(jī)模型在這里就不適用了。我們只能采用單點(diǎn)服務(wù)的模式,當(dāng)某臺(tái)AllocSvr發(fā)生服務(wù)不可用時(shí),將該機(jī)服務(wù)的uid段切換到其它機(jī)器來實(shí)現(xiàn)容災(zāi)。這里需要引入一個(gè)仲裁服務(wù),探測(cè)AllocSvr的服務(wù)狀態(tài),決定每個(gè)uid段由哪臺(tái)AllocSvr加載。出于可靠性的考慮,仲裁模塊并不直接操作AllocSvr,而是將加載配置寫到StoreSvr持久化,然后AllocSvr定期訪問StoreSvr讀取最新的加載配置,決定自己的加載狀態(tài)。

圖6. 號(hào)段遷移示意。通過更新加載配置把0~2號(hào)段從AllocSvrA遷移到AllocSvrB
同時(shí),為了避免失聯(lián)AllocSvr提供錯(cuò)誤的服務(wù),返回臟數(shù)據(jù),AllocSvr需要跟StoreSvr保持租約。這個(gè)租約機(jī)制由以下兩個(gè)條件組成:
租約失效:AllocSvr N秒內(nèi)無法從StoreSvr讀取加載配置時(shí),AllocSvr停止服務(wù)
租約生效:AllocSvr讀取到新的加載配置后,立即卸載需要卸載的號(hào)段,需要加載的新號(hào)段等待N秒后提供服務(wù)

圖7. 租約機(jī)制。AllocSvrB嚴(yán)格保證在AllocSvrA停止服務(wù)后提供服務(wù)
這兩個(gè)條件保證了切換時(shí),新AllocSvr肯定在舊AllocSvr下線后才開始提供服務(wù)。但這種租約機(jī)制也會(huì)造成切換的號(hào)段存在小段時(shí)間的不可服務(wù),不過由于微信后臺(tái)邏輯層存在重試機(jī)制及異步重試隊(duì)列,小段時(shí)間的不可服務(wù)是用戶無感知的,而且出現(xiàn)租約失效、切換是小概率事件,整體上是可以接受的。
到此講了AllocSvr容災(zāi)切換的基本原理,接下來會(huì)介紹整個(gè)seqsvr架構(gòu)容災(zāi)架構(gòu)的演變
五、容災(zāi)1.0架構(gòu):主備容災(zāi)
最初版本的seqsvr采用了主機(jī)+冷備機(jī)容災(zāi)模式:全量的uid空間均勻分成N個(gè)Section,連續(xù)的若干個(gè)Section組成了一個(gè)Set,每個(gè)Set都有一主一備兩臺(tái)AllocSvr。正常情況下只有主機(jī)提供服務(wù);在主機(jī)出故障時(shí),仲裁服務(wù)切換主備,原來的主機(jī)下線變成備機(jī),原備機(jī)變成主機(jī)后加載uid號(hào)段提供服務(wù)。

圖8. 容災(zāi)1.0架構(gòu):主備容災(zāi)
可能看到前文的敘述,有些同學(xué)已經(jīng)想到這種容災(zāi)架構(gòu)。一主機(jī)一備機(jī)的模型設(shè)計(jì)簡單,并且具有不錯(cuò)的可用性——畢竟主備兩臺(tái)機(jī)器同時(shí)不可用的概率極低,相信很多后臺(tái)系統(tǒng)也采用了類似的容災(zāi)策略。
設(shè)計(jì)權(quán)衡
主備容災(zāi)存在一些明顯的缺陷,比如備機(jī)閑置導(dǎo)致有一半的空閑機(jī)器;比如主備切換的時(shí)候,備機(jī)在瞬間要接受主機(jī)所有的請(qǐng)求,容易導(dǎo)致備機(jī)過載。既然一主一備容災(zāi)存在這樣的問題,為什么一開始還要采用這種容災(zāi)模型?事實(shí)上,架構(gòu)的選擇往往跟當(dāng)時(shí)的背景有關(guān),seqsvr誕生于微信發(fā)展初期,也正是微信快速擴(kuò)張的時(shí)候,選擇一主一備容災(zāi)模型是出于以下的考慮:
架構(gòu)簡單,可以快速開發(fā)
機(jī)器數(shù)少,機(jī)器冗余不是主要問題
Client端更新AllocSvr的路由狀態(tài)很容易實(shí)現(xiàn)
前兩點(diǎn)好懂,人力、機(jī)器都不如時(shí)間寶貴。而第三點(diǎn)比較有意思,下面展開講下
微信后臺(tái)絕大部分模塊使用了一個(gè)自研的RPC框架,seqsvr也不例外。在這個(gè)RPC框架里,調(diào)用端讀取本地機(jī)器的client配置文件,決定去哪臺(tái)服務(wù)端調(diào)用。這種模型對(duì)于無狀態(tài)的服務(wù)端,是很好用的,也很方便實(shí)現(xiàn)容災(zāi)。我們可以在client配置文件里面寫“對(duì)于號(hào)段x,可以去SvrA、SvrB、SvrC三臺(tái)機(jī)器的任意一臺(tái)訪問”,實(shí)現(xiàn)三主機(jī)容災(zāi)。
但在seqsvr里,AllocSvr是預(yù)分配中間層,并不是無狀態(tài)的。而前面我們提到,AllocSvr加載哪些uid號(hào)段,是由保存在StoreSvr的加載配置決定的。那么這時(shí)候就尷尬了,業(yè)務(wù)想要申請(qǐng)某個(gè)uid的sequence,Client端其實(shí)并不清楚具體去哪臺(tái)AllocSvr訪問,client配置文件只會(huì)跟它說“AllocSvrA、AllocSvrB…這堆機(jī)器的某一臺(tái)會(huì)有你想要的sequence”。換句話講,原來負(fù)責(zé)提供服務(wù)的AllocSvrA故障,仲裁服務(wù)決定由AllocSvrC來替代AllocSvrA提供服務(wù),Client要如何獲知這個(gè)路由信息的變更?