高可用架構之異地多活

當談到架構的高可用時,無論是高可用計算架構,還是高可用存儲架構,其本質的設計目的都是爲了解決部分服務器故障的場景下,如何保證系統能夠繼續提供服務。但在一些極端場景下,有可能所有服務器都出現故障。例如,典型的有機房斷電、機房火災、地震、水災……這些極端情況會導致某個系統所有服務器都故障,或者業務整體癱瘓,而且即使有其他地區的備份,把備份業務系統全部恢復到能夠正常提供業務,花費的時間也比較長,可能是半小時,也可能是一天。因爲備份系統平時不對外提供服務,可能會存在很多隱藏的問題沒有發現。如果業務期望達到即使在此類災難性故障的情況下,業務也不受影響,或者在幾分鐘內就能夠很快恢復,那麼就需要設計異地多活架構。

今天我來聊聊異地多活架構,以及異地多活架構的設計技巧和流程。

應用場景

顧名思義,異地多活架構的關鍵點就是異地、多活,其中異地就是指地理位置上不同的地方,類似於“不要把雞蛋都放在同一籃子裏”;多活就是指不同地理位置上的系統都能夠提供業務服務,這裏的“活”是活動、活躍的意思。判斷一個系統是否符合異地多活,需要滿足兩個標準:

  • 正常情況下,用戶無論訪問哪一個地點的業務系統,都能夠得到正確的業務服務。

  • 某個地方業務異常的時候,用戶訪問其他地方正常的業務系統,能夠得到正確的業務服務。

與“活”對應的是字是“備”,備是備份,正常情況下對外是不提供服務的,如果需要提供服務,則需要大量的人工干預和操作,花費大量的時間才能讓“備”變成“活”。

單純從異地多活的描述來看,異地多活很強大,能夠保證在災難的情況下業務都不受影響。那是不是意味着不管什麼業務,我們都要去實現異地多活架構呢?其實不然,因爲實現異地多活架構不是沒有代價的,相反其 代價很高,具體表現爲:

  • 系統複雜度會發生質的變化,需要設計複雜的異地多活架構。

  • 成本會上升,畢竟要多在一個或者多個機房搭建獨立的一套業務系統。

因此,異地多活雖然功能很強大,但也不是每個業務不管三七二十一都要上異地多活。例如,常見的新聞網站、企業內部的IT系統、遊戲、博客站點等,如果無法承受異地多活帶來的複雜度和成本,是可以不做異地多活的,只需要做異地備份即可。因爲這類業務系統即使中斷,對用戶的影響並不會很大,例如,A新聞網站看不了,用戶換個新聞網站即可。而共享單車、滴滴出行、支付寶、微信這類業務,就需要做異地多活了,這類業務系統中斷後,對用戶的影響很大。例如,支付寶用不了,就沒法買東西了;滴滴用不了,用戶就打不到車了。

當然,如果業務規模很大,能夠做異地多活的情況下還是儘量。首先,這樣能夠在異常的場景下給用戶提供更好的體驗;其次,業務規模很大肯定會伴隨衍生的收入,例如廣告收入,異地多活能夠減少異常場景帶來的收入損失。同樣以新聞網站爲例,雖然從業務的角度來看,新聞類網站對用戶影響不大,反正用戶也可以從其他地方看到基本相同的新聞,甚至用戶幾個小時不看新聞也沒什麼問題。但是從網站本身來看,幾個小時不可訪問肯定會影響用戶對網站的口碑;其次幾個小時不可訪問,網站上的廣告收入損失也會很大。

架構模式

根據地理位置上的距離來劃分,異地多活架構可以分爲同城異區、跨城異地、跨國異地。接下來我詳細解釋一下每一種架構的細節與優缺點。

1.同城異區

同城異區指的是將業務部署在同一個城市不同區的多個機房。例如,在北京部署兩個機房,一個機房在海淀區,一個在通州區,然後將兩個機房用專用的高速網絡連接在一起。

如果我們考慮一些極端場景(例如,美加大停電、新奧爾良水災),同城異區似乎沒什麼作用,那爲何我們還要設計同城異區這種架構呢?答案就在於“同城”。

同城的兩個機房,距離上一般大約就是幾十千米,通過搭建高速的網絡,同城異區的兩個機房能夠實現和同一個機房內幾乎一樣的網絡傳輸速度。這就意味着雖然是兩個不同地理位置上的機房,但邏輯上我們可以將它們看作同一個機房,這樣的設計大大降低了複雜度,減少了異地多活的設計和實現複雜度及成本。

那如果採用了同城異區架構,一旦發生新奧爾良水災這種災難怎麼辦呢?很遺憾,答案是無能爲力。但我們需要考慮的是,這種極端災難發生概率是比較低的,可能幾年或者十幾年才發生一次。其次,除了這類災難,機房火災、機房停電、機房空調故障這類問題發生的概率更高,而且破壞力一樣很大。而這些故障場景,同城異區架構都可以很好地解決。因此,結合複雜度、成本、故障發生概率來綜合考慮,同城異區是應對機房級別故障的最優架構。

2.跨城異地

跨城異地指的是業務部署在不同城市的多個機房,而且距離最好要遠一些。例如,將業務部署在北京和廣州兩個機房,而不是將業務部署在廣州和深圳的兩個機房。

爲何跨城異地要強調距離要遠呢?前面我在介紹同城異區的架構時提到同城異區不能解決新奧爾良水災這種問題,而兩個城市離得太近又無法應對如美加大停電這種問題,跨城異地其實就是爲了解決這兩類問題的,因此需要在距離上比較遠,纔能有效應對這類極端災難事件。

跨城異地雖然能夠有效應對極端災難事件,但“距離較遠”這點並不只是一個距離數字上的變化,而是量變引起了質變,導致了跨城異地的架構複雜度大大上升。距離增加帶來的最主要問題是兩個機房的網絡傳輸速度會降低,這不是以人的意志爲轉移的,而是物理定律決定的,即光速真空傳播大約是每秒30萬千米,在光纖中傳輸的速度大約是每秒20萬千米,再加上傳輸中的各種網絡設備的處理,實際還遠遠達不到理論上的速度。

除了距離上的限制,中間傳輸各種不可控的因素也非常多。例如,挖掘機把光纖挖斷、中美海底電纜被拖船扯斷、骨幹網故障等,這些線路很多是第三方維護,針對故障我們根本無能爲力也無法預知。例如,廣州機房到北京機房,正常情況下RTT大約是50毫秒左右,遇到網絡波動之類的情況,RTT可能飆升到500毫秒甚至1秒,更不用說經常發生的線路丟包問題,那延遲可能就是幾秒幾十秒了。

以上描述的問題,雖然同城異區理論上也會遇到,但由於同城異區距離較短,中間經過的線路和設備較少,問題發生的概率會低很多。而且同城異區距離短,即使是搭建多條互聯通道,成本也不會太高,而跨城異區距離太遠,搭建或者使用多通道的成本會高不少。

跨城異地距離較遠帶來的網絡傳輸延遲問題,給異地多活架構設計帶來了複雜性,如果要做到真正意義上的多活,業務系統需要考慮部署在不同地點的兩個機房,在數據短時間不一致的情況下,還能夠正常提供業務。這就引入了一個看似矛盾的地方:數據不一致業務肯定不會正常,但跨城異地肯定會導致數據不一致。

如何解決這個問題呢?重點還是在“數據”上,即根據數據的特性來做不同的架構。如果是強一致性要求的數據,例如銀行存款餘額、支付寶餘額等,這類數據實際上是無法做到跨城異地多活的。我們來看一個假設的例子,假如我們做一個互聯網金融的業務,用戶餘額支持跨城異地多活,我們的系統分別部署在廣州和北京,那麼如果挖掘機挖斷光纜後,會出現如下場景:

  • 用戶A餘額有10000元錢,北京和廣州機房都是這個數據。

  • 用戶A向用戶B轉了5000元錢,這個操作是在廣州機房完成的,完成後用戶A在廣州機房的餘額是5000元。

  • 由於廣州和北京機房網絡被挖掘機挖斷,廣州機房無法將餘額變動通知北京機房,此時北京機房用戶A的餘額還是10000元。

  • 用戶A到北京機房又發起轉賬,此時他看到自己的餘額還有10000元,於是向用戶C轉賬10000元,轉賬完成後用戶A的餘額變爲0。

  • 用戶A到廣州機房一看,餘額怎麼還有5000元?於是趕緊又發起轉賬,轉賬5000元給用戶D;此時廣州機房用戶A的餘額也變爲0了。

最終,本來餘額10000元的用戶A,卻轉了20000元出去給其他用戶。

對於以上這種假設場景,雖然普通用戶很難這樣自如地操作,但如果真的這麼做,被黑客發現後,後果不堪設想。正因爲如此,支付寶等金融相關的系統,對餘額這類數據,一般不會做跨城異地的多活架構,而只能採用同城異區這種架構。

而對數據一致性要求不那麼高,或者數據不怎麼改變,或者即使數據丟失影響也不大的業務,跨城異地多活就能夠派上用場了。例如,用戶登錄(數據不一致時用戶重新登錄即可)、新聞類網站(一天內的新聞數據變化較少)、微博類網站(丟失用戶發佈的微博或者評論影響不大),這些業務採用跨城異地多活,能夠很好地應對極端災難的場景。

3.跨國異地

跨國異地指的是業務部署在不同國家的多個機房。相比跨城異地,跨國異地的距離就更遠了,因此數據同步的延時會更長,正常情況下可能就有幾秒鐘了。這種程度的延遲已經無法滿足異地多活標準的第一條:“正常情況下,用戶無論訪問哪一個地點的業務系統,都能夠得到正確的業務服務”。例如,假設有一個微博類網站,分別在中國的上海和美國的紐約都建了機房,用戶A在上海機房發表了一篇微博,此時如果他的一個關注者B用戶訪問到美國的機房,很可能無法看到用戶A剛剛發表的微博。雖然跨城異地也會有此類同步延時問題,但正常情況下幾十毫秒的延時對用戶來說基本無感知的;而延時達到幾秒鐘就感覺比較明顯了。

因此,跨國異地的“多活”,和跨城異地的“多活”,實際的含義並不完全一致。跨國異地多活的主要應用場景一般有這幾種情況:

  • 爲不同地區用戶提供服務

例如,亞馬遜中國是爲中國用戶服務的,而亞馬遜美國是爲美國用戶服務的,亞馬遜中國的用戶如果訪問美國亞馬遜,是無法用亞馬遜中國的賬號登錄美國亞馬遜的。

  • 只讀類業務做多活

例如,谷歌的搜索業務,由於用戶搜索資料時,這些資料都已經存在於谷歌的搜索引擎上面,無論是訪問英國谷歌,還是訪問美國谷歌,搜索結果基本相同,並且對用戶來說,也不需要搜索到最新的實時資料,跨國異地的幾秒鐘網絡延遲,對搜索結果是沒有什麼影響的。、

接下來我將介紹跨城異地多活架構設計的一些技巧和步驟

技巧1:保證核心業務的異地多活

“異地多活”是爲了保證業務的高可用,但很多架構師在考慮這個“業務”時,會不自覺地陷入一個思維誤區:我要保證所有業務都能“異地多活”!

假設我們需要做一個“用戶子系統”,這個子系統負責“註冊”“登錄”“用戶信息”三個業務。爲了支持海量用戶,我們設計了一個“用戶分區”的架構,即正常情況下用戶屬於某個主分區,每個分區都有其他數據的備份,用戶用郵箱或者手機號註冊,路由層拿到郵箱或者手機號後,通過Hash計算屬於哪個中心,然後請求對應的業務中心。基本的架構如下:

這樣一個系統,如果3個業務要同時實現異地多活,會發現這些難以解決的問題:

  • 註冊問題

A中心註冊了用戶,數據還未同步到B中心,此時A中心宕機,爲了支持註冊業務多活,可以挑選B中心讓用戶去重新註冊。看起來很容易就支持多活了,但仔細思考一下會發現這樣做會有問題:一個手機號只能註冊一個賬號,A中心的數據沒有同步過來,B中心無法判斷這個手機號是否重複,如果B中心讓用戶註冊,後來A中心恢復了,發現數據有衝突,怎麼解決?實際上是無法解決的,因爲同一個手機號註冊的賬號不能以後一次註冊爲準;而如果B中心不支持本來屬於A中心的業務進行註冊,註冊業務的多活又成了空談。

如果我們修改業務規則,允許一個手機號註冊多個賬號不就可以了嗎?

這樣做是不可行的,類似一個手機號只能註冊一個賬號這種規則,是核心業務規則,修改核心業務規則的代價非常大,幾乎所有的業務都要重新設計,爲了架構設計去改變業務規則(而且是這麼核心的業務規則)是得不償失的。

  • 用戶信息問題

用戶信息的修改和註冊有類似的問題,即A、B兩個中心在異常的情況下都修改了用戶信息,如何處理衝突?

由於用戶信息並沒有賬號那麼關鍵,一種簡單的處理方式是按照時間合併,即最後修改的生效。業務邏輯上沒問題,但實際操作也有一個很關鍵的“坑”:怎麼保證多箇中心所有機器時間絕對一致?在異地多中心的網絡下,這個是無法保證的,即使有時間同步也無法完全保證,只要兩個中心的時間誤差超過1秒,數據就可能出現混亂,即先修改的反而生效。

還有一種方式是生成全局唯一遞增ID,這個方案的成本很高,因爲這個全局唯一遞增ID的系統本身又要考慮異地多活,同樣涉及數據一致性和衝突的問題。

綜合上面的簡單分析可以發現,如果“註冊”“登錄”“用戶信息”全部都要支持異地多活,實際上是挺難的,有的問題甚至是無解的。那這種情況下我們應該如何考慮“異地多活”的架構設計呢?答案其實很簡單: 優先實現核心業務的異地多活架構!

對於這個模擬案例來說,“登錄”纔是最核心的業務,“註冊”和“用戶信息”雖然也是主要業務,但並不一定要實現異地多活,主要原因在於業務影響不同。對於一個日活1000萬的業務來說,每天註冊用戶可能是幾萬,修改用戶信息的可能還不到1萬,但登錄用戶是1000萬,很明顯我們應該保證登錄的異地多活。

對於新用戶來說,註冊不了的影響並不明顯,因爲他還沒有真正開始使用業務。用戶信息修改也類似,暫時修改不了用戶信息,對於其業務不會有很大影響。而如果有幾百萬用戶登錄不了,就相當於幾百萬用戶無法使用業務,對業務的影響就非常大了:公司的客服熱線很快就被打爆,微博、微信上到處都在傳業務宕機,論壇裏面到處是抱怨的用戶,那就是互聯網大事件了!

而登錄實現“異地多活”恰恰是最簡單的,因爲每個中心都有所有用戶的賬號和密碼信息,用戶在哪個中心都可以登錄。用戶在A中心登錄,A中心宕機後,用戶到B中心重新登錄即可。

如果某個用戶在A中心修改了密碼,此時數據還沒有同步到B中心,用戶到B中心登錄是無法登錄的,這個怎麼處理?這個問題其實就涉及另外一個設計技巧了,我賣個關子稍後再談。

技巧2:保證核心數據最終一致性

異地多活本質上是通過異地的數據冗餘,來保證在極端異常的情況下業務也能夠正常提供給用戶,因此數據同步是異地多活架構設計的核心。但大部分架構師在考慮數據同步方案時,會不知不覺地陷入完美主義誤區:我要所有數據都實時同步!

數據冗餘是要將數據從A地同步到B地,從業務的角度來看是越快越好,最好和本地機房一樣的速度最好。但讓人頭疼的問題正在這裏:異地多活理論上就不可能很快,因爲這是物理定律決定的。

因此異地多活架構面臨一個無法徹底解決的矛盾:業務上要求數據快速同步,物理上正好做不到數據快速同步,因此所有數據都實時同步,實際上是一個無法達到的目標。

既然是無法徹底解決的矛盾,那就只能想辦法儘量減少影響。有幾種方法可以參考:

  • 儘量減少異地多活機房的距離,搭建高速網絡

搭建跨城異地的高速網絡成本遠遠超過同城異區的高速網絡,成本巨大,一般只有巨頭公司才能承擔。

  • 儘量減少數據同步,只同步核心業務相關的數據

簡單來說就是不重要的數據不同步,同步後沒用的數據不同步,只同步核心業務相關的數據。

以前面的“用戶子系統”爲例,用戶登錄所產生的token或者session信息,數據量很大,但其實並不需要同步到其他業務中心,因爲這些數據丟失後重新登錄就可以再次獲取了。

這時你可能會想到:這些數據丟失後要求用戶重新登錄,影響用戶體驗!

確實如此,畢竟需要用戶重新輸入賬戶和密碼信息,或者至少要彈出登錄界面讓用戶點擊一次,但相比爲了同步所有數據帶來的代價,這個影響完全可以接受。

  • 保證最終一致性,不保證實時一致性

最終一致性就是業務不依賴數據同步的實時性,只要數據最終能一致即可。例如,A機房註冊了一個用戶,業務上不要求能夠在50毫秒內就同步到所有機房,正常情況下要求5分鐘同步到所有機房即可,異常情況下甚至可以允許1小時或者1天后能夠一致。

最終一致性在具體實現時,還需要根據不同的數據特徵,進行差異化的處理,以滿足業務需要。例如,對“賬號”信息來說,如果在A機房新註冊的用戶5分鐘內正好跑到B機房了,此時B機房還沒有這個用戶的信息,爲了保證業務的正確,B機房就需要根據路由規則到A機房請求數據。

而對“用戶信息”來說,5分鐘後同步也沒有問題,也不需要採取其他措施來彌補,但還是會影響用戶體驗,即用戶看到了舊的用戶信息,這個問題怎麼解決呢?好像又是一個解決不了的問題,和前面我留下的兩個問題一起,在最後我來給出答案。

技巧3:採用多種手段同步數據

數據同步是異地多活架構設計的核心,幸運的是基本上存儲系統本身都會有同步的功能。例如,MySQL的主備複製、Redis的Cluster功能、Elasticsearch的集羣功能。這些系統本身的同步功能已經比較強大,能夠直接拿來就用,但這也無形中將我們引入了一個思維誤區:只使用存儲系統的同步功能!

既然說存儲系統本身就有同步功能,而且同步功能還很強大,爲何說只使用存儲系統是一個思維誤區呢?因爲雖然絕大部分場景下,存儲系統本身的同步功能基本上也夠用了,但在某些比較極端的情況下,存儲系統本身的同步功能可能難以滿足業務需求。

以MySQL爲例,MySQL 5.1版本的複製是單線程的複製,在網絡抖動或者大量數據同步時,經常發生延遲較長的問題,短則延遲十幾秒,長則可能達到十幾分鍾。而且即使我們通過監控的手段知道了MySQL同步時延較長,也難以採取什麼措施,只能乾等。

Redis又是另外一個問題,Redis 3.0之前沒有Cluster功能,只有主從複製功能,而爲了設計上的簡單,Redis 2.8之前的版本,主從複製有一個比較大的隱患:從機宕機或者和主機斷開連接都需要重新連接主機,重新連接主機都會觸發全量的主從複製。這時主機會生成內存快照,主機依然可以對外提供服務,但是作爲讀的從機,就無法提供對外服務了,如果數據量大,恢復的時間會相當長。

綜合上面的案例可以看出,存儲系統本身自帶的同步功能,在某些場景下是無法滿足業務需要的。尤其是異地多機房這種部署,各種各樣的異常情況都可能出現,當我們只考慮存儲系統本身的同步功能時,就會發現無法做到真正的異地多活。

解決的方案就是拓開思路,避免只使用存儲系統的同步功能,可以將多種手段配合存儲系統的同步來使用,甚至可以不採用存儲系統的同步方案,改用自己的同步方案。

還是以前面的“用戶子系統”爲例,我們可以採用如下幾種方式同步數據:

  • 消息隊列方式

對於賬號數據,由於賬號只會創建,不會修改和刪除(假設我們不提供刪除功能),我們可以將賬號數據通過消息隊列同步到其他業務中心。

  • 二次讀取方式

某些情況下可能出現消息隊列同步也延遲了,用戶在A中心註冊,然後訪問B中心的業務,此時B中心本地拿不到用戶的賬號數據。爲了解決這個問題,B中心在讀取本地數據失敗時,可以根據路由規則,再去A中心訪問一次(這就是所謂的二次讀取,第一次讀取本地,本地失敗後第二次讀取對端),這樣就能夠解決異常情況下同步延遲的問題。

  • 存儲系統同步方式

對於密碼數據,由於用戶改密碼頻率較低,而且用戶不可能在1秒內連續改多次密碼,所以通過數據庫的同步機制將數據複製到其他業務中心即可,用戶信息數據和密碼類似。

  • 回源讀取方式

對於登錄的session數據,由於數據量很大,我們可以不同步數據;但當用戶在A中心登錄後,然後又在B中心登錄,B中心拿到用戶上傳的session id後,根據路由判斷session屬於A中心,直接去A中心請求session數據即可;反之亦然,A中心也可以到B中心去獲取session數據。

  • 重新生成數據方式

對於“回源讀取”場景,如果異常情況下,A中心宕機了,B中心請求session數據失敗,此時就只能登錄失敗,讓用戶重新在B中心登錄,生成新的session數據。

注意:以上方案僅僅是示意,實際的設計方案要比這個複雜一些,還有很多細節要考慮。

綜合上述的各種措施,最後“用戶子系統”同步方式整體如下:

圖片

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

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

其實這個問題涉及異地多活架構設計中一個典型的思維誤區:我要保證業務100%可用!但極端情況下就是會丟一部分數據,就是會有一部分數據不能同步,有沒有什麼巧妙能做到100%可用呢?

很遺憾,答案是沒有!異地多活也無法保證100%的業務可用,這是由物理規律決定的,光速和網絡的傳播速度、硬盤的讀寫速度、極端異常情況的不可控等,都是無法100%解決的。所以針對這個思維誤區,我的答案是“忍”!也就是說我們要忍受這一小部分用戶或者業務上的損失,否則本來想爲了保證最後的0.01%的用戶的可用性,做一個完美方案,結果卻發現99.99%的用戶都保證不了了。

對於某些實時強一致性的業務,實際上受影響的用戶會更多,甚至可能達到1/3的用戶。以銀行轉賬這個業務爲例,假設小明在北京XX銀行開了賬號,如果小明要轉賬,一定要北京的銀行業務中心纔可用,否則就不允許小明自己轉賬。如果不這樣的話,假設在北京和上海兩個業務中心實現了實時轉賬的異地多活,某些異常情況下就可能出現小明只有1萬元存款,他在北京轉給了張三1萬元,然後又到上海轉給了李四1萬元,兩次轉賬都成功了。這種漏洞如果被人利用,後果不堪設想。

當然,針對銀行轉賬這個業務,雖然無法做到“實時轉賬”的異地多活,但可以通過特殊的業務手段讓轉賬業務也能實現異地多活。例如,轉賬業務除了“實時轉賬”外,還提供“轉賬申請”業務,即小明在上海業務中心提交轉賬請求,但上海的業務中心並不立即轉賬,而是記錄這個轉賬請求,然後後臺異步發起真正的轉賬操作,如果此時北京業務中心不可用,轉賬請求就可以繼續等待重試;假設等待2個小時後北京業務中心恢復了,此時上海業務中心去請求轉賬,發現餘額不夠,這個轉賬請求就失敗了。小明再登錄上來就會看到轉賬申請失敗,原因是“餘額不足”。

不過需要注意的是“轉賬申請”的這種方式雖然有助於實現異地多活,但其實還是犧牲了用戶體驗的,對於小明來說,本來一次操作的事情,需要分爲兩次:一次提交轉賬申請,另外一次是要確認是否轉賬成功。

雖然我們無法做到100%可用性,但並不意味着我們什麼都不能做,爲了讓用戶心裏更好受一些,我們可以採取一些措施進行安撫或者補償,例如:

  • 掛公告

說明現在有問題和基本的問題原因,如果不明確原因或者不方便說出原因,可以發佈“技術哥哥正在緊急處理”這類比較輕鬆和有趣的公告。

  • 事後對用戶進行補償

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

  • 補充體驗

對於爲了做異地多活而帶來的體驗損失,可以想一些方法減少或者規避。以“轉賬申請”爲例,爲了讓用戶不用確認轉賬申請是否成功,我們可以在轉賬成功或者失敗後直接給用戶發個短信,告訴他轉賬結果,這樣用戶就不用時不時地登錄系統來確認轉賬是否成功了。

設計跨城異地多活架構

我們講完了異地多活設計的核心要點,下面我們談一下如何設計跨城異地多活架構。

第1步:業務分級

按照一定的標準將業務進行分級,挑選出核心的業務,只爲核心業務設計異地多活,降低方案整體複雜度和實現成本。

常見的分級標準有下面幾種:

  • 訪問量大的業務

以用戶管理系統爲例,業務包括登錄、註冊、用戶信息管理,其中登錄的訪問量肯定是最大的。

  • 核心業務

以QQ爲例,QQ的主場景是聊天,QQ空間雖然也是重要業務,但和聊天相比,重要性就會低一些,如果要從聊天和QQ空間兩個業務裏面挑選一個做異地多活,那明顯聊天要更重要(當然,此類公司如騰訊,應該是兩個都實現了異地多活的)。

  • 產生大量收入的業務

同樣以QQ爲例,聊天可能很難爲騰訊帶來收益,因爲聊天沒法插入廣告;而QQ空間反而可能帶來更多收益,因爲QQ空間可以插入很多廣告,因此如果從收入的角度來看,QQ空間做異地多活的優先級反而高於QQ聊天了。

以我們一直在舉例的用戶管理系統爲例,“登錄”業務符合“訪問量大的業務”和“核心業務”這兩條標準,因此我們將登錄業務作爲核心業務。

第2步:數據分類

挑選出核心業務後,需要對核心業務相關的數據進一步分析,目的在於識別所有的數據及數據特徵,這些數據特徵會影響後面的方案設計。

常見的數據特徵分析維度有:

  • 數據量

這裏的數據量包括總的數據量和新增、修改、刪除的量。對異地多活架構來說,新增、修改、刪除的數據就是可能要同步的數據,數據量越大,同步延遲的機率越高,同步方案需要考慮相應的解決方案。

  • 唯一性

唯一性指數據是否要求多個異地機房產生的同類數據必須保證唯一。例如用戶ID,如果兩個機房的兩個不同用戶註冊後生成了一樣的用戶ID,這樣業務上就出錯了。

數據的唯一性影響業務的多活設計,如果數據不需要唯一,那就說明兩個地方都產生同類數據是可能的;如果數據要求必須唯一,要麼只能一箇中心點產生數據,要麼需要設計一個數據唯一生成的算法。

  • 實時性

實時性指如果在A機房修改了數據,要求多長時間必須同步到B機房,實時性要求越高,對同步的要求越高,方案越複雜。

  • 可丟失性

可丟失性指數據是否可以丟失。例如,寫入A機房的數據還沒有同步到B機房,此時A機房機器宕機會導致數據丟失,那這部分丟失的數據是否對業務會產生重大影響。

例如,登錄過程中產生的session數據就是可丟失的,因爲用戶只要重新登錄就可以生成新的session;而用戶ID數據是不可丟失的,丟失後用戶就會失去所有和用戶ID相關的數據,例如用戶的好友、用戶的錢等。

  • 可恢復性

可恢復性指數據丟失後,是否可以通過某種手段進行恢復,如果數據可以恢復,至少說明對業務的影響不會那麼大,這樣可以相應地降低異地多活架構設計的複雜度。

例如,用戶的微博丟失後,用戶重新發一篇一模一樣的微博,這個就是可恢復的;或者用戶密碼丟失,用戶可以通過找回密碼來重新設置一個新密碼,這也算是可以恢復的;而用戶賬號如果丟失,用戶無法登錄系統,系統也無法通過其他途徑來恢復這個賬號,這就是不可恢復的數據。

我們同樣以用戶管理系統的登錄業務爲例,簡單分析如下表所示。

圖片

第3步:數據同步

確定數據的特點後,我們可以根據不同的數據設計不同的同步方案。常見的數據同步方案有:

  • 存儲系統同步

這是最常用也是最簡單的同步方式。例如,使用MySQL的數據主從數據同步、主主數據同步。

這類數據同步的優點是使用簡單,因爲幾乎主流的存儲系統都會有自己的同步方案;缺點是這類同步方案都是通用的,無法針對業務數據特點做定製化的控制。例如,無論需要同步的數據量有多大,MySQL都只有一個同步通道。因爲要保證事務性,一旦數據量比較大,或者網絡有延遲,則同步延遲就會比較嚴重。

  • 消息隊列同步

採用獨立消息隊列進行數據同步,常見的消息隊列有Kafka、RocketMQ等。

消息隊列同步適合無事務性或者無時序性要求的數據。例如,用戶賬號,兩個用戶先後註冊了賬號A和B,如果同步時先把B同步到異地機房,再同步A到異地機房,業務上是沒有問題的。而如果是用戶密碼,用戶先改了密碼爲m,然後改了密碼爲n,同步時必須先保證同步m到異地機房,再同步n到異地機房;如果反過來,同步後用戶的密碼就不對了。因此,對於新註冊的用戶賬號,我們可以採用消息隊列同步了;而對於用戶密碼,就不能採用消息隊列同步了。

  • 重複生成

數據不同步到異地機房,每個機房都可以生成數據,這個方案適合於可以重複生成的數據。例如,登錄產生的cookie、session數據、緩存數據等。

我們同樣以用戶管理系統的登錄業務爲例,針對不同的數據特點設計不同的同步方案,如下表所示。

圖片

第4步:異常處理

無論數據同步方案如何設計,一旦出現極端異常的情況,總是會有部分數據出現異常的。例如,同步延遲、數據丟失、數據不一致等。異常處理就是假設在出現這些問題時,系統將採取什麼措施來應對。異常處理主要有以下幾個目的:

  • 問題發生時,避免少量數據異常導致整體業務不可用。

  • 問題恢復後,將異常的數據進行修正。

  • 對用戶進行安撫,彌補用戶損失。

常見的異常處理措施有這幾類:

1.多通道同步

多通道同步的含義是採取多種方式來進行數據同步,其中某條通道故障的情況下,系統可以通過其他方式來進行同步,這種方式可以應對同步通道處故障的情況。

以用戶管理系統中的用戶賬號數據爲例,我們的設計方案一開始挑選了消息隊列的方式進行同步,考慮異常情況下,消息隊列同步通道可能中斷,也可能延遲很嚴重;爲了保證新註冊賬號能夠快速同步到異地機房,我們再增加一種MySQL同步這種方式作爲備份。這樣針對用戶賬號數據同步,系統就有兩種同步方式:MySQL主從同步和消息隊列同步。除非兩個通道同時故障,否則用戶賬號數據在其中一個通道異常的情況下,能夠通過另外一個通道繼續同步到異地機房,如下圖所示。

圖片

多通道同步設計的方案關鍵點有:

  • 一般情況下,採取兩通道即可,採取更多通道理論上能夠降低風險,但付出的成本也會增加很多。

  • 數據庫同步通道和消息隊列同步通道不能採用相同的網絡連接,否則一旦網絡故障,兩個通道都同時故障;可以一個走公網連接,一個走內網連接。

  • 需要數據是可以重複覆蓋的,即無論哪個通道先到哪個通道後到,最終結果是一樣的。例如,新建賬號數據就符合這個標準,而密碼數據則不符合這個標準。

2.同步和訪問結合

這裏的訪問指異地機房通過系統的接口來進行數據訪問。例如業務部署在異地兩個機房A和B,B機房的業務系統通過接口來訪問A機房的系統獲取賬號信息,如下圖所示。

圖片

同步和訪問結合方案的設計關鍵點有:

  • 接口訪問通道和數據庫同步通道不能採用相同的網絡連接,不能讓數據庫同步和接口訪問都走同一條網絡通道,可以採用接口訪問走公網連接,數據庫同步走內網連接這種方式。

  • 數據有路由規則,可以根據數據來推斷應該訪問哪個機房的接口來讀取數據。例如,有3個機房A、B、C,B機房拿到一個不屬於B機房的數據後,需要根據路由規則判斷是訪問A機房接口,還是訪問C機房接口。

  • 由於有同步通道,優先讀取本地數據,本地數據無法讀取到再通過接口去訪問,這樣可以大大降低跨機房的異地接口訪問數量,適合於實時性要求非常高的數據。

3.日誌記錄

日誌記錄主要用於用戶故障恢復後對數據進行恢復,其主要方式是每個關鍵操作前後都記錄相關一條日誌,然後將日誌保存在一個獨立的地方,當故障恢復後,拿出日誌跟數據進行對比,對數據進行修復。

爲了應對不同級別的故障,日誌保存的要求也不一樣,常見的日誌保存方式有:

  • 服務器上保存日誌,數據庫中保存數據,這種方式可以應對單臺數據庫服務器故障或者宕機的情況。

  • 本地獨立系統保存日誌,這種方式可以應對某業務服務器和數據庫同時宕機的情況。例如,服務器和數據庫部署在同一個機架,或者同一個電源線路上,就會出現服務器和數據庫同時宕機的情況。

  • 日誌異地保存,這種方式可以應對機房宕機的情況。

上面不同的日誌保存方式,應對的故障越嚴重,方案本身的複雜度和成本就會越高,實際選擇時需要綜合考慮成本和收益情況。

4.用戶補償

無論採用什麼樣的異常處理措施,都只能最大限度地降低受到影響的範圍和程度,無法完全做到沒有任何影響。例如,雙同步通道有可能同時出現故障、日誌記錄方案本身日誌也可能丟失。因此,無論多麼完美的方案,故障的場景下總是可能有一小部分用戶業務上出問題,系統無法彌補這部分用戶的損失。但我們可以採用人工的方式對用戶進行補償,彌補用戶損失,培養用戶的忠誠度。簡單來說,系統的方案是爲了保證99.99%的用戶在故障的場景下業務不受影響,人工的補償是爲了彌補0.01%的用戶的損失。

總結

今天我們談了異地多活架構的應用場景和常見架構模式,結合CAP、BASE等理論講了異地多活的設計技巧,並詳細講述瞭如何設計異地多活架構,希望對你有所幫助。

 

作者| 易安

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章