跨城異地多活是架構(gòu)設(shè)計復(fù)雜度最高的一種春塌,接下來我將介紹跨城異地多活架構(gòu)
技巧 1:保證核心業(yè)務(wù)的異地多活
“異地多活”是為了保證業(yè)務(wù)的高可用,但很多架構(gòu)師在考慮這個“業(yè)務(wù)”時,會不自覺地陷入一個思維誤區(qū):我要保證所有業(yè)務(wù)都能“異地多活”碘菜!
假設(shè)我們需要做一個“用戶子系統(tǒng)”律姨,這個子系統(tǒng)負(fù)責(zé)“注冊”“登錄”“用戶信息”三個業(yè)務(wù)。為了支持海量用戶公般,我們設(shè)計了一個“用戶分區(qū)”的架構(gòu)万搔,即正常情況下用戶屬于某個主分區(qū),每個分區(qū)都有其他數(shù)據(jù)的備份官帘,用戶用郵箱或者手機號注冊瞬雹,路由層拿到郵箱或者手機號后,通過 Hash 計算屬于哪個中心刽虹,然后請求對應(yīng)的業(yè)務(wù)中心酗捌。基本的架構(gòu)如下:
這樣一個系統(tǒng)涌哲,如果 3 個業(yè)務(wù)要同時實現(xiàn)異地多活胖缤,會發(fā)現(xiàn)這些難以解決的問題:
注冊問題
A 中心注冊了用戶,數(shù)據(jù)還未同步到 B 中心阀圾,此時 A 中心宕機哪廓,為了支持注冊業(yè)務(wù)多活,可以挑選 B 中心讓用戶去重新注冊稍刀×枚溃看起來很容易就支持多活了敞曹,但仔細(xì)思考一下會發(fā)現(xiàn)這樣做會有問題:一個手機號只能注冊一個賬號,A 中心的數(shù)據(jù)沒有同步過來综膀,B 中心無法判斷這個手機號是否重復(fù)澳迫,如果 B 中心讓用戶注冊,后來 A 中心恢復(fù)了剧劝,發(fā)現(xiàn)數(shù)據(jù)有沖突橄登,怎么解決?實際上是無法解決的讥此,因為同一個手機號注冊的賬號不能以后一次注冊為準(zhǔn)拢锹;而如果 B 中心不支持本來屬于 A 中心的業(yè)務(wù)進(jìn)行注冊,注冊業(yè)務(wù)的多活又成了空談萄喳。
如果我們修改業(yè)務(wù)規(guī)則卒稳,允許一個手機號注冊多個賬號不就可以了嗎?
這樣做是不可行的他巨,類似一個手機號只能注冊一個賬號這種規(guī)則充坑,是核心業(yè)務(wù)規(guī)則,修改核心業(yè)務(wù)規(guī)則的代價非常大染突,幾乎所有的業(yè)務(wù)都要重新設(shè)計捻爷,為了架構(gòu)設(shè)計去改變業(yè)務(wù)規(guī)則(而且是這么核心的業(yè)務(wù)規(guī)則)是得不償失的。
用戶信息問題
用戶信息的修改和注冊有類似的問題份企,即 A也榄、B 兩個中心在異常的情況下都修改了用戶信息,如何處理沖突司志?
由于用戶信息并沒有賬號那么關(guān)鍵甜紫,一種簡單的處理方式是按照時間合并,即最后修改的生效俐芯。業(yè)務(wù)邏輯上沒問題棵介,但實際操作也有一個很關(guān)鍵的“坑”:怎么保證多個中心所有機器時間絕對一致?在異地多中心的網(wǎng)絡(luò)下吧史,這個是無法保證的邮辽,即使有時間同步也無法完全保證,只要兩個中心的時間誤差超過 1 秒贸营,數(shù)據(jù)就可能出現(xiàn)混亂吨述,即先修改的反而生效。
還有一種方式是生成全局唯一遞增 ID钞脂,這個方案的成本很高揣云,因為這個全局唯一遞增 ID 的系統(tǒng)本身又要考慮異地多活,同樣涉及數(shù)據(jù)一致性和沖突的問題冰啃。
優(yōu)先實現(xiàn)核心業(yè)務(wù)的異地多活架構(gòu)邓夕!
對于這個模擬案例來說刘莹,“登錄”才是最核心的業(yè)務(wù),“注冊”和“用戶信息”雖然也是主要業(yè)務(wù)焚刚,但并不一定要實現(xiàn)異地多活点弯,主要原因在于業(yè)務(wù)影響不同。對于一個日活 1000 萬的業(yè)務(wù)來說矿咕,每天注冊用戶可能是幾萬抢肛,修改用戶信息的可能還不到 1 萬,但登錄用戶是 1000 萬碳柱,很明顯我們應(yīng)該保證登錄的異地多活捡絮。
而登錄實現(xiàn)“異地多活”恰恰是最簡單的,因為每個中心都有所有用戶的賬號和密碼信息莲镣,用戶在哪個中心都可以登錄福稳。用戶在 A 中心登錄,A 中心宕機后剥悟,用戶到 B 中心重新登錄即可灵寺。
如果某個用戶在 A 中心修改了密碼,此時數(shù)據(jù)還沒有同步到 B 中心区岗,用戶到 B 中心登錄是無法登錄的,這個怎么處理毁枯?
技巧 2:保證核心數(shù)據(jù)最終一致性
異地多活本質(zhì)上是通過異地的數(shù)據(jù)冗余慈缔,來保證在極端異常的情況下業(yè)務(wù)也能夠正常提供給用戶。
數(shù)據(jù)冗余是要將數(shù)據(jù)從 A 地同步到 B 地种玛,從業(yè)務(wù)的角度來看是越快越好藐鹤,但不可能很快,因為這是物理定律決定的赂韵,幾種方法可以參考:
盡量減少異地多活機房的距離娱节,搭建高速網(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ù)量很大毡代,但其實并不需要同步到其他業(yè)務(wù)中心阅羹,因為這些數(shù)據(jù)丟失后重新登錄就可以再次獲取了勺疼。
保證最終一致性,不保證實時一致性
例如捏鱼,A 機房注冊了一個用戶执庐,業(yè)務(wù)上不要求能夠在 50 毫秒內(nèi)就同步到所有機房,正常情況下要求 5 分鐘同步到所有機房即可穷躁,異常情況下甚至可以允許 1 小時或者 1 天后能夠一致耕肩。
最終一致性在具體實現(xiàn)時,還需要根據(jù)不同的數(shù)據(jù)特征问潭,進(jìn)行差異化的處理猿诸,以滿足業(yè)務(wù)需要。例如狡忙,對“賬號”信息來說梳虽,如果在 A 機房新注冊的用戶 5 分鐘內(nèi)正好跑到 B 機房了,此時 B 機房還沒有這個用戶的信息灾茁,為了保證業(yè)務(wù)的正確窜觉,B 機房就需要根據(jù)路由規(guī)則到 A 機房請求數(shù)據(jù)。
而對“用戶信息”來說北专,5 分鐘后同步也沒有問題禀挫,也不需要采取其他措施來彌補,但還是會影響用戶體驗拓颓,即用戶看到了舊的用戶信息语婴,這個問題怎么解決呢?
技巧 3:采用多種手段同步數(shù)據(jù)
數(shù)據(jù)同步是異地多活架構(gòu)設(shè)計的核心驶睦,幸運的是基本上存儲系統(tǒng)本身都會有同步的功能砰左。例如,MySQL 的主備復(fù)制场航、Redis 的 Cluster 功能缠导、Elasticsearch 的集群功能。這些系統(tǒng)本身的同步功能已經(jīng)比較強大溉痢,能夠直接拿來就用僻造,但這也無形中將我們引入了一個思維誤區(qū):只使用存儲系統(tǒng)的同步功能!
既然說存儲系統(tǒng)本身就有同步功能适室,而且同步功能還很強大嫡意,為何說只使用存儲系統(tǒng)是一個思維誤區(qū)呢?因為雖然絕大部分場景下捣辆,存儲系統(tǒng)本身的同步功能基本上也夠用了蔬螟,但在某些比較極端的情況下,存儲系統(tǒng)本身的同步功能可能難以滿足業(yè)務(wù)需求汽畴。
以 MySQL 為例旧巾,MySQL 5.1 版本的復(fù)制是單線程的復(fù)制耸序,在網(wǎng)絡(luò)抖動或者大量數(shù)據(jù)同步時,經(jīng)常發(fā)生延遲較長的問題鲁猩,短則延遲十幾秒坎怪,長則可能達(dá)到十幾分鐘。而且即使我們通過監(jiān)控的手段知道了 MySQL 同步時延較長廓握,也難以采取什么措施搅窿,只能干等。
Redis 又是另外一個問題隙券,Redis 3.0 之前沒有 Cluster 功能男应,只有主從復(fù)制功能,而為了設(shè)計上的簡單娱仔,Redis 2.8 之前的版本沐飘,主從復(fù)制有一個比較大的隱患:從機宕機或者和主機斷開連接都需要重新連接主機,重新連接主機都會觸發(fā)全量的主從復(fù)制牲迫。這時主機會生成內(nèi)存快照耐朴,主機依然可以對外提供服務(wù),但是作為讀的從機盹憎,就無法提供對外服務(wù)了筛峭,如果數(shù)據(jù)量大,恢復(fù)的時間會相當(dāng)長陪每。
可以將多種手段配合存儲系統(tǒng)的同步來使用蜒滩,甚至可以不采用存儲系統(tǒng)的同步方案,改用自己的同步方案奶稠。
還是以前面的“用戶子系統(tǒng)”為例,我們可以采用如下幾種方式同步數(shù)據(jù):
(1)消息隊列方式
對于賬號數(shù)據(jù)捡遍,由于賬號只會創(chuàng)建锌订,不會修改和刪除(假設(shè)我們不提供刪除功能),我們可以將賬號數(shù)據(jù)通過消息隊列同步到其他業(yè)務(wù)中心画株。
(2) 二次讀取方式
某些情況下可能出現(xiàn)消息隊列同步也延遲了辆飘,用戶在 A 中心注冊,然后訪問 B 中心的業(yè)務(wù)谓传,此時 B 中心本地拿不到用戶的賬號數(shù)據(jù)蜈项。為了解決這個問題,B 中心在讀取本地數(shù)據(jù)失敗時续挟,可以根據(jù)路由規(guī)則炬称,再去 A 中心訪問一次(這就是所謂的二次讀取肺素,第一次讀取本地,本地失敗后第二次讀取對端)戒劫,這樣就能夠解決異常情況下同步延遲的問題。
(3) 存儲系統(tǒng)同步方式
對于密碼數(shù)據(jù)禁悠,由于用戶改密碼頻率較低,而且用戶不可能在 1 秒內(nèi)連續(xù)改多次密碼,所以通過數(shù)據(jù)庫的同步機制將數(shù)據(jù)復(fù)制到其他業(yè)務(wù)中心即可怀樟,用戶信息數(shù)據(jù)和密碼類似。
(4) 回源讀取方式
對于登錄的 session 數(shù)據(jù)盆佣,由于數(shù)據(jù)量很大往堡,我們可以不同步數(shù)據(jù);但當(dāng)用戶在 A 中心登錄后共耍,然后又在 B 中心登錄虑灰,B 中心拿到用戶上傳的 session id 后,根據(jù)路由判斷 session 屬于 A 中心征堪,直接去 A 中心請求 session 數(shù)據(jù)即可瘩缆;反之亦然,A 中心也可以到 B 中心去獲取 session 數(shù)據(jù)佃蚜。
(5) 重新生成數(shù)據(jù)方式
對于“回源讀取”場景庸娱,如果異常情況下,A 中心宕機了谐算,B 中心請求 session 數(shù)據(jù)失敗熟尉,此時就只能登錄失敗,讓用戶重新在 B 中心登錄洲脂,生成新的 session 數(shù)據(jù)斤儿。
注意:以上方案僅僅是示意,實際的設(shè)計方案要比這個復(fù)雜一些恐锦,還有很多細(xì)節(jié)要考慮往果。
綜合上述的各種措施,最后“用戶子系統(tǒng)”同步方式整體如下:
技巧 4:只保證絕大部分用戶的異地多活
某些場景下我們無法保證 100% 的業(yè)務(wù)可用性一铅,總是會有一定的損失陕贮。例如,密碼不同步導(dǎo)致無法登錄潘飘、用戶信息不同步導(dǎo)致用戶看到舊的信息等肮之,這個問題怎么解決呢?
異地多活也無法保證 100% 的業(yè)務(wù)可用卜录,這是由物理規(guī)律決定的戈擒,光速和網(wǎng)絡(luò)的傳播速度、硬盤的讀寫速度艰毒、極端異常情況的不可控等筐高,都是無法 100% 解決的。所以針對這個思維誤區(qū),我的答案是“忍”凯傲!否則本來想為了保證最后的 0.01% 的用戶的可用性犬辰,做一個完美方案,結(jié)果卻發(fā)現(xiàn) 99.99% 的用戶都保證不了了冰单。
對于某些實時強一致性的業(yè)務(wù)幌缝,實際上受影響的用戶會更多,甚至可能達(dá)到 1/3 的用戶诫欠。以銀行轉(zhuǎn)賬這個業(yè)務(wù)為例涵卵,假設(shè)小明在北京 XX 銀行開了賬號,如果小明要轉(zhuǎn)賬荒叼,一定要北京的銀行業(yè)務(wù)中心才可用轿偎,否則就不允許小明自己轉(zhuǎn)賬。如果不這樣的話被廓,假設(shè)在北京和上海兩個業(yè)務(wù)中心實現(xiàn)了實時轉(zhuǎn)賬的異地多活坏晦,某些異常情況下就可能出現(xiàn)小明只有 1 萬元存款,他在北京轉(zhuǎn)給了張三 1 萬元嫁乘,然后又到上海轉(zhuǎn)給了李四 1 萬元昆婿,兩次轉(zhuǎn)賬都成功了。這種漏洞如果被人利用蜓斧,后果不堪設(shè)想仓蛆。
當(dāng)然,針對銀行轉(zhuǎn)賬這個業(yè)務(wù)挎春,雖然無法做到“實時轉(zhuǎn)賬”的異地多活看疙,但可以通過特殊的業(yè)務(wù)手段讓轉(zhuǎn)賬業(yè)務(wù)也能實現(xiàn)異地多活。例如直奋,轉(zhuǎn)賬業(yè)務(wù)除了“實時轉(zhuǎn)賬”外能庆,還提供“轉(zhuǎn)賬申請”業(yè)務(wù),即小明在上海業(yè)務(wù)中心提交轉(zhuǎn)賬請求脚线,但上海的業(yè)務(wù)中心并不立即轉(zhuǎn)賬相味,而是記錄這個轉(zhuǎn)賬請求,然后后臺異步發(fā)起真正的轉(zhuǎn)賬操作殉挽,如果此時北京業(yè)務(wù)中心不可用,轉(zhuǎn)賬請求就可以繼續(xù)等待重試拓巧;假設(shè)等待 2 個小時后北京業(yè)務(wù)中心恢復(fù)了斯碌,此時上海業(yè)務(wù)中心去請求轉(zhuǎn)賬,發(fā)現(xiàn)余額不夠肛度,這個轉(zhuǎn)賬請求就失敗了傻唾。小明再登錄上來就會看到轉(zhuǎn)賬申請失敗,原因是“余額不足”。
不過需要注意的是“轉(zhuǎn)賬申請”的這種方式雖然有助于實現(xiàn)異地多活冠骄,但其實還是犧牲了用戶體驗的伪煤,對于小明來說,本來一次操作的事情凛辣,需要分為兩次:一次提交轉(zhuǎn)賬申請抱既,另外一次是要確認(rèn)是否轉(zhuǎn)賬成功。
雖然我們無法做到 100% 可用性扁誓,但并不意味著我們什么都不能做防泵,為了讓用戶心里更好受一些,我們可以采取一些措施進(jìn)行安撫或者補償蝗敢,例如:
掛公告
說明現(xiàn)在有問題和基本的問題原因捷泞,如果不明確原因或者不方便說出原因,可以發(fā)布“技術(shù)哥哥正在緊急處理”這類比較輕松和有趣的公告寿谴。
事后對用戶進(jìn)行補償
例如锁右,送一些業(yè)務(wù)上可用的代金券、小禮包等讶泰,減少用戶的抱怨咏瑟。
補充體驗
對于為了做異地多活而帶來的體驗損失,可以想一些方法減少或者規(guī)避峻厚。以“轉(zhuǎn)賬申請”為例响蕴,為了讓用戶不用確認(rèn)轉(zhuǎn)賬申請是否成功,我們可以在轉(zhuǎn)賬成功或者失敗后直接給用戶發(fā)個短信惠桃,告訴他轉(zhuǎn)賬結(jié)果浦夷,這樣用戶就不用時不時地登錄系統(tǒng)來確認(rèn)轉(zhuǎn)賬是否成功了。
核心思想
異地多活設(shè)計的理念可以總結(jié)為一句話:采用多種手段辜王,保證絕大部分用戶的核心業(yè)務(wù)異地多活劈狐!
小結(jié)
異地多活的 4 大技巧需要結(jié)合業(yè)務(wù)進(jìn)行分析取舍,這樣沒法通用呐馆,如果底層存儲采用 OceanBase 這種分布式強一致性的數(shù)據(jù)存儲系統(tǒng)肥缔,是否就可以做到和業(yè)務(wù)無關(guān)的異地多活?