29 | 異地多活設(shè)計(jì)4大技巧?

跨城異地多活是架構(gòu)設(shè)計(jì)復(fù)雜度最高的一種,接下來我將介紹跨城異地多活架構(gòu)

技巧 1:保證核心業(yè)務(wù)的異地多活

“異地多活”是為了保證業(yè)務(wù)的高可用,但很多架構(gòu)師在考慮這個(gè)“業(yè)務(wù)”時(shí),會(huì)不自覺地陷入一個(gè)思維誤區(qū):我要保證所有業(yè)務(wù)都能“異地多活”!

假設(shè)我們需要做一個(gè)“用戶子系統(tǒng)”,這個(gè)子系統(tǒng)負(fù)責(zé)“注冊(cè)”“登錄”“用戶信息”三個(gè)業(yè)務(wù)。為了支持海量用戶,我們?cè)O(shè)計(jì)了一個(gè)“用戶分區(qū)”的架構(gòu),即正常情況下用戶屬于某個(gè)主分區(qū),每個(gè)分區(qū)都有其他數(shù)據(jù)的備份,用戶用郵箱或者手機(jī)號(hào)注冊(cè),路由層拿到郵箱或者手機(jī)號(hào)后,通過 Hash 計(jì)算屬于哪個(gè)中心,然后請(qǐng)求對(duì)應(yīng)的業(yè)務(wù)中心?;镜募軜?gòu)如下:

這樣一個(gè)系統(tǒng),如果 3 個(gè)業(yè)務(wù)要同時(shí)實(shí)現(xiàn)異地多活,會(huì)發(fā)現(xiàn)這些難以解決的問題:

注冊(cè)問題

A 中心注冊(cè)了用戶,數(shù)據(jù)還未同步到 B 中心,此時(shí) A 中心宕機(jī),為了支持注冊(cè)業(yè)務(wù)多活,可以挑選 B 中心讓用戶去重新注冊(cè)??雌饋砗苋菀拙椭С侄嗷盍耍屑?xì)思考一下會(huì)發(fā)現(xiàn)這樣做會(huì)有問題:一個(gè)手機(jī)號(hào)只能注冊(cè)一個(gè)賬號(hào),A 中心的數(shù)據(jù)沒有同步過來,B 中心無法判斷這個(gè)手機(jī)號(hào)是否重復(fù),如果 B 中心讓用戶注冊(cè),后來 A 中心恢復(fù)了,發(fā)現(xiàn)數(shù)據(jù)有沖突,怎么解決?實(shí)際上是無法解決的,因?yàn)橥粋€(gè)手機(jī)號(hào)注冊(cè)的賬號(hào)不能以后一次注冊(cè)為準(zhǔn);而如果 B 中心不支持本來屬于 A 中心的業(yè)務(wù)進(jìn)行注冊(cè),注冊(cè)業(yè)務(wù)的多活又成了空談。

如果我們修改業(yè)務(wù)規(guī)則,允許一個(gè)手機(jī)號(hào)注冊(cè)多個(gè)賬號(hào)不就可以了嗎?

這樣做是不可行的,類似一個(gè)手機(jī)號(hào)只能注冊(cè)一個(gè)賬號(hào)這種規(guī)則,是核心業(yè)務(wù)規(guī)則,修改核心業(yè)務(wù)規(guī)則的代價(jià)非常大,幾乎所有的業(yè)務(wù)都要重新設(shè)計(jì),為了架構(gòu)設(shè)計(jì)去改變業(yè)務(wù)規(guī)則(而且是這么核心的業(yè)務(wù)規(guī)則)是得不償失的。

用戶信息問題

用戶信息的修改和注冊(cè)有類似的問題,即 A、B 兩個(gè)中心在異常的情況下都修改了用戶信息,如何處理沖突

由于用戶信息并沒有賬號(hào)那么關(guān)鍵,一種簡單的處理方式是按照時(shí)間合并,即最后修改的生效。業(yè)務(wù)邏輯上沒問題,但實(shí)際操作也有一個(gè)很關(guān)鍵的“坑”:怎么保證多個(gè)中心所有機(jī)器時(shí)間絕對(duì)一致?在異地多中心的網(wǎng)絡(luò)下,這個(gè)是無法保證的,即使有時(shí)間同步也無法完全保證,只要兩個(gè)中心的時(shí)間誤差超過 1 秒,數(shù)據(jù)就可能出現(xiàn)混亂,即先修改的反而生效。

還有一種方式是生成全局唯一遞增 ID,這個(gè)方案的成本很高,因?yàn)檫@個(gè)全局唯一遞增 ID 的系統(tǒng)本身又要考慮異地多活,同樣涉及數(shù)據(jù)一致性和沖突的問題。

優(yōu)先實(shí)現(xiàn)核心業(yè)務(wù)的異地多活架構(gòu)!

對(duì)于這個(gè)模擬案例來說,“登錄”才是最核心的業(yè)務(wù),“注冊(cè)”和“用戶信息”雖然也是主要業(yè)務(wù),但并不一定要實(shí)現(xiàn)異地多活,主要原因在于業(yè)務(wù)影響不同。對(duì)于一個(gè)日活 1000 萬的業(yè)務(wù)來說,每天注冊(cè)用戶可能是幾萬,修改用戶信息的可能還不到 1 萬,但登錄用戶是 1000 萬,很明顯我們應(yīng)該保證登錄的異地多活。

而登錄實(shí)現(xiàn)“異地多活”恰恰是最簡單的,因?yàn)槊總€(gè)中心都有所有用戶的賬號(hào)和密碼信息,用戶在哪個(gè)中心都可以登錄。用戶在 A 中心登錄,A 中心宕機(jī)后,用戶到 B 中心重新登錄即可。

如果某個(gè)用戶在 A 中心修改了密碼,此時(shí)數(shù)據(jù)還沒有同步到 B 中心,用戶到 B 中心登錄是無法登錄的,這個(gè)怎么處理?

技巧 2:保證核心數(shù)據(jù)最終一致性

異地多活本質(zhì)上是通過異地的數(shù)據(jù)冗余,來保證在極端異常的情況下業(yè)務(wù)也能夠正常提供給用戶。

數(shù)據(jù)冗余是要將數(shù)據(jù)從 A 地同步到 B 地,從業(yè)務(wù)的角度來看是越快越好,但不可能很快,因?yàn)檫@是物理定律決定的,幾種方法可以參考:

盡量減少異地多活機(jī)房的距離,搭建高速網(wǎng)絡(luò)

這和我上一期講到的同城異區(qū)架構(gòu)類似,但搭建跨城異地的高速網(wǎng)絡(luò)成本遠(yuǎn)遠(yuǎn)超過同城異區(qū)的高速網(wǎng)絡(luò),成本巨大,一般只有巨頭公司才能承擔(dān)。

盡量減少數(shù)據(jù)同步,只同步核心業(yè)務(wù)相關(guān)的數(shù)據(jù)

簡單來說就是不重要的數(shù)據(jù)不同步,同步后沒用的數(shù)據(jù)不同步,只同步核心業(yè)務(wù)相關(guān)的數(shù)據(jù)。

以前面的“用戶子系統(tǒng)”為例,用戶登錄所產(chǎn)生的 token 或者 session 信息,數(shù)據(jù)量很大,但其實(shí)并不需要同步到其他業(yè)務(wù)中心,因?yàn)檫@些數(shù)據(jù)丟失后重新登錄就可以再次獲取了。

保證最終一致性,不保證實(shí)時(shí)一致性

例如,A 機(jī)房注冊(cè)了一個(gè)用戶,業(yè)務(wù)上不要求能夠在 50 毫秒內(nèi)就同步到所有機(jī)房,正常情況下要求 5 分鐘同步到所有機(jī)房即可,異常情況下甚至可以允許 1 小時(shí)或者 1 天后能夠一致。

最終一致性在具體實(shí)現(xiàn)時(shí),還需要根據(jù)不同的數(shù)據(jù)特征,進(jìn)行差異化的處理,以滿足業(yè)務(wù)需要。例如,對(duì)“賬號(hào)”信息來說,如果在 A 機(jī)房新注冊(cè)的用戶 5 分鐘內(nèi)正好跑到 B 機(jī)房了,此時(shí) B 機(jī)房還沒有這個(gè)用戶的信息,為了保證業(yè)務(wù)的正確,B 機(jī)房就需要根據(jù)路由規(guī)則到 A 機(jī)房請(qǐng)求數(shù)據(jù)。

而對(duì)“用戶信息”來說,5 分鐘后同步也沒有問題,也不需要采取其他措施來彌補(bǔ),但還是會(huì)影響用戶體驗(yàn),即用戶看到了舊的用戶信息,這個(gè)問題怎么解決呢?

技巧 3:采用多種手段同步數(shù)據(jù)

數(shù)據(jù)同步是異地多活架構(gòu)設(shè)計(jì)的核心,幸運(yùn)的是基本上存儲(chǔ)系統(tǒng)本身都會(huì)有同步的功能。例如,MySQL 的主備復(fù)制、Redis 的 Cluster 功能、Elasticsearch 的集群功能。這些系統(tǒng)本身的同步功能已經(jīng)比較強(qiáng)大,能夠直接拿來就用,但這也無形中將我們引入了一個(gè)思維誤區(qū):只使用存儲(chǔ)系統(tǒng)的同步功能!

既然說存儲(chǔ)系統(tǒng)本身就有同步功能,而且同步功能還很強(qiáng)大,為何說只使用存儲(chǔ)系統(tǒng)是一個(gè)思維誤區(qū)呢?因?yàn)殡m然絕大部分場景下,存儲(chǔ)系統(tǒng)本身的同步功能基本上也夠用了,但在某些比較極端的情況下,存儲(chǔ)系統(tǒng)本身的同步功能可能難以滿足業(yè)務(wù)需求。

MySQL 為例,MySQL 5.1 版本的復(fù)制是單線程的復(fù)制,在網(wǎng)絡(luò)抖動(dòng)或者大量數(shù)據(jù)同步時(shí),經(jīng)常發(fā)生延遲較長的問題,短則延遲十幾秒,長則可能達(dá)到十幾分鐘。而且即使我們通過監(jiān)控的手段知道了 MySQL 同步時(shí)延較長,也難以采取什么措施,只能干等。

Redis 又是另外一個(gè)問題,Redis 3.0 之前沒有 Cluster 功能,只有主從復(fù)制功能,而為了設(shè)計(jì)上的簡單,Redis 2.8 之前的版本,主從復(fù)制有一個(gè)比較大的隱患:從機(jī)宕機(jī)或者和主機(jī)斷開連接都需要重新連接主機(jī),重新連接主機(jī)都會(huì)觸發(fā)全量的主從復(fù)制。這時(shí)主機(jī)會(huì)生成內(nèi)存快照,主機(jī)依然可以對(duì)外提供服務(wù),但是作為讀的從機(jī),就無法提供對(duì)外服務(wù)了,如果數(shù)據(jù)量大,恢復(fù)的時(shí)間會(huì)相當(dāng)長。

可以將多種手段配合存儲(chǔ)系統(tǒng)的同步來使用,甚至可以不采用存儲(chǔ)系統(tǒng)的同步方案,改用自己的同步方案。

還是以前面的“用戶子系統(tǒng)”為例,我們可以采用如下幾種方式同步數(shù)據(jù):

(1)消息隊(duì)列方式

對(duì)于賬號(hào)數(shù)據(jù),由于賬號(hào)只會(huì)創(chuàng)建不會(huì)修改和刪除(假設(shè)我們不提供刪除功能),我們可以將賬號(hào)數(shù)據(jù)通過消息隊(duì)列同步到其他業(yè)務(wù)中心。

(2) 二次讀取方式

某些情況下可能出現(xiàn)消息隊(duì)列同步也延遲了,用戶在 A 中心注冊(cè),然后訪問 B 中心的業(yè)務(wù),此時(shí) B 中心本地拿不到用戶的賬號(hào)數(shù)據(jù)。為了解決這個(gè)問題,B 中心在讀取本地?cái)?shù)據(jù)失敗時(shí),可以根據(jù)路由規(guī)則,再去 A 中心訪問一次(這就是所謂的二次讀取,第一次讀取本地,本地失敗后第二次讀取對(duì)端),這樣就能夠解決異常情況下同步延遲的問題。

(3) 存儲(chǔ)系統(tǒng)同步方式

對(duì)于密碼數(shù)據(jù),由于用戶改密碼頻率較低,而且用戶不可能在 1 秒內(nèi)連續(xù)改多次密碼,所以通過數(shù)據(jù)庫的同步機(jī)制將數(shù)據(jù)復(fù)制到其他業(yè)務(wù)中心即可,用戶信息數(shù)據(jù)和密碼類似。

(4) 回源讀取方式

對(duì)于登錄的 session 數(shù)據(jù),由于數(shù)據(jù)量很大,我們可以不同步數(shù)據(jù);但當(dāng)用戶在 A 中心登錄后,然后又在 B 中心登錄,B 中心拿到用戶上傳的 session id 后,根據(jù)路由判斷 session 屬于 A 中心直接去 A 中心請(qǐng)求 session 數(shù)據(jù)即可;反之亦然,A 中心也可以到 B 中心去獲取 session 數(shù)據(jù)。

(5) 重新生成數(shù)據(jù)方式

對(duì)于“回源讀取”場景,如果異常情況下,A 中心宕機(jī)了,B 中心請(qǐng)求 session 數(shù)據(jù)失敗,此時(shí)就只能登錄失敗,讓用戶重新在 B 中心登錄,生成新的 session 數(shù)據(jù)。

注意:以上方案僅僅是示意,實(shí)際的設(shè)計(jì)方案要比這個(gè)復(fù)雜一些,還有很多細(xì)節(jié)要考慮。

綜合上述的各種措施,最后“用戶子系統(tǒng)”同步方式整體如下:

技巧 4:只保證絕大部分用戶的異地多活

某些場景下我們無法保證 100% 的業(yè)務(wù)可用性,總是會(huì)有一定的損失。例如,密碼不同步導(dǎo)致無法登錄、用戶信息不同步導(dǎo)致用戶看到舊的信息等,這個(gè)問題怎么解決呢?

異地多活也無法保證 100% 的業(yè)務(wù)可用,這是由物理規(guī)律決定的,光速和網(wǎng)絡(luò)的傳播速度、硬盤的讀寫速度、極端異常情況的不可控等,都是無法 100% 解決的。所以針對(duì)這個(gè)思維誤區(qū),我的答案是“忍”!否則本來想為了保證最后的 0.01% 的用戶的可用性,做一個(gè)完美方案,結(jié)果卻發(fā)現(xiàn) 99.99% 的用戶都保證不了了。

對(duì)于某些實(shí)時(shí)強(qiáng)一致性的業(yè)務(wù),實(shí)際上受影響的用戶會(huì)更多,甚至可能達(dá)到 1/3 的用戶。以銀行轉(zhuǎn)賬這個(gè)業(yè)務(wù)為例,假設(shè)小明在北京 XX 銀行開了賬號(hào),如果小明要轉(zhuǎn)賬,一定要北京的銀行業(yè)務(wù)中心才可用,否則就不允許小明自己轉(zhuǎn)賬。如果不這樣的話,假設(shè)在北京和上海兩個(gè)業(yè)務(wù)中心實(shí)現(xiàn)了實(shí)時(shí)轉(zhuǎn)賬的異地多活,某些異常情況下就可能出現(xiàn)小明只有 1 萬元存款,他在北京轉(zhuǎn)給了張三 1 萬元,然后又到上海轉(zhuǎn)給了李四 1 萬元,兩次轉(zhuǎn)賬都成功了。這種漏洞如果被人利用,后果不堪設(shè)想。

當(dāng)然,針對(duì)銀行轉(zhuǎn)賬這個(gè)業(yè)務(wù),雖然無法做到“實(shí)時(shí)轉(zhuǎn)賬”的異地多活,但可以通過特殊的業(yè)務(wù)手段讓轉(zhuǎn)賬業(yè)務(wù)也能實(shí)現(xiàn)異地多活。例如,轉(zhuǎn)賬業(yè)務(wù)除了“實(shí)時(shí)轉(zhuǎn)賬”外,還提供“轉(zhuǎn)賬申請(qǐng)”業(yè)務(wù),即小明在上海業(yè)務(wù)中心提交轉(zhuǎn)賬請(qǐng)求,但上海的業(yè)務(wù)中心并不立即轉(zhuǎn)賬,而是記錄這個(gè)轉(zhuǎn)賬請(qǐng)求,然后后臺(tái)異步發(fā)起真正的轉(zhuǎn)賬操作,如果此時(shí)北京業(yè)務(wù)中心不可用,轉(zhuǎn)賬請(qǐng)求就可以繼續(xù)等待重試;假設(shè)等待 2 個(gè)小時(shí)后北京業(yè)務(wù)中心恢復(fù)了,此時(shí)上海業(yè)務(wù)中心去請(qǐng)求轉(zhuǎn)賬,發(fā)現(xiàn)余額不夠,這個(gè)轉(zhuǎn)賬請(qǐng)求就失敗了。小明再登錄上來就會(huì)看到轉(zhuǎn)賬申請(qǐng)失敗,原因是“余額不足”。

不過需要注意的是“轉(zhuǎn)賬申請(qǐng)”的這種方式雖然有助于實(shí)現(xiàn)異地多活,但其實(shí)還是犧牲了用戶體驗(yàn)的,對(duì)于小明來說,本來一次操作的事情,需要分為兩次:一次提交轉(zhuǎn)賬申請(qǐng),另外一次是要確認(rèn)是否轉(zhuǎn)賬成功。

雖然我們無法做到 100% 可用性,但并不意味著我們什么都不能做,為了讓用戶心里更好受一些,我們可以采取一些措施進(jìn)行安撫或者補(bǔ)償,例如:

掛公告

說明現(xiàn)在有問題和基本的問題原因,如果不明確原因或者不方便說出原因,可以發(fā)布“技術(shù)哥哥正在緊急處理”這類比較輕松和有趣的公告。

事后對(duì)用戶進(jìn)行補(bǔ)償

例如,送一些業(yè)務(wù)上可用的代金券、小禮包等,減少用戶的抱怨。

補(bǔ)充體驗(yàn)

對(duì)于為了做異地多活而帶來的體驗(yàn)損失,可以想一些方法減少或者規(guī)避。以“轉(zhuǎn)賬申請(qǐng)”為例,為了讓用戶不用確認(rèn)轉(zhuǎn)賬申請(qǐng)是否成功,我們可以在轉(zhuǎn)賬成功或者失敗后直接給用戶發(fā)個(gè)短信,告訴他轉(zhuǎn)賬結(jié)果,這樣用戶就不用時(shí)不時(shí)地登錄系統(tǒng)來確認(rèn)轉(zhuǎn)賬是否成功了。

核心思想

異地多活設(shè)計(jì)的理念可以總結(jié)為一句話:采用多種手段,保證絕大部分用戶的核心業(yè)務(wù)異地多活!

小結(jié)

異地多活的 4 大技巧需要結(jié)合業(yè)務(wù)進(jìn)行分析取舍,這樣沒法通用,如果底層存儲(chǔ)采用 OceanBase 這種分布式強(qiáng)一致性的數(shù)據(jù)存儲(chǔ)系統(tǒng),是否就可以做到和業(yè)務(wù)無關(guān)的異地多活?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容