這時(shí)候假如我們的AllocSvr采用了主備容災(zāi)模型的話,事情就變得簡(jiǎn)單多了。我們可以在client配置文件里寫:對(duì)于某個(gè)uid號(hào)段,要么是AllocSvrA加載,要么是AllocSvrB加載。Client端發(fā)起請(qǐng)求時(shí),盡管Client端并不清楚AllocSvrA和AllocSvrB哪一臺(tái)真正加載了目標(biāo)uid號(hào)段,但是Client端可以先嘗試給其中任意一臺(tái)AllocSvr發(fā)請(qǐng)求,就算這次請(qǐng)求了錯(cuò)誤的AllocSvr,那么就知道另外一臺(tái)是正確的AllocSvr,再發(fā)起一次請(qǐng)求即可。
也就是說,對(duì)于主備容災(zāi)模型,最多也只會(huì)浪費(fèi)一次的試探請(qǐng)求來確定AllocSvr的服務(wù)狀態(tài),額外消耗少,編碼也簡(jiǎn)單??墒?,如果Svr端采用了其它復(fù)雜的容災(zāi)策略,那么基于靜態(tài)配置的框架就很難去確定Svr端的服務(wù)狀態(tài):Svr發(fā)生狀態(tài)變更,Client端無法確定應(yīng)該向哪臺(tái)Svr發(fā)起請(qǐng)求。這也是為什么一開始選擇了主備容災(zāi)的原因之一。
主備容災(zāi)的缺陷
在我們的實(shí)際運(yùn)營(yíng)中,容災(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來做請(qǐng)求轉(zhuǎn)發(fā),具體的詳情這里不做展開)。
對(duì)于第二個(gè)問題,常見的方法是用一致性Hash算法替代主備,一個(gè)Set有多臺(tái)機(jī)器,過載機(jī)器的請(qǐng)求被分?jǐn)偟蕉嗯_(tái)機(jī)器,容災(zāi)效果會(huì)更好。在seqsvr中使用類似一致性Hash的容災(zāi)策略也是可行的,只要Client端與仲裁服務(wù)都使用完全一樣的一致性Hash算法,這樣Client端可以啟發(fā)式地去嘗試,直到找到正確的AllocSvr。例如對(duì)于某個(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)營(yíng)起來也很麻煩。
六、容災(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請(qǐng)求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)
把路由表嵌入到請(qǐng)求響應(yīng)包看似很簡(jiǎn)單的架構(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請(qǐng)求不均的問題,大幅提升機(jī)器使用率。
另外在運(yùn)營(yíng)上也得到了大幅簡(jiǎn)化。之前對(duì)機(jī)器進(jìn)行運(yùn)維操作有著繁雜的操作步驟,而新架構(gòu)只需要更新路由即可輕松實(shí)現(xiàn)上線、下線、替換機(jī)器,不需要關(guān)心配置文件不一致的問題,避免了一些由于人工誤操作引發(fā)的故障。

圖10. 機(jī)器故障號(hào)段遷移
路由同步優(yōu)化
把路由表嵌入到取sequence的請(qǐng)求響應(yīng)包中,那么會(huì)引入一個(gè)類似“先有雞還是先有蛋”的哲學(xué)命題:沒有路由表,怎么知道去哪臺(tái)AllocSvr取路由表?另外,取sequence是一個(gè)超高頻的請(qǐng)求,如何避免嵌入路由表帶來的帶寬消耗?
這里通過在Client端內(nèi)存緩存路由表以及路由版本號(hào)來解決,請(qǐng)求步驟如下: