軟件體系架構閱讀筆記(八)

1. 全部業務異地多活數據庫

「異地多活」是爲了保證業務的高可用,但不少朋友在考慮這個「業務」的時候,會不自覺的陷入一個思惟誤區:我要保證全部業務的「異地多活」!微信

好比說假設咱們須要作一個「用戶子系統」,這個子系統負責「註冊」、「登陸」、「用戶信息」三個業務。爲了支持海量用戶,咱們設計了一個「用戶分區」的架構,即:正常狀況下用戶屬於某個主分區,每一個分區都有其它數據的備份,用戶用郵箱或者手機號註冊,路由層拿到郵箱或者手機號後,經過hash計算屬於哪一個中心,而後請求對應的業務中心。基本的架構以下:網絡

考慮這樣一個系統,若是3個業務要同時實現異地多活,咱們會發現以下一些難以解決的問題:session

【註冊】架構

A中心註冊了用戶,數據還未同步到B中心,此時A中心宕機,爲了支持註冊業務多活,那咱們能夠挑選B中心讓用戶去從新註冊。看起來很容易就支持多活了,但仔細思考一下會發現這樣作會有問題:一個手機號只能註冊一個帳號,A中心的數據沒有同步過來,B中心沒法判斷這個手機號是否重複,若是B中心讓用戶註冊,後來A中心恢復了,發現數據有衝突,怎麼解決?其實是沒法解決的,由於註冊帳號不能說挑選最後一個生效;而若是B中心不支持原本屬於A中心的業務進行註冊,註冊業務的雙活又成了空談。異步

有的朋友可能會說:那我修改業務規則,容許一個手機號註冊多個帳號不就能夠了麼?elasticsearch

這樣作是不可行的,相似一個手機號只能註冊一個帳號這種規則,是核心業務規則,修改核心業務規則的代價很是大,幾乎全部的業務都要從新設計,爲了架構設計去改變業務規則,並且是這麼核心的業務規則是得不償失的。性能

【用戶信息】線程

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

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

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

綜合上面的簡單分析,咱們能夠發現,若是「註冊」「登陸」、「用戶信息」所有都要支持異地多活的話,其實是挺難的,有的問題甚至是無解的。那這種狀況下咱們應該如何考慮「異地多活」的方案設計呢?答案其實很簡單:優先實現核心業務的異地多活方案!

對於咱們的這個模擬案例來講,「登陸」纔是最核心的業務,「註冊」和「用戶信息」雖然也是主要業務,但並不必定要實現異地多活。主要緣由在於業務影響。對於一個日活1000萬的業務來講,天天註冊用戶多是幾萬,修改用戶信息的可能還不到1萬,但登陸用戶是1000萬,很明顯咱們應該保證登陸的異地多活。對於新用戶來講,註冊不了影響並不很明顯,由於他尚未真正開始業務;用戶信息修改也相似,用戶暫時修改不了用戶信息,對於其業務不會有很大影響,而若是有幾百萬用戶登陸不了,就至關於幾百萬用戶沒法使用業務,對業務的影響就很是大了:公司的客服熱線很快就被打爆了,微博微信上處處都在傳業務宕機,論壇裏面處處是在罵孃的用戶,那就是互聯網大事件了!

而登陸實現「異地多活」偏偏是最簡單的,由於每一箇中心都有全部用戶的帳號和密碼信息,用戶在哪一個中心均可以登陸。用戶在A中心登陸,A中心宕機後,用戶到B中心從新登陸便可。

有的朋友可能會問,若是某個用戶在A中心修改了密碼,此時數據尚未同步到B中心,用戶到B中心登陸是沒法登陸的,這個怎麼處理?這個問題其實就涉及另一個思惟誤區了,咱們稍後再談。

2. 實時一致性

異地多活本質上是經過異地的數據冗餘,來保證在極端異常的狀況下業務也可以正常提供給用戶,所以數據同步是異地多活設計方案的核心,但咱們大部分人在考慮數據同步方案的時候,也會不知不覺的陷入完美主義誤區:我要全部數據都實時同步!

數據冗餘就要將數據從A地同步到B地,從業務的角度來看是越快越好,最好和本地機房同樣的速度最好,但讓人頭疼的問題正在這裏:異地多活理論上就不可能很快,由於這是物理定律決定的,即:光速真空傳播是每秒30萬千米,在光纖中傳輸的速度大約是每秒20萬千米,再加上傳輸中的各類網絡設備的處理,實際還遠遠達不到光速的速度。

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

所以異地多活方案面臨一個沒法完全解決的矛盾:業務上要求數據快速同步,物理上正好作不到數據快速同步,所以全部數據都實時同步,其實是一個沒法達到的目標。

既然是沒法完全解決的矛盾,那就只能想辦法儘可能減小影響。有幾種方法能夠參考:

儘可能減小異地多活機房的距離,搭建高速網絡; 儘可能減小數據同步; 保證最終一致性,不保證明時一致性;

【減小距離:同城多中心】

爲了減小兩個業務中心的距離,選擇在同一個城市不一樣的區搭建機房,機房間經過高速網絡連通,例如在北京的海定區和通州區各搭建一個機房,兩個機房間採用高速光纖網絡連通,可以達到近似在一個機房的性能。

這個方案的優點在於對業務幾乎沒有影響,業務能夠無縫的切換到同城多中心方案;缺點就是沒法應對例如新奧爾良全城被水淹,或者2003美加大停電這種極端狀況。因此即便採用這種方案,也還必須有一個其它城市的業務中心做爲備份,最終的方案一樣仍是要考慮遠距離的數據傳輸問題。

【減小數據同步】

另一種方式就是減小須要同步的數據。簡單來講就是不重要的數據不要同步,同步後沒用的數據不一樣步。

之前面的「用戶子系統」爲例,用戶登陸所產生的token或者session信息,數據量很大,但其實並不須要同步到其它業務中心,由於這些數據丟失後從新登陸就能夠了。

有的朋友會問:這些數據丟失後要求用戶從新登陸,影響用戶體驗的呀! 確實如此,畢竟須要用戶從新輸入帳戶和密碼信息,或者至少要彈出登陸界面讓用戶點擊一次,但相比爲了同步全部數據帶來的代價,這個影響徹底能夠接受,其實這個問題也涉及了一個異地多活設計的典型思惟誤區,後面咱們會詳細講到。

【保證最終一致性】

第三種方式就是業務不依賴數據同步的實時性,只要數據最終能一致便可。例如:A機房註冊了一個用戶,業務上不要求可以在50ms內就同步到全部機房,正常狀況下要求5分鐘同步到全部機房便可,異常狀況下甚至能夠容許1小時或者1天后可以一致。

最終一致性在具體實現的時候,還須要根據不一樣的數據特徵,進行差別化的處理,以知足業務須要。例如對「帳號」信息來講,若是在A機房新註冊的用戶5分鐘內正好跑到B機房了,此時B機房尚未這個用戶的信息,爲了保證業務的正確,B機房就須要根據路由規則到A機房請求數據(這種處理方式其實就是後面講的「二次讀取」)。

而對「用戶信息」來講,5分鐘後同步也沒有問題,也不須要採起其它措施來彌補,但仍是會影響用戶體驗,即用戶看到了舊的用戶信息,這個問題怎麼解決呢?這個問題實際上也涉及到了一個思惟誤區,在最後咱們統一分析。

3. 只使用存儲系統的同步功能

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

既然說存儲系統自己就有同步功能,並且同步功能還很強大,爲什麼說只使用存儲系統是一個思惟誤區呢?由於雖然絕大部分場景下,存儲系統自己的同步功能基本上也夠用了,但在某些比較極端的狀況下,存儲系統自己的同步功能可能難以知足業務需求。

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

Redis又是另一個問題,Redis 3.0以前沒有Cluster功能,只有主從複製功能,而爲了設計上的簡單,Redis主從複製有一個比較大的隱患:從機宕機或者和主機斷開鏈接都須要從新鏈接主機,從新鏈接主機都會觸發全量的主從複製,這時候主機會生成內存快照,主機依然能夠對外提供服務,可是做爲讀的從機,就沒法提供對外服務了,若是數據量大,恢復的時間會至關的長。

綜合上述的案例能夠看出,存儲系統自己自帶的同步功能,在某些場景下是沒法知足咱們業務須要的。尤爲是異地多機房這種部署,各類各樣的異常均可能出現,當咱們只考慮存儲系統自己的同步功能時,就會發現沒法作到真正的異地多活。

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

例如,仍是之前面的「用戶子系統」爲例,咱們能夠採用以下幾種方式同步數據:

消息隊列方式:對於帳號數據,因爲帳號只會建立,不會修改和刪除(假設咱們不提供刪除功能),咱們能夠將帳號數據經過消息隊列同步到其它業務中心。

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

存儲系統同步方式:對於密碼數據,因爲用戶改密碼頻率較低,並且用戶不可能在1s內連續改屢次密碼,因此經過數據庫的同步機制將數據複製到其它業務中心便可,用戶信息數據和密碼相似。

回源讀取方式:對於登陸的session數據,因爲數據量很大,咱們能夠不一樣步數據;但當用戶在A中心登陸後,而後又在B中心登陸,B中心拿到用戶上傳的session id後,根據路由判斷session屬於A中心,直接去A中心請求session數據便可,反之亦然,A中心也能夠到B中心去拿取session數據。

從新生成數據方式:對於第4中場景,若是異常狀況下,A中心宕機了,B中心請求session數據失敗,此時就只能登陸失敗,讓用戶從新在B中心登陸,生成新的session數據。

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

綜合上述的各類措施,最後咱們的「用戶子系統」同步方式總體以下:

 

4. 100%可用性

前面咱們在給出每一個思惟誤區對應的解決方案的時候,其實都遺留了一些小尾巴:某些場景下咱們沒法保證100%的業務可用性,老是會有必定的損失。例如密碼不一樣步致使沒法登陸、用戶信息不一樣步致使用戶看到舊的用戶信息等等,這個問題怎麼解決?

其實這個問題涉及異地多活設計方案中一個典型的思惟誤區:我要保證業務100%可用!但極端狀況下就是會丟一部分數據,就是會有一部分數據不能同步,怎麼辦呢,有沒有什麼巧妙和神通的辦法能作到?

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

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

固然,針對銀行轉帳這個業務,能夠有不少特殊的業務手段來實現異地多活。例如分爲「實時轉帳」和「轉帳申請」。實時轉帳就是咱們上述的案例,是沒法作到「異地多活」的;但「轉帳申請」是能夠作到「異地多活」的,即:小明在上海業務中心提交轉帳請求,但上海的業務中心並不當即轉帳,而是記錄這個轉帳請求,而後後臺異步發起真正的轉帳操做,若是此時北京業務中心不可用,轉帳請求就能夠繼續等待重試;假設等待2個小時後北京業務中心恢復了,此時上海業務中心去請求轉帳,發現餘額不夠,這個轉帳請求就失敗了。小明再登陸上來就會看到轉帳申請失敗,緣由是「餘額不足」。不過須要注意的是「轉帳申請」的這種方式雖然有助於實現異地多活,但其實仍是犧牲了用戶體驗的,對於小明來講,原本一次操做的事情,須要分爲兩次:一次提交轉帳申請,另一次要確認是否轉帳成功。

雖然咱們沒法作到100%可用性,但並不意味着咱們什麼都不能作,爲了讓用戶內心更好受一些,咱們能夠採起一些措施進行安撫或者補償,例如:

掛公告:說明如今有問題和基本的問題緣由,若是不明確緣由或者不方便說出緣由,能夠說「技術哥哥正在緊急處理」比較輕鬆和有趣的公告。

過後對用戶進行補償:例如送一些業務上可用的代金券、小禮包等,下降用戶的抱怨。

補充體驗:對於爲了作異地多活而帶來的體驗損失,能夠想一些方法減小或者規避。以「轉帳申請」爲例,爲了讓用戶不用確認轉帳申請是否成功,咱們能夠在轉帳成功或者失敗後直接給用戶發個短信,告訴他轉帳結果,這樣用戶就不用不時的登陸系統來確認轉帳是否成功了。

5. 一句話談「異地多活」

綜合前面的分析,異地多活設計的理念能夠總結爲一句話:採用多種手段,保證絕大部分用戶的核心業務異地多活

文章連接:http://www.uml.org.cn/zjjs/2016120505.asp

相關文章
相關標籤/搜索