萬億級(jí)調(diào)用系統(tǒng):微信序列號(hào)生成器架構(gòu)設(shè)計(jì)及演變
每天萬億級(jí)調(diào)用的重量級(jí)系統(tǒng)荸百,每次申請序列號(hào)平時(shí)調(diào)用耗時(shí)1ms潮针,99.9%的調(diào)用耗時(shí)小于3ms塘匣,服務(wù)部署于數(shù)百臺(tái)4核CPU服務(wù)器上净当!
老司機(jī)介紹
曾欽松,微信高級(jí)工程師祈坠,目前負(fù)責(zé)微信后臺(tái)基礎(chǔ)服務(wù)、朋友圈后臺(tái)等開發(fā)優(yōu)化矢劲,致力于高可用高性能后臺(tái)系統(tǒng)的設(shè)計(jì)與研發(fā)赦拘。2011年畢業(yè)于西安電子科技大學(xué),早先曾在騰訊搜搜從事檢索架構(gòu)芬沉、分布式數(shù)據(jù)庫方面的工作躺同。
微信在立項(xiàng)之初,就已確立了利用數(shù)據(jù)版本號(hào)實(shí)現(xiàn)終端與后臺(tái)的數(shù)據(jù)增量同步機(jī)制花嘶,確保發(fā)消息時(shí)消息可靠送達(dá)對方手機(jī)笋籽,避免了大量潛在的家庭糾紛。時(shí)至今日椭员,微信已經(jīng)走過第五個(gè)年頭车海,這套同步機(jī)制仍然在消息收發(fā)、朋友圈通知、好友數(shù)據(jù)更新等需要數(shù)據(jù)同步的地方發(fā)揮著核心的作用侍芝。
而在這同步機(jī)制的背后研铆,需要一個(gè)高可用、高可靠的序列號(hào)生成器來產(chǎn)生同步數(shù)據(jù)用的版本號(hào)州叠。這個(gè)序列號(hào)生成器我們稱之為seqsvr棵红,目前已經(jīng)發(fā)展為一個(gè)每天萬億級(jí)調(diào)用的重量級(jí)系統(tǒng),其中每次申請序列號(hào)平時(shí)調(diào)用耗時(shí)1ms咧栗,99.9%的調(diào)用耗時(shí)小于3ms逆甜,服務(wù)部署于數(shù)百臺(tái)4核CPU服務(wù)器上。本文會(huì)重點(diǎn)介紹seqsvr的架構(gòu)核心思想致板,以及seqsvr隨著業(yè)務(wù)量快速上漲所做的架構(gòu)演變交煞。
背景
微信服務(wù)器端為每一份需要與客戶端同步的數(shù)據(jù)(例如消息)都會(huì)賦予一個(gè)唯一的、遞增的序列號(hào)(后文稱為sequence)斟或,作為這份數(shù)據(jù)的版本號(hào)素征。在客戶端與服務(wù)器端同步的時(shí)候,客戶端會(huì)帶上已經(jīng)同步下去數(shù)據(jù)的最大版本號(hào)萝挤,后臺(tái)會(huì)根據(jù)客戶端最大版本號(hào)與服務(wù)器端的最大版本號(hào)御毅,計(jì)算出需要同步的增量數(shù)據(jù),返回給客戶端怜珍。這樣不僅保證了客戶端與服務(wù)器端的數(shù)據(jù)同步的可靠性端蛆,同時(shí)也大幅減少了同步時(shí)的冗余數(shù)據(jù)。
這里不用樂觀鎖機(jī)制來生成版本號(hào)绘面,而是使用了一個(gè)獨(dú)立的seqsvr來處理序列號(hào)操作欺税,一方面因?yàn)闃I(yè)務(wù)有大量的sequence查詢需求——查詢已經(jīng)分配出去的最后一個(gè)sequence,而基于seqsvr的查詢操作可以做到非常輕量級(jí)揭璃,避免對存儲(chǔ)層的大量IO查詢操作晚凿;另一方面微信用戶的不同種類的數(shù)據(jù)存在不同的Key-Value系統(tǒng)中,使用統(tǒng)一的序列號(hào)有助于避免重復(fù)開發(fā)瘦馍,同時(shí)業(yè)務(wù)邏輯可以很方便地判斷一個(gè)用戶的各類數(shù)據(jù)是否有更新歼秽。
從seqsvr申請的、用作數(shù)據(jù)版本號(hào)的sequence情组,具有兩種基本的性質(zhì):
遞增的64位整型變量
每個(gè)用戶都有自己獨(dú)立的64位sequence空間
舉個(gè)例子燥筷,小明當(dāng)前申請的sequence為100,那么他下一次申請的sequence院崇,可能為101肆氓,也可能是110,總之一定大于之前申請的100底瓣。而小紅呢谢揪,她的sequence與小明的sequence是獨(dú)立開的,假如她當(dāng)前申請到的sequence為50,然后期間不管小明申請多少次sequence怎么折騰拨扶,都不會(huì)影響到她下一次申請到的值(很可能是51)凳鬓。
這里用了每個(gè)用戶獨(dú)立的64位sequence的體系,而不是用一個(gè)全局的64位(或更高位)sequence患民,很大原因是全局唯一的sequence會(huì)有非常嚴(yán)重的申請互斥問題缩举,不容易去實(shí)現(xiàn)一個(gè)高性能高可靠的架構(gòu)。對微信業(yè)務(wù)來說匹颤,每個(gè)用戶獨(dú)立的64位sequence空間已經(jīng)滿足業(yè)務(wù)要求仅孩。
目前sequence用在終端與后臺(tái)的數(shù)據(jù)同步外,同時(shí)也廣泛用于微信后臺(tái)邏輯層的基礎(chǔ)數(shù)據(jù)一致性cache中印蓖,大幅減少邏輯層對存儲(chǔ)層的訪問杠氢。雖然一個(gè)用于終端——后臺(tái)數(shù)據(jù)同步,一個(gè)用于后臺(tái)cache的一致性保證另伍,場景大不相同。
但我們仔細(xì)分析就會(huì)發(fā)現(xiàn)绞旅,兩個(gè)場景都是利用sequence可靠遞增的性質(zhì)來實(shí)現(xiàn)數(shù)據(jù)的一致性保證摆尝,這就要求我們的seqsvr保證分配出去的sequence是穩(wěn)定遞增的,一旦出現(xiàn)回退必然導(dǎo)致各種數(shù)據(jù)錯(cuò)亂因悲、消息消失堕汞;另外,這兩個(gè)場景都非常普遍晃琳,我們在使用微信的時(shí)候會(huì)不知不覺地對應(yīng)到這兩個(gè)場景:小明給小紅發(fā)消息讯检、小紅拉黑小明、小明發(fā)一條失戀狀態(tài)的朋友圈卫旱,一次簡單的分手背后可能申請了無數(shù)次sequence人灼。
微信目前擁有數(shù)億的活躍用戶,每時(shí)每刻都會(huì)有海量sequence申請顾翼,這對seqsvr的設(shè)計(jì)也是個(gè)極大的挑戰(zhàn)投放。那么,既要sequence可靠遞增适贸,又要能頂住海量的訪問灸芳,要如何設(shè)計(jì)seqsvr的架構(gòu)?我們先從seqsvr的架構(gòu)原型說起拜姿。
架構(gòu)原型
不考慮seqsvr的具體架構(gòu)的話烙样,它應(yīng)該是一個(gè)巨大的64位數(shù)組,而我們每一個(gè)微信用戶蕊肥,都在這個(gè)大數(shù)組里獨(dú)占一格8bytes的空間谒获,這個(gè)格子就放著用戶已經(jīng)分配出去的最后一個(gè)sequence:cur_seq。每個(gè)用戶來申請sequence的時(shí)候,只需要將用戶的cur_seq+=1究反,保存回?cái)?shù)組寻定,并返回給用戶。
圖1. 小明申請了一個(gè)sequence精耐,返回101
預(yù)分配中間層
任何一件看起來很簡單的事狼速,在海量的訪問量下都會(huì)變得不簡單。前文提到卦停,seqsvr需要保證分配出去的sequence遞增(數(shù)據(jù)可靠)向胡,還需要滿足海量的訪問量(每天接近萬億級(jí)別的訪問)。滿足數(shù)據(jù)可靠的話惊完,我們很容易想到把數(shù)據(jù)持久化到硬盤僵芹,但是按照目前每秒千萬級(jí)的訪問量(~10^7 QPS),基本沒有任何硬盤系統(tǒng)能扛住小槐。
后臺(tái)架構(gòu)設(shè)計(jì)很多時(shí)候是一門關(guān)于權(quán)衡的哲學(xué)拇派,針對不同的場景去考慮能不能降低某方面的要求,以換取其它方面的提升凿跳。仔細(xì)考慮我們的需求件豌,我們只要求遞增,并沒有要求連續(xù)控嗜,也就是說出現(xiàn)一大段跳躍是允許的(例如分配出的sequence序列:1,2,3,10,100,101)茧彤。于是我們實(shí)現(xiàn)了一個(gè)簡單優(yōu)雅的策略:
內(nèi)存中儲(chǔ)存最近一個(gè)分配出去的sequence:cur_seq,以及分配上限:max_seq
分配sequence時(shí)疆栏,將cur_seq++曾掂,同時(shí)與分配上限max_seq比較:如果cur_seq > max_seq,將分配上限提升一個(gè)步長max_seq += step壁顶,并持久化max_seq
重啟時(shí)珠洗,讀出持久化的max_seq,賦值給cur_seq
圖2. 小明博助、小紅险污、小白都各自申請了一個(gè)sequence,但只有小白的max_seq增加了步長100
這樣通過增加一個(gè)預(yù)分配sequence的中間層富岳,在保證sequence不回退的前提下蛔糯,大幅地提升了分配sequence的性能。實(shí)際應(yīng)用中每次提升的步長為10000窖式,那么持久化的硬盤IO次數(shù)從之前~10^7 QPS降低到~10^3 QPS蚁飒,處于可接受范圍。在正常運(yùn)作時(shí)分配出去的sequence是順序遞增的萝喘,只有在機(jī)器重啟后淮逻,第一次分配的sequence會(huì)產(chǎn)生一個(gè)比較大的跳躍琼懊,跳躍大小取決于步長大小。
分號(hào)段共享存儲(chǔ)
請求帶來的硬盤IO問題解決了爬早,可以支持服務(wù)平穩(wěn)運(yùn)行哼丈,但該模型還是存在一個(gè)問題:重啟時(shí)要讀取大量的max_seq數(shù)據(jù)加載到內(nèi)存中。
我們可以簡單計(jì)算下筛严,以目前uid(用戶唯一ID)上限2^32個(gè)醉旦、一個(gè)max_seq 8bytes的空間,數(shù)據(jù)大小一共為32GB桨啃,從硬盤加載需要不少時(shí)間车胡。另一方面,出于數(shù)據(jù)可靠性的考慮照瘾,必然需要一個(gè)可靠存儲(chǔ)系統(tǒng)來保存max_seq數(shù)據(jù)匈棘,重啟時(shí)通過網(wǎng)絡(luò)從該可靠存儲(chǔ)系統(tǒng)加載數(shù)據(jù)。如果max_seq數(shù)據(jù)過大的話析命,會(huì)導(dǎo)致重啟時(shí)在數(shù)據(jù)傳輸花費(fèi)大量時(shí)間主卫,造成一段時(shí)間不可服務(wù)。
為了解決這個(gè)問題鹃愤,我們引入號(hào)段Section的概念队秩,uid相鄰的一段用戶屬于一個(gè)號(hào)段,而同個(gè)號(hào)段內(nèi)的用戶共享一個(gè)max_seq昼浦,這樣大幅減少了max_seq數(shù)據(jù)的大小,同時(shí)也降低了IO次數(shù)筒主。
圖3. 小明关噪、小紅、小白屬于同個(gè)Section乌妙,他們共用一個(gè)max_seq使兔。在每個(gè)人都申請一個(gè)sequence的時(shí)候,只有小白突破了max_seq上限藤韵,需要更新max_seq并持久化
目前seqsvr一個(gè)Section包含10萬個(gè)uid虐沥,max_seq數(shù)據(jù)只有300+KB,為我們實(shí)現(xiàn)從可靠存儲(chǔ)系統(tǒng)讀取max_seq數(shù)據(jù)重啟打下基礎(chǔ)泽艘。
工程實(shí)現(xiàn)
工程實(shí)現(xiàn)在上面兩個(gè)策略上做了一些調(diào)整欲险,主要是出于數(shù)據(jù)可靠性及災(zāi)難隔離考慮
把存儲(chǔ)層和緩存中間層分成兩個(gè)模塊StoreSvr及AllocSvr。StoreSvr為存儲(chǔ)層匹涮,利用了多機(jī)NRW策略來保證數(shù)據(jù)持久化后不丟失天试;AllocSvr則是緩存中間層,部署于多臺(tái)機(jī)器然低,每臺(tái)AllocSvr負(fù)責(zé)若干號(hào)段的sequence分配喜每,分?jǐn)偤A康膕equence申請請求务唐。
整個(gè)系統(tǒng)又按uid范圍進(jìn)行分Set,每個(gè)Set都是一個(gè)完整的带兜、獨(dú)立的StoreSvr+AllocSvr子系統(tǒng)枫笛。分Set設(shè)計(jì)目的是為了做災(zāi)難隔離,一個(gè)Set出現(xiàn)故障只會(huì)影響該Set內(nèi)的用戶刚照,而不會(huì)影響到其它用戶刑巧。
圖4. 原型架構(gòu)圖
容災(zāi)設(shè)計(jì)
接下來我們會(huì)介紹seqsvr的容災(zāi)架構(gòu)。我們知道涩咖,后臺(tái)系統(tǒng)絕大部分情況下并沒有一種唯一的海诲、完美的解決方案,同樣的需求在不同的環(huán)境背景下甚至有可能演化出兩種截然不同的架構(gòu)檩互。既然架構(gòu)是多變的特幔,那純粹講架構(gòu)的意義并不是特別大,期間也會(huì)講下seqsvr容災(zāi)設(shè)計(jì)時(shí)的一些思考和權(quán)衡闸昨,希望對大家有所幫助蚯斯。
seqsvr的容災(zāi)模型在五年中進(jìn)行過一次比較大的重構(gòu),提升了可用性饵较、機(jī)器利用率等方面拍嵌。其中不管是重構(gòu)前還是重構(gòu)后的架構(gòu),seqsvr一直遵循著兩條架構(gòu)設(shè)計(jì)原則:
保持自身架構(gòu)簡單
避免對外部模塊的強(qiáng)依賴
這兩點(diǎn)都是基于seqsvr可靠性考慮的循诉,畢竟seqsvr是一個(gè)與整個(gè)微信服務(wù)端正常運(yùn)行息息相關(guān)的模塊横辆。按照我們對這個(gè)世界的認(rèn)識(shí),系統(tǒng)的復(fù)雜度往往是跟可靠性成反比的茄猫,想得到一個(gè)可靠的系統(tǒng)一個(gè)關(guān)鍵點(diǎn)就是要把它做簡單狈蚤。相信大家身邊都有一些這樣的例子,設(shè)計(jì)方案里有很多高大上划纽、復(fù)雜的東西脆侮,同時(shí)也總能看到他們在默默地填一些高大上的坑。當(dāng)然簡單的系統(tǒng)不意味著粗制濫造勇劣,我們要做的是理出最核心的點(diǎn)靖避,然后在滿足這些核心點(diǎn)的基礎(chǔ)上,針對性地提出一個(gè)足夠簡單的解決方案比默。
那么幻捏,seqsvr最核心的點(diǎn)是什么呢?每個(gè)uid的sequence申請要遞增不回退命咐。這里我們發(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ù),探測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ī)所有的請求,容易導(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)用。這種模型對于無狀態(tài)的服務(wù)端轴脐,是很好用的调卑,也很方便實(shí)現(xiàn)容災(zāi)。我們可以在client配置文件里面寫“對于號(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ù)想要申請某個(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è)路由信息的變更艺智?
這時(shí)候假如我們的AllocSvr采用了主備容災(zāi)模型的話倘要,事情就變得簡單多了。我們可以在client配置文件里寫:對于某個(gè)uid號(hào)段十拣,要么是AllocSvrA加載封拧,要么是AllocSvrB加載。Client端發(fā)起請求時(shí)夭问,盡管Client端并不清楚AllocSvrA和AllocSvrB哪一臺(tái)真正加載了目標(biāo)uid號(hào)段泽西,但是Client端可以先嘗試給其中任意一臺(tái)AllocSvr發(fā)請求,就算這次請求了錯(cuò)誤的AllocSvr缰趋,那么就知道另外一臺(tái)是正確的AllocSvr捧杉,再發(fā)起一次請求即可。
也就是說秘血,對于主備容災(zāi)模型味抖,最多也只會(huì)浪費(fèi)一次的試探請求來確定AllocSvr的服務(wù)狀態(tài),額外消耗少灰粮,編碼也簡單仔涩。可是粘舟,如果Svr端采用了其它復(fù)雜的容災(zāi)策略熔脂,那么基于靜態(tài)配置的框架就很難去確定Svr端的服務(wù)狀態(tài):Svr發(fā)生狀態(tài)變更,Client端無法確定應(yīng)該向哪臺(tái)Svr發(fā)起請求蓖乘。這也是為什么一開始選擇了主備容災(zāi)的原因之一。
主備容災(zāi)的缺陷
在我們的實(shí)際運(yùn)營中韧骗,容災(zāi)1.0架構(gòu)存在兩個(gè)重大的不足:
擴(kuò)容嘉抒、縮容非常麻煩
一個(gè)Set的主備機(jī)都過載,無法使用其他Set的機(jī)器進(jìn)行容災(zāi)
在主備容災(zāi)中袍暴,Client和AllocSvr需要使用完全一致的配置文件些侍。變更這個(gè)配置文件的時(shí)候隶症,由于無法實(shí)現(xiàn)在同一時(shí)間更新給所有的Client和AllocSvr,因此需要非常復(fù)雜的人工操作來保證變更的正確性(包括需要使用iptables來做請求轉(zhuǎn)發(fā)岗宣,具體的詳情這里不做展開)蚂会。
對于第二個(gè)問題,常見的方法是用一致性Hash算法替代主備耗式,一個(gè)Set有多臺(tái)機(jī)器胁住,過載機(jī)器的請求被分?jǐn)偟蕉嗯_(tái)機(jī)器,容災(zāi)效果會(huì)更好刊咳。在seqsvr中使用類似一致性Hash的容災(zāi)策略也是可行的彪见,只要Client端與仲裁服務(wù)都使用完全一樣的一致性Hash算法,這樣Client端可以啟發(fā)式地去嘗試娱挨,直到找到正確的AllocSvr余指。
例如對于某個(gè)uid,仲裁服務(wù)會(huì)優(yōu)先把它分配到AllocSvrA跷坝,如果AllocSvrA掛掉則分配到AllocSvrB酵镜,再不行分配到AllocSvrC。那么Client在訪問AllocSvr時(shí)柴钻,按照AllocSvrA -> AllocSvrB -> AllocSvrC的順序去訪問淮韭,也能實(shí)現(xiàn)容災(zāi)的目的。但這種方法仍然沒有克服前面主備容災(zāi)面臨的配置文件變更的問題,運(yùn)營起來也很麻煩。
容災(zāi)2.0架構(gòu):嵌入式路由表容災(zāi)
最后我們另辟蹊徑狮惜,采用了一種不同的思路:既然Client端與AllocSvr存在路由狀態(tài)不一致的問題对省,那么讓AllocSvr把當(dāng)前的路由狀態(tài)傳遞給Client端,打破之前只能根據(jù)本地Client配置文件做路由決策的限制伏伐,從根本上解決這個(gè)問題。
所以在2.0架構(gòu)中,我們把AllocSvr的路由狀態(tài)嵌入到Client請求sequence的響應(yīng)包中捞慌,在不帶來額外的資源消耗的情況下,實(shí)現(xiàn)了Client端與AllocSvr之間的路由狀態(tài)一致柬批。具體實(shí)現(xiàn)方案如下:
seqsvr所有模塊使用了統(tǒng)一的路由表啸澡,描述了uid號(hào)段到AllocSvr的全映射。這份路由表由仲裁服務(wù)根據(jù)AllocSvr的服務(wù)狀態(tài)生成氮帐,寫到StoreSvr中嗅虏,由AllocSvr當(dāng)作租約讀出,最后在業(yè)務(wù)返回包里旁路給Client端上沐。
圖9. 容災(zāi)2.0架構(gòu):動(dòng)態(tài)號(hào)段遷移容災(zāi)
把路由表嵌入到請求響應(yīng)包看似很簡單的架構(gòu)變動(dòng)皮服,卻是整個(gè)seqsvr容災(zāi)架構(gòu)的技術(shù)奇點(diǎn)。利用它解決了路由狀態(tài)不一致的問題后,可以實(shí)現(xiàn)一些以前不容易實(shí)現(xiàn)的特性龄广。例如靈活的容災(zāi)策略硫眯,讓所有機(jī)器都互為備機(jī),在機(jī)器故障時(shí)择同,把故障機(jī)上的號(hào)段均勻地遷移到其它可用的AllocSvr上两入;還可以根據(jù)AllocSvr的負(fù)載情況,進(jìn)行負(fù)載均衡敲才,有效緩解AllocSvr請求不均的問題裹纳,大幅提升機(jī)器使用率。
另外在運(yùn)營上也得到了大幅簡化归斤。之前對機(jī)器進(jìn)行運(yùn)維操作有著繁雜的操作步驟痊夭,而新架構(gòu)只需要更新路由即可輕松實(shí)現(xiàn)上線、下線脏里、替換機(jī)器她我,不需要關(guān)心配置文件不一致的問題,避免了一些由于人工誤操作引發(fā)的故障迫横。
圖10. 機(jī)器故障號(hào)段遷移
路由同步優(yōu)化
把路由表嵌入到取sequence的請求響應(yīng)包中番舆,那么會(huì)引入一個(gè)類似“先有雞還是先有蛋”的哲學(xué)命題:沒有路由表,怎么知道去哪臺(tái)AllocSvr取路由表矾踱?另外恨狈,取sequence是一個(gè)超高頻的請求,如何避免嵌入路由表帶來的帶寬消耗呛讲?
這里通過在Client端內(nèi)存緩存路由表以及路由版本號(hào)來解決禾怠,請求步驟如下:
Client根據(jù)本地共享內(nèi)存緩存的路由表,選擇對應(yīng)的AllocSvr贝搁;如果路由表不存在吗氏,隨機(jī)選擇一臺(tái)AllocSvr
對選中的AllocSvr發(fā)起請求,請求帶上本地路由表的版本號(hào)
AllocSvr收到請求雷逆,除了處理sequence邏輯外弦讽,判斷Client帶上版本號(hào)是否最新,如果是舊版則在響應(yīng)包中附上最新的路由表
Client收到響應(yīng)包膀哲,除了處理sequence邏輯外往产,判斷響應(yīng)包是否帶有新路由表。如果有某宪,更新本地路由表仿村,并決策是否返回第1步重試
基于以上的請求步驟,在本地路由表失效的時(shí)候兴喂,使用少量的重試便可以拉到正確的路由蔼囊,正常提供服務(wù)包颁。
總結(jié)
到此把seqsvr的架構(gòu)設(shè)計(jì)和演變基本講完了,正是如此簡單優(yōu)雅的模型压真,為微信的其它模塊提供了一種簡單可靠的一致性解決方案,支撐著微信五年來的高速發(fā)展蘑险,相信在可預(yù)見的未來仍然會(huì)發(fā)揮著重要的作用滴肿。
▽
延展閱讀(點(diǎn)擊標(biāo)題):
微信朋友圈技術(shù)之道:三個(gè)人的后臺(tái)團(tuán)隊(duì)與每日十億的發(fā)布量
微信斑馬系統(tǒng):微信朋友圈廣告背后的利器
從0到1:微信后臺(tái)系統(tǒng)的演進(jìn)之路