后臺架構(gòu)設計很多時候是一門關于權(quán)衡的哲學,針對不同的場景去考慮能不能降低某方面的要求,以換取其它方面的提升。仔細考慮我們的需求,我們只要求遞增,并沒有要求連續(xù),也就是說出現(xiàn)一大段跳躍是允許的(例如分配出的sequence序列:1,2,3,10,100,101)。于是我們實現(xiàn)了一個簡單優(yōu)雅的策略:
內(nèi)存中儲存最近一個分配出去的sequence:cur_seq,以及分配上限:max_seq
分配sequence時,將cur_seq++,同時與分配上限max_seq比較:如果cur_seq > max_seq,將分配上限提升一個步長max_seq += step,并持久化max_seq
重啟時,讀出持久化的max_seq,賦值給cur_seq

圖2. 小明、小紅、小白都各自申請了一個sequence,但只有小白的max_seq增加了步長100
這樣通過增加一個預分配sequence的中間層,在保證sequence不回退的前提下,大幅地提升了分配sequence的性能。實際應用中每次提升的步長為10000,那么持久化的硬盤IO次數(shù)從之前~10^7 QPS降低到~10^3 QPS,處于可接受范圍。在正常運作時分配出去的sequence是順序遞增的,只有在機器重啟后,第一次分配的sequence會產(chǎn)生一個比較大的跳躍,跳躍大小取決于步長大小。
分號段共享存儲
請求帶來的硬盤IO問題解決了,可以支持服務平穩(wěn)運行,但該模型還是存在一個問題:重啟時要讀取大量的max_seq數(shù)據(jù)加載到內(nèi)存中。
我們可以簡單計算下,以目前uid(用戶唯一ID)上限2^32個、一個max_seq 8bytes的空間,數(shù)據(jù)大小一共為32GB,從硬盤加載需要不少時間。另一方面,出于數(shù)據(jù)可靠性的考慮,必然需要一個可靠存儲系統(tǒng)來保存max_seq數(shù)據(jù),重啟時通過網(wǎng)絡從該可靠存儲系統(tǒng)加載數(shù)據(jù)。如果max_seq數(shù)據(jù)過大的話,會導致重啟時在數(shù)據(jù)傳輸花費大量時間,造成一段時間不可服務。
為了解決這個問題,我們引入號段Section的概念,uid相鄰的一段用戶屬于一個號段,而同個號段內(nèi)的用戶共享一個max_seq,這樣大幅減少了max_seq數(shù)據(jù)的大小,同時也降低了IO次數(shù)。

圖3. 小明、小紅、小白屬于同個Section,他們共用一個max_seq。在每個人都申請一個sequence的時候,只有小白突破了max_seq上限,需要更新max_seq并持久化
目前seqsvr一個Section包含10萬個uid,max_seq數(shù)據(jù)只有300+KB,為我們實現(xiàn)從可靠存儲系統(tǒng)讀取max_seq數(shù)據(jù)重啟打下基礎。
工程實現(xiàn)
工程實現(xiàn)在上面兩個策略上做了一些調(diào)整,主要是出于數(shù)據(jù)可靠性及災難隔離考慮
把存儲層和緩存中間層分成兩個模塊StoreSvr及AllocSvr。StoreSvr為存儲層,利用了多機NRW策略來保證數(shù)據(jù)持久化后不丟失;AllocSvr則是緩存中間層,部署于多臺機器,每臺AllocSvr負責若干號段的sequence分配,分攤海量的sequence申請請求。
整個系統(tǒng)又按uid范圍進行分Set,每個Set都是一個完整的、獨立的StoreSvr+AllocSvr子系統(tǒng)。分Set設計目的是為了做災難隔離,一個Set出現(xiàn)故障只會影響該Set內(nèi)的用戶,而不會影響到其它用戶。

圖4. 原型架構(gòu)圖
四、容災設計
接下來我們會介紹seqsvr的容災架構(gòu)。我們知道,后臺系統(tǒng)絕大部分情況下并沒有一種唯一的、完美的解決方案,同樣的需求在不同的環(huán)境背景下甚至有可能演化出兩種截然不同的架構(gòu)。既然架構(gòu)是多變的,那純粹講架構(gòu)的意義并不是特別大,期間也會講下seqsvr容災設計時的一些思考和權(quán)衡,希望對大家有所幫助。
seqsvr的容災模型在五年中進行過一次比較大的重構(gòu),提升了可用性、機器利用率等方面。其中不管是重構(gòu)前還是重構(gòu)后的架構(gòu),seqsvr一直遵循著兩條架構(gòu)設計原則:
保持自身架構(gòu)簡單
避免對外部模塊的強依賴
這兩點都是基于seqsvr可靠性考慮的,畢竟seqsvr是一個與整個微信服務端正常運行息息相關的模塊。按照我們對這個世界的認識,系統(tǒng)的復雜度往往是跟可靠性成反比的,想得到一個可靠的系統(tǒng)一個關鍵點就是要把它做簡單。相信大家身邊都有一些這樣的例子,設計方案里有很多高大上、復雜的東西,同時也總能看到他們在默默地填一些高大上的坑。當然簡單的系統(tǒng)不意味著粗制濫造,我們要做的是理出最核心的點,然后在滿足這些核心點的基礎上,針對性地提出一個足夠簡單的解決方案。