做者丨湯波
來源丨uee.me/cFgQC自 2008 年雙 11 以來,在每一年雙 11 超大規模流量的衝擊上,螞蟻金服都會不斷突破現有技術的極限。2010 年雙 11 的支付峯值爲 2 萬筆/分鐘,到 2017 年雙 11 時這個數字變爲了 25.6 萬筆/秒。html
2018 年雙 11 的支付峯值爲 48 萬筆/秒,2019 年雙 11 支付峯值爲 54.4 萬筆/秒,創下新紀錄,是 2009 年第一次雙 11 的 1360 倍。
在如此之大的支付 TPS 背後除了削峯等錦上添花的應用級優化,最解渴最實質的招數當數基於分庫分表的單元化了,螞蟻技術稱之爲 LDC(邏輯數據中心)。
本文不打算討論具體到代碼級的分析,而是嘗試用最簡單的描述來講明其中最大快人心的原理。
我想關心分佈式系統設計的人都曾被下面這些問題所困擾過:
支付寶海量支付背後最解渴的設計是啥?換句話說,實現支付寶高 TPS 的最關鍵的設計是啥?前端
LDC 是啥?LDC 怎麼實現異地多活和異地災備的?程序員
CAP 魔咒究竟是啥?P 到底怎麼理解?算法
什麼是腦裂?跟 CAP 又是啥關係?數據庫
什麼是 PAXOS,它解決了啥問題?express
PAXOS 和 CAP 啥關係?PAXOS 能夠逃脫 CAP 魔咒麼?後端
Oceanbase 能逃脫 CAP 魔咒麼?緩存
若是你對這些感興趣,不妨看一場赤裸裸的論述,拒絕使用晦澀難懂的詞彙,直面最本質的邏輯。
LDC 和單元化服務器
LDC(logic data center)是相對於傳統的(Internet Data Center-IDC)提出的,邏輯數據中心所表達的中心思想是不管物理結構如何的分佈,整個數據中心在邏輯上是協同和統一的。
這句話暗含的是強大的體系設計,分佈式系統的挑戰就在於總體協同工做(可用性,分區容忍性)和統一(一致性)。
單元化是大型互聯網系統的必然選擇趨勢,舉個最最通俗的例子來講明單元化。
咱們老是說 TPS 很難提高,確實任何一家互聯網公司(好比淘寶、攜程、新浪)它的交易 TPS 頂多以十萬計量(平均水平),很難往上串了。
由於數據庫存儲層瓶頸的存在再多水平擴展的服務器都沒法繞開,而從整個互聯網的視角看,全世界電商的交易 TPS 能夠輕鬆上億。
這個例子帶給咱們一些思考:
爲啥幾家互聯網公司的 TPS 之和能夠那麼大,服務的用戶數規模也極爲嚇人,而單個互聯網公司的 TPS 卻很難提高?
究其本質,每家互聯網公司都是一個獨立的大型單元,他們各自服務本身的用戶互不干擾。
這就是單元化的基本特性,任何一家互聯網公司,其想要成倍的擴大本身系統的服務能力,都必然會走向單元化之路。
它的本質是分治,咱們把廣大的用戶分爲若干部分,同時把系統複製多份,每一份都獨立部署,每一份系統都服務特定的一羣用戶。
以淘寶舉例,這樣以後,就會有不少個淘寶系統分別爲不一樣的用戶服務,每一個淘寶系統都作到十萬 TPS 的話,N 個這樣的系統就能夠輕鬆作到 N*十萬的 TPS 了。
LDC 實現的關鍵就在於單元化系統架構設計,因此在螞蟻內部,LDC 和單元化是不分家的,這也是不少同窗比較困擾的地方,看似沒啥關係,實則是單元化體系設計成就了 LDC。
小結:
分庫分表解決的最大痛點是數據庫單點瓶頸,這個瓶頸的產生是由現代二進制數據存儲體系決定的(即 I/O 速度)。
單元化只是分庫分表後系統部署的一種方式,這種部署模式在災備方面也發揮了極大的優點。
系統架構演化史網絡
幾乎任何規模的互聯網公司,都有本身的系統架構迭代和更新,大體的演化路徑都大同小異。
最先通常爲了業務快速上線,全部功能都會放到一個應用裏,系統架構以下圖所示:
這樣的架構顯然是有問題的,單機有着明顯的單點效應,單機的容量和性能都是很侷限的,而使用中小型機會帶來大量的浪費。
隨着業務發展,這個矛盾逐漸轉變爲主要矛盾,所以工程師們採用瞭如下架構:
這是整個公司第一次觸碰到分佈式,也就是對某個應用進行了水平擴容,它將多個微機的計算能力團結了起來,能夠完勝同等價格的中小型機器。
慢慢的,你們發現,應用服務器 CPU 都很正常了,可是仍是有不少慢請求,究其緣由,是由於單點數據庫帶來了性能瓶頸。
因而程序員們決定使用主從結構的數據庫集羣,以下圖所示:
其中大部分讀操做能夠直接訪問從庫,從而減輕主庫的壓力。然而這種方式仍是沒法解決寫瓶頸,寫依舊須要主庫來處理,當業務量量級再次增高時,寫已經變成刻不容緩的待處理瓶
這時候,分庫分表方案出現了:
分庫分表不只能夠對相同的庫進行拆分,還能夠對相同的表進行拆分,對錶進行拆分的方式叫作水平拆分。
不一樣功能的表放到不一樣的庫裏,通常對應的是垂直拆分(按照業務功能進行拆分),此時通常還對應了微服務化。
這種方法作到極致基本能支撐 TPS 在萬級甚至更高的訪問量了。然而隨着相同應用擴展的越多,每一個數據庫的連接數也巨量增加,這讓數據庫自己的資源成爲了瓶頸。
這個問題產生的本質是全量數據無差異的分享了全部的應用資源,好比 A 用戶的請求在負載均衡的分配下可能分配到任意一個應用服務器上,於是全部應用所有都要連接 A 用戶所在的分庫,數據庫鏈接數就變成笛卡爾乘積了。
在本質點說,這種模式的資源隔離性還不夠完全。要解決這個問題,就須要把識別用戶分庫的邏輯往上層移動,從數據庫層移動到路由網關層。
這樣一來,從應用服務器 a 進來的來自 A 客戶的全部請求必然落庫到 DB-A,所以 a 也不用連接其餘的數據庫實例了,這樣一個單元化的雛形就誕生了。
思考一下,應用間其實也存在交互(好比 A 轉帳給 B),也就意味着,應用不須要連接其餘的數據庫了,可是還須要連接其餘應用。
若是是常見的 RPC 框架如 Dubbo 等,使用的是 TCP/IP 協議,那麼等同於把以前與數據庫創建的連接,換成與其餘應用之間的連接了。
爲啥這樣就消除瓶頸了呢?首先因爲合理的設計,應用間的數據交互並不巨量,其次應用間的交互能夠共享 TCP 連接,好比 A->B 之間的 Socket 連接能夠被 A 中的多個線程複用。
而通常的數據庫如 MySQL 則不行,因此 MySQL 才須要數據庫連接池。
如上圖所示,但咱們把整套系統打包爲單元化時,每一類的數據從進單元開始就註定在這個單元被消化,因爲這種完全的隔離性,整個單元能夠輕鬆的部署到任意機房而依然能保證邏輯上的統一。
下圖爲一個三地五機房的部署方式:
螞蟻單元化架構實踐
螞蟻支付寶應該是國內最大的支付工具,其在雙 11 等活動日當日的支付 TPS 可達幾十萬級,將來這個數字可能會更大,這決定了螞蟻單元化架構從容量要求上看必然從單機房走向多機房。
另外一方面,異地災備也決定了這些 IDC 機房必須是異地部署的。總體上支付寶也採用了三地五中心(IDC 機房)來保障系統的可用性。
跟上文中描述的有所不一樣的是,支付寶將單元分紅了三類(也稱 CRG 架構):
RZone(Region Zone):直譯可能有點反而很差理解。實際上就是全部能夠分庫分表的業務系統總體部署的最小單元。每一個 RZone 連上數據庫就能夠撐起一片天空,把業務跑的溜溜的。
GZone(Global Zone):全局單元,意味着全局只有一份。部署了不可拆分的數據和服務,好比系統配置等。
實際狀況下,GZone 異地也會部署,不過僅是用於災備,同一時刻,只有一地 GZone 進行全局服務。GZone 通常被 RZone 依賴,提供的大部分是讀取服務。
CZone(City Zone):顧名思義,這是以城市爲單位部署的單元。一樣部署了不可拆分的數據和服務,好比用戶帳號服務,客戶信息服務等。理論上 CZone 會被 RZone 以比訪問 GZone 高不少的頻率進行訪問。
CZone 是基於特定的 GZone 場景進行優化的一種單元,它把 GZone 中有些有着」寫讀時間差現象」的數據和服務進行了的單獨部署,這樣 RZone 只須要訪問本地的 CZone 便可,而不是訪問異地的 GZone。
「寫讀時間差現象」是螞蟻架構師們根據實踐統計總結的,他們發現大部分狀況下,一個數據被寫入後,都會過足夠長的時間後纔會被訪問。
生活中這種例子很常見,咱們辦完銀行卡後可能好久纔會存第一筆錢;咱們建立微博帳號後,可能想半天才會發微博;咱們下載建立淘寶帳號後,可能得瀏覽好幾分鐘纔會下單買東西。
固然了這些例子中的時間差遠遠超過了系統同步時間。通常來講異地的延時在 100ms 之內,因此只要知足某地 CZone 寫入數據後 100ms 之後才用這個數據,這樣的數據和服務就適合放到 CZone 中。
相信你們看到這都會問:爲啥分這三種單元?
其實其背後對應的是不一樣性質的數據,而服務不過是對數據的操做集。
下面咱們來根據數據性質的不一樣來解釋支付寶的 CRG 架構。當下幾乎全部互聯網公司的分庫分表規則都是根據用戶 ID 來制定的。
而圍繞用戶來看整個系統的數據能夠分爲如下兩類:
用戶流水型數據:
典型的有用戶的訂單、用戶發的評論、用戶的行爲記錄等。
這些數據都是用戶行爲產生的流水型數據,具有自然的用戶隔離性,好比 A 用戶的 App 上絕對看不到 B 用戶的訂單列表。因此此類數據很是適合分庫分表後獨立部署服務。
用戶間共享型數據:
這種類型的數據又分兩類。一類共享型數據是像帳號、我的博客等可能會被全部用戶請求訪問的用戶數據。
好比 A 向 B 轉帳,A 給 B 發消息,這時候須要確認 B 帳號是否存在;又好比 A 想看 B 的我的博客之類的。
另一類是用戶無關型數據,像商品、系統配置(匯率、優惠政策)、財務統計等這些非用戶緯度的數據,很難說跟具體的某一類用戶掛鉤,可能涉及到全部用戶。
好比商品,假設按商品所在地來存放商品數據(這須要雙維度分庫分表),那麼上海的用戶仍然須要訪問杭州的商品。
這就又構成跨地跨 Zone 訪問了,仍是達不到單元化的理想狀態,並且雙維度分庫分表會給整個 LDC 運維帶來複雜度提高。
注:網上和支付寶內部有另一些分法,好比流水型和狀態性,有時候還會分爲三類:流水型、狀態型和配置型。
我的以爲這些分法雖然嘗試去更高層次的抽象數據分類,但實際上邊界很模糊,拔苗助長。
直觀的類比,咱們能夠很輕易的將上述兩類數據對應的服務劃分爲 RZone 和 GZone,RZone 包含的就是分庫分表後負責固定客戶羣體的服務,GZone 則包含了用戶間共享的公共數據對應的服務。
到這裏爲止,一切都很完美,這也是主流的單元化話題了。
對比支付寶的 CRG 架構,咱們一眼就發現少了 C(City Zone),CZone 確實是螞蟻在單元化實踐領域的一個創新點。
再來分析下 GZone,GZone 之因此只能單地部署,是由於其數據要求被全部用戶共享,沒法分庫分表,而多地部署會帶來由異地延時引發的不一致。
好比實時風控系統,若是多地部署,某個 RZone 直接讀取本地的話,很容易讀取到舊的風控狀態,這是很危險的。
這時螞蟻架構師們問了本身一個問題——難道全部數據受不了延時麼?
這個問題像是打開了新世界的大門,經過對 RZone 已有業務的分析,架構師們發現 80% 甚至更高的場景下,數據更新後都不要求立馬被讀取到。
也就是上文提到的」寫讀時間差現象」,那麼這就好辦了,對於這類數據,咱們容許每一個地區的 RZone 服務直接訪問本地,爲了給這些 RZone 提供這些數據的本地訪問能力,螞蟻架構師設計出了 CZone。
在 CZone 的場景下,寫請求通常從 GZone 寫入公共數據所在庫,而後同步到整個 OB 集羣,而後由 CZone 提供讀取服務。好比支付寶的會員服務就是如此。
即使架構師們設計了完美的 CRG,但即使在螞蟻的實際應用中,各個系統仍然存在不合理的 CRG 分類,尤爲是 CG 不分的現象很常見。
支付寶單元化的異地多活和災備
流量唆使技術探祕簡介
單元化後,異地多活只是多地部署而已。好比上海的兩個單元爲 ID 範圍爲 [00~19],[40~59] 的用戶服務。
而杭州的兩個單元爲 ID 爲 [20~39]和[60,79]的用戶服務,這樣上海和杭州就是異地雙活的。
支付寶對單元化的基本要求是每一個單元都具有服務全部用戶的能力,即——具體的那個單元服務哪些用戶是能夠動態配置的。因此異地雙活的這些單元還充當了彼此的備份。
發現工做中冷備熱備已經被用的很亂了。最先冷備是指數據庫在備份數據時須要關閉後進行備份(也叫離線備份),防止數據備份過程當中又修改了,不須要關閉即在運行過程當中進行數據備份的方式叫作熱備(也叫在線備份)。
也不知道從哪一天開始,冷備在主備系統裏表明了這臺備用機器是關閉狀態的,只有主服務器掛了以後,備服務器纔會被啓動。
而相同的熱備變成了備服務器也是啓動的,只是沒有流量而已,一旦主服務器掛了以後,流量自動打到備服務器上。本文不打算用第二種理解,由於感受有點野。
爲了作到每一個單元訪問哪些用戶變成可配置,支付寶要求單元化管理系統具有流量到單元的可配置以及單元到 DB 的可配置能力。
以下圖所示:
其中 Spanner 是螞蟻基於 Nginx 自研的反向代理網關,也很好理解,有些請求咱們但願在反向代理層就被轉發至其餘 IDC 的 Spanner 而無需進入後端服務,如圖箭頭 2 所示。
那麼對於應該在本 IDC 處理的請求,就直接映射到對應的 RZ 便可,如圖箭頭 1。
進入後端服務後,理論上若是請求只是讀取用戶流水型數據,那麼通常不會再進行路由了。
然而,對於有些場景來講,A 用戶的一個請求可能關聯了對 B 用戶數據的訪問,好比 A 轉帳給 B,A 扣完錢後要調用帳務系統去增長 B 的餘額。
這時候就涉及到再次的路由,一樣有兩個結果:跳轉到其餘 IDC(如圖箭頭 3)或是跳轉到本 IDC 的其餘 RZone(如圖箭頭 4)。
RZone 到 DB 數據分區的訪問這是事先配置好的,上圖中 RZ 和 DB 數據分區的關係爲:
RZ0* --> a
RZ1* --> b
RZ2* --> c
RZ3* --> d
下面咱們舉個例子來講明整個流量唆使的過程,假設 C 用戶所屬的數據分區是 c,而 C 用戶在杭州訪問了 cashier.alipay.com(隨便編的)。
①目前支付寶默認會按照地域來路由流量,具體的實現承載者是自研的 GLSB(Global Server Load Balancing):
https://developer.alipay.com/article/1889
它會根據請求者的 IP,自動將 cashier.alipay.com 解析爲杭州 IDC 的 IP 地址(或者跳轉到 IDC 所在的域名)。
你們本身搞過網站的化應該知道大部分 DNS 服務商的地址都是靠人去配置的,GLSB 屬於動態配置域名的系統,網上也有比較火的相似產品,好比花生殼之類(建過私站的同窗應該很熟悉)的。
②好了,到此爲止,用戶的請求來到了 IDC-1 的 Spanner 集羣服務器上,Spanner 從內存中讀取到了路由配置,知道了這個請求的主體用戶 C 所屬的 RZ3* 再也不本 IDC,因而直接轉到了 IDC-2 進行處理。
③進入 IDC-2 以後,根據流量配比規則,該請求被分配到了 RZ3B 進行處理。
④RZ3B 獲得請求後對數據分區 c 進行訪問。
⑤處理完畢後原路返回。
你們應該發現問題所在了,若是再來一個這樣的請求,豈不是每次都要跨地域進行調用和返回體傳遞?
確實是存在這樣的問題的,對於這種問題,支付寶架構師們決定繼續把決策邏輯往用戶終端推移。
好比,每一個 IDC 機房都會有本身的域名(真實狀況可能不是這樣命名的):
那麼請求從 IDC-1 涮過一遍返回時會將前端請求跳轉到 cashieridc-2.alipay.com 去(若是是 App,只須要替換 rest 調用的接口域名),後面全部用戶的行爲都會在這個域名上發生,就避免了走一遍 IDC-1 帶來的延時。
支付寶災備機制
流量唆使是災備切換的基礎和前提條件,發生災難後的通用方法就是把陷入災難的單元的流量從新打到正常的單元上去,這個流量切換的過程俗稱切流。
支付寶 LDC 架構下的災備有三個層次:
同機房單元間災備:
災難發生可能性相對最高(但其實也很小)。對 LDC 來講,最小的災難就是某個單元因爲一些緣由(局部插座斷開、線路老化、人爲操做失誤)宕機了。
從上節裏的圖中能夠看到每組 RZ 都有 A,B 兩個單元,這就是用來作同機房災備的,而且 AB 之間也是雙活雙備的。
正常狀況下 AB 兩個單元共同分擔全部的請求,一旦 A 單元掛了,B 單元將自動承擔 A 單元的流量份額。這個災備方案是默認的。
同城機房間災備:
災難發生可能性相對更小。這種災難發生的緣由通常是機房電線網線被挖斷,或者機房維護人員操做失誤致使的。
在這種狀況下,就須要人工的制定流量唆使(切流)方案了。下面咱們舉例說明這個過程,以下圖所示爲上海的兩個 IDC 機房。
整個切流配置過程分兩步,首先須要將陷入災難的機房中 RZone 對應的數據分區的訪問權配置進行修改。
假設咱們的方案是由 IDC-2 機房的 RZ2 和 RZ3 分別接管 IDC-1 中的 RZ0 和 RZ1。
那麼首先要作的是把數據分區 a,b 對應的訪問權從 RZ0 和 RZ1 收回,分配給 RZ2 和 RZ3。
即將(如上圖所示爲初始映射):
RZ0* --> a
RZ1* --> b
RZ2* --> c
RZ3* --> d
變爲:
RZ0* --> /
RZ1* --> /
RZ2* --> a
RZ2* --> c
RZ3* --> b
RZ3* --> d
而後再修改用戶 ID 和 RZ 之間的映射配置。假設以前爲:
[00-24] --> RZ0A(50%),RZOB(50%)
[25-49] --> RZ1A(50%),RZ1B(50%)
[50-74] --> RZ2A(50%),RZ2B(50%)
[75-99] --> RZ3A(50%),RZ3B(50%)
那麼按照災備方案的要求,這個映射配置將變爲:
[00-24] --> RZ2A(50%),RZ2B(50%)
[25-49] --> RZ3A(50%),RZ3B(50%)
[50-74] --> RZ2A(50%),RZ2B(50%)
[75-99] --> RZ3A(50%),RZ3B(50%)
這樣以後,全部流量將會被打到 IDC-2 中,期間部分已經向 IDC-1 發起請求的用戶會收到失敗並重試的提示。
實際狀況中,整個過程並非災難發生後再去作的,整個切換的流程會以預案配置的形式事先準備好,推送給每一個流量唆使客戶端(集成到了全部的服務和 Spanner 中)。
這裏能夠思考下,爲什麼先切數據庫映射,再切流量呢?這是由於若是先切流量,意味着大量註定失敗的請求會被打到新的正常單元上去,從而影響系統的穩定性(數據庫還沒準備好)。
異地機房間災備:
這個基本上跟同城機房間災備一致(這也是單元化的優勢),再也不贅述。
螞蟻單元化架構的 CAP 分析
回顧 CAP
①CAP 的定義
CAP 原則是指任意一個分佈式系統,同時最多隻能知足其中的兩項,而沒法同時知足三項。
所謂的分佈式系統,說白了就是一件事一我的作的,如今分給好幾我的一塊兒幹。
咱們先簡單回顧下 CAP 各個維度的含義:
Consistency(一致性),
這個理解起來很簡單,就是每時每刻每一個節點上的同一份數據都是一致的。
這就要求任何更新都是原子的,即要麼所有成功,要麼所有失敗。想象一下使用分佈式事務來保證全部系統的原子性是多麼低效的一個操做。
Availability(可用性),
這個可用性看起來很容易理解,但真正說清楚的很少。我更願意把可用性解釋爲:任意時刻系統均可以提供讀寫服務。
舉個例子,當咱們用事務將全部節點鎖住來進行某種寫操做時,若是某個節點發生不可用的狀況,會讓整個系統不可用。
對於分片式的 NoSQL 中間件集羣(Redis,Memcached)來講,一旦一個分片歇菜了,整個系統的數據也就不完整了,讀取宕機分片的數據就會沒響應,也就是不可用了。
須要說明一點,哪些選擇 CP 的分佈式系統,並非表明可用性就徹底沒有了,只是可用性沒有保障了。
爲了增長可用性保障,這類中間件每每都提供了」分片集羣+複製集」的方案。
Partition tolerance(分區容忍性),
這個可能也是不少文章都沒說清楚的。P 並非像 CA 同樣是一個獨立的性質,它依託於 CA 來進行討論。
參考文獻中的解釋:」除非整個網絡癱瘓,不然任什麼時候刻系統都能正常工做」,言下之意是小範圍的網絡癱瘓,節點宕機,都不會影響整個系統的 CA。
我感受這個解釋聽着仍是有點懵逼,因此我的更願意解釋爲當節點之間網絡不通時(出現網絡分區),可用性和一致性仍然能獲得保障。
從我的角度理解,分區容忍性又分爲「可用性分區容忍性」和「一致性分區容忍性」。
出現分區時會不會影響可用性的關鍵在於需不須要全部節點互相溝通協做來完成一次事務,不須要的話是鐵定不影響可用性的。
慶幸的是應該不太會有分佈式系統會被設計成完成一次事務須要全部節點聯動,必定要舉個例子的話,全同步複製技術下的 MySQL 是一個典型案例。
出現分區時會不會影響一致性的關鍵則在於出現腦裂時有沒有保證一致性的方案,這對主從同步型數據庫(MySQL、SQL Server)是致命的。
一旦網絡出現分區,產生腦裂,系統會出現一份數據兩個值的狀態,誰都不以爲本身是錯的。
須要說明的是,正常來講同一局域網內,網絡分區的機率很是低,這也是爲啥咱們最熟悉的數據庫(MySQL、SQL Server 等)也是不考慮 P 的緣由。
下圖爲 CAP 之間的經典關係圖:
還有個須要說明的地方,其實分佈式系統很難知足 CAP 的前提條件是這個系統必定是有讀有寫的,若是隻考慮讀,那麼 CAP 很容易都知足。
好比一個計算器服務,接受表達式請求,返回計算結果,搞成水平擴展的分佈式,顯然這樣的系統沒有一致性問題,網絡分區也不怕,可用性也是很穩的,因此能夠知足 CAP。
②CAP 分析方法
先說下 CA 和 P 的關係,若是不考慮 P 的話,系統是能夠輕鬆實現 CA 的。
而 P 並非一個單獨的性質,它表明的是目標分佈式系統有沒有對網絡分區的狀況作容錯處理。
若是作了處理,就必定是帶有 P 的,接下來再考慮分區狀況下到底選擇了 A 仍是 C。
因此分析 CAP,建議先肯定有沒有對分區狀況作容錯處理。
如下是我的總結的分析一個分佈式系統 CAP 知足狀況的通常方法:
if( 不存在分區的可能性 || 分區後不影響可用性或一致性 || 有影響但考慮了分區狀況-P){
if(可用性分區容忍性-A under P))
return "AP";
else if(一致性分區容忍性-C under P)
return "CP";
}
else{ //分區有影響但沒考慮分區狀況下的容錯
if(具有可用性-A && 具有一致性-C){
return AC;
}
}
這裏說明下,若是考慮了分區容忍性,就不須要考慮不分區狀況下的可用性和一致性了(大可能是知足的)。
水平擴展應用+單數據庫實例的 CAP 分析
讓咱們再來回顧下分佈式應用系統的來由,早年每一個應用都是單體的,跑在一個服務器上,服務器一掛,服務就不可用了。
另一方面,單體應用因爲業務功能複雜,對機器的要求也逐漸變高,普通的微機沒法知足這種性能和容量的要求。
因此要拆!還在 IBM 大賣小型商用機的年代,阿里巴巴就提出要以分佈式微機替代小型機。
因此咱們發現,分佈式系統解決的最大的痛點,就是單體單機系統的可用性問題。
要想高可用,必須分佈式。一家互聯網公司的發展之路上,第一次與分佈式相遇應該都是在單體應用的水平擴展上。
也就是同一個應用啓動了多個實例,鏈接着相同的數據庫(爲了簡化問題,先不考慮數據庫是否單點),以下圖所示:
這樣的系統自然具備的就是 AP(可用性和分區容忍性):
然而,這樣的系統是沒有一致性可言的,想象一下每一個實例均可以往數據庫 insert 和 update(注意這裏還沒討論到事務),那還不亂了套。
因而咱們轉向了讓 DB 去作這個事,這時候」數據庫事務」就被用上了。用大部分公司會選擇的 MySQL 來舉例,用了事務以後會發現數據庫又變成了單點和瓶頸。
單點就像單機同樣(本例子中不考慮從庫模式),理論上就不叫分佈式了,若是必定要分析其 CAP 的話,
根據上面的步驟分析過程應該是這樣的:
分區容忍性:先看有沒有考慮分區容忍性,或者分區後是否會有影響。單臺 MySQL 沒法構成分區,要麼整個系統掛了,要麼就活着。
可用性分區容忍性:分區狀況下,假設剛好是該節點掛了,系統也就不可用了,因此可用性分區容忍性不知足。
一致性分區容忍性:分區狀況下,只要可用,單點單機的最大好處就是一致性能夠獲得保障。
所以這樣的一個系統,我的認爲只是知足了 CP。A 有但不出色,從這點能夠看出,CAP 並非非黑即白的。
包括常說的 BASE (最終一致性)方案,其實只是 C 不出色,但最終也是達到一致性的,BASE 在一致性上選擇了退讓。
關於分佈式應用+單點數據庫的模式算不算純正的分佈式系統,這個可能每一個人見解有點差別,上述只是我我的的一種理解,是否是分佈式系統不重要,重要的是分析過程。
其實咱們討論分佈式,就是但願系統的可用性是多個系統多活的,一個掛了另外的也能頂上,顯然單機單點的系統不具有這樣的高可用特性。
因此在我看來,廣義的說 CAP 也適用於單點單機系統,單機系統是 CP 的。
說到這裏,你們彷佛也發現了,水平擴展的服務應用+數據庫這樣的系統的 CAP 魔咒主要發生在數據庫層。
由於大部分這樣的服務應用都只是承擔了計算的任務(像計算器那樣),自己不須要互相協做,全部寫請求帶來的數據的一致性問題下沉到了數據庫層去解決。
想象一下,若是沒有數據庫層,而是應用本身來保障數據一致性,那麼這樣的應用之間就涉及到狀態的同步和交互了,ZooKeeper 就是這麼一個典型的例子。
水平擴展應用+主從數據庫集羣的CAP分析
上一節咱們討論了多應用實例+單數據庫實例的模式,這種模式是分佈式系統也好,不是分佈式系統也罷,總體是偏 CP 的。
現實中,技術人員們也會很快發現這種架構的不合理性——可用性過低了。
因而以下圖所示的模式成爲了當下大部分中小公司所使用的架構:
從上圖我能夠看到三個數據庫實例中只有一個是主庫,其餘是從庫。
必定程度上,這種架構極大的緩解了」讀可用性」問題,而這樣的架構通常會作讀寫分離來達到更高的」讀可用性」,幸運的是大部分互聯網場景中讀都佔了 80% 以上,因此這樣的架構能獲得較長時間的普遍應用。
寫可用性能夠經過 Keepalived 這種 HA(高可用)框架來保證主庫是活着的,但仔細一想就能夠明白,這種方式並無帶來性能上的可用性提高。
還好,至少系統不會由於某個實例掛了就都不可用了。
可用性勉強達標了,這時候的 CAP 分析以下:
分區容忍性:依舊先看分區容忍性,主從結構的數據庫存在節點之間的通訊,他們之間須要經過心跳來保證只有一個 Master。
然而一旦發生分區,每一個分區會本身選取一個新的 Master,這樣就出現了腦裂,常見的主從數據庫(MySQL,Oracle 等)並無自帶解決腦裂的方案。因此分區容忍性是沒考慮的。
一致性:不考慮分區,因爲任意時刻只有一個主庫,因此一致性是知足的。
可用性:不考慮分區,HA 機制的存在能夠保證可用性,因此可用性顯然也是知足的。
因此這樣的一個系統,咱們認爲它是 AC 的。咱們再深刻研究下,若是發生腦裂產生數據不一致後有一種方式能夠仲裁一致性問題,是否是就能夠知足 P 了呢。
還真有嘗試經過預先設置規則來解決這種多主庫帶來的一致性問題的系統,好比 CouchDB,它經過版本管理來支持多庫寫入,在其仲裁階段會經過 DBA 配置的仲裁規則(也就是合併規則,好比誰的時間戳最晚誰的生效)進行自動仲裁(自動合併),從而保障最終一致性(BASE),自動規則沒法合併的狀況則只能依賴人工決策了。
螞蟻單元化 LDC 架構 CAP 分析
①打敗分區容忍性
在討論螞蟻 LDC 架構的 CAP 以前,咱們再來想一想分區容忍性有啥值得一提的,爲啥不少大名鼎鼎的 BASE(最終一致性)體系系統都選擇損失實時一致性,而不是丟棄分區容忍性呢?
分區的產生通常有兩種狀況:
某臺機器宕機了,
過一下子又重啓了,看起來就像失聯了一段時間,像是網絡不可達同樣。
異地部署狀況下,
異地多活意味着每一地均可能會產生數據寫入,而異地之間偶爾的網絡延時尖刺(網絡延時曲線圖陡增)、網絡故障都會致使小範圍的網絡分區產生。
前文也提到過,若是一個分佈式系統是部署在一個局域網內的(一個物理機房內),那麼我的認爲分區的機率極低,即使有複雜的拓撲,也不多會有在同一個機房裏出現網絡分區的狀況。
而異地這個機率會大大增高,因此螞蟻的三地五中心必須須要思考這樣的問題,分區容忍不能丟!
一樣的狀況還會發生在不一樣 ISP 的機房之間(想象一下你和朋友組隊玩 DOTA,他在電信,你在聯通)。
爲了應對某一時刻某個機房突發的網絡延時尖刺活着間歇性失聯,一個好的分佈式系統必定能處理好這種狀況下的一致性問題。
那麼螞蟻是怎麼解決這個問題的呢?咱們在上文討論過,其實 LDC 機房的各個單元都由兩部分組成:負責業務邏輯計算的應用服務器和負責數據持久化的數據庫。
大部分應用服務器就像一個個計算器,自身是不對寫一致性負責的,這個任務被下沉到了數據庫。因此螞蟻解決分佈式一致性問題的關鍵就在於數據庫!
想必螞蟻的讀者大概猜到下面的討論重點了——OceanBase(下文簡稱OB),中國第一款自主研發的分佈式數據庫,一時間也確實得到了不少光環。
在討論 OB 前,咱們先來想一想 Why not MySQL?
首先,就像 CAP 三角圖中指出的,MySQL 是一款知足 AC 但不知足 P 的分佈式系統。
試想一下,一個 MySQL 主從結構的數據庫集羣,當出現分區時,問題分區內的 Slave 會認爲主已經掛了,因此本身成爲本分區的 Master(腦裂)。
等分區問題恢復後,會產生 2 個主庫的數據,而沒法肯定誰是正確的,也就是分區致使了一致性被破壞。這樣的結果是嚴重的,這也是螞蟻寧願自研 OceanBase 的原動力之一。
那麼如何才能讓分佈式系統具有分區容忍性呢?
按照老慣例,咱們從」可用性分區容忍」和」一致性分區容忍」兩個方面來討論:
可用性分區容忍性保障機制:
可用性分區容忍的關鍵在於別讓一個事務一來全部節點來完成,這個很簡單,別要求全部節點共同同時參與某個事務便可。
一致性分區容忍性保障機制:
老實說,都產生分區了,哪還可能得到實時一致性。
但要保證最終一致性也不簡單,一旦產生分區,如何保證同一時刻只會產生一份提議呢?
換句話說,如何保障仍然只有一個腦呢?下面咱們來看下 PAXOS 算法是如何解決腦裂問題的。
這裏能夠發散下,所謂的「腦」其實就是具有寫能力的系統,「非腦」就是隻具有讀能力的系統,對應了 MySQL 集羣中的從庫。
下面是一段摘自維基百科的 PAXOS 定義:
Paxos is a family of protocols for solving consensus in a network of unreliable processors (that is, processors that may fail).
大體意思就是說,PAXOS 是在一羣不是特別可靠的節點組成的集羣中的一種共識機制。
Paxos 要求任何一個提議,至少有 (N/2)+1 的系統節點承認,才被認爲是可信的,這背後的一個基礎理論是少數服從多數。
想象一下,若是多數節點承認後,整個系統宕機了,重啓後,仍然能夠經過一次投票知道哪一個值是合法的(多數節點保留的那個值)。
這樣的設定也巧妙的解決了分區狀況下的共識問題,由於一旦產生分區,勢必最多隻有一個分區內的節點數量會大於等於 (N/2)+1。
經過這樣的設計就能夠巧妙的避開腦裂,固然 MySQL 集羣的腦裂問題也是能夠經過其餘方法來解決的,好比同時 Ping 一個公共的 IP,成功者繼續爲腦,顯然這就又製造了另一個單點。
若是你瞭解過比特幣或者區塊鏈,你就知道區塊鏈的基礎理論也是 PAXOS。區塊鏈藉助 PAXOS 對最終一致性的貢獻來抵禦惡意篡改。
而本文涉及的分佈式應用系統則是經過 PAXOS 來解決分區容忍性。再說本質一點,一個是抵禦部分節點變壞,一個是防範部分節點失聯。
你們必定據說過這樣的描述:
PAXOS 是惟一能解決分佈式一致性問題的解法。
這句話越是理解愈加以爲詭異,這會讓人覺得 PAXOS 逃離於 CAP 約束了,因此我的更願意理解爲:PAXOS 是惟一一種保障分佈式系統最終一致性的共識算法(所謂共識算法,就是你們都按照這個算法來操做,你們最後的結果必定相同)。
PAXOS 並無逃離 CAP 魔咒,畢竟達成共識是 (N/2)+1 的節點之間的事,剩下的 (N/2)-1 的節點上的數據仍是舊的,這時候仍然是不一致的。
因此 PAXOS 對一致性的貢獻在於通過一次事務後,這個集羣裏已經有部分節點保有了本次事務正確的結果(共識的結果),這個結果隨後會被異步的同步到其餘節點上,從而保證最終一致性。
如下摘自維基百科:
Paxos is a family of protocols for solving consensus in a network of unreliable processors (that is, processors that may fail).Quorums express the safety (or consistency) properties of Paxos by ensuring at least some surviving processor retains knowledge of the results.
另外 PAXOS 不要求對全部節點作實時同步,實質上是考慮到了分區狀況下的可用性,經過減小完成一次事務須要的參與者個數,來保障系統的可用性。
②OceanBase 的 CAP 分析
上文提到過,單元化架構中的成千山萬的應用就像是計算器,自己無 CAP 限制,其 CAP 限制下沉到了其數據庫層,也就是螞蟻自研的分佈式數據庫 OceanBase(本節簡稱 OB)。
在 OB 體系中,每一個數據庫實例都具有讀寫能力,具體是讀是寫能夠動態配置(參考第二部分)。
實際狀況下大部分時候,對於某一類數據(固定用戶號段的數據)任意時刻只有一個單元會負責寫入某個節點,其餘節點要麼是實時庫間同步,要麼是異步數據同步。
OB 也採用了 PAXOS 共識協議。實時庫間同步的節點(包含本身)個數至少須要 (N/2)+1 個,這樣就能夠解決分區容忍性問題。
下面咱們舉個馬老師改英文名的例子來講明 OB 設計的精妙之處:
假設數據庫按照用戶 ID 分庫分表,馬老師的用戶 ID 對應的數據段在 [0-9],開始由單元 A 負責數據寫入。
假如馬老師(用戶 ID 假設爲 000)正在用支付寶 App 修改本身的英文名,馬老師一開始打錯了,打成了 Jason Ma,A 單元收到了這個請求。
這時候發生了分區(好比 A 網絡斷開了),咱們將單元 A 對數據段 [0,9] 的寫入權限轉交給單元 B(更改映射),馬老師此次寫對了,爲 Jack Ma。
而在網絡斷開前請求已經進入了 A,寫權限轉交給單元 B 生效後,A 和 B 同時對 [0,9] 數據段進行寫入馬老師的英文名。
假如這時候都容許寫入的話就會出現不一致,A 單元說我看到馬老師設置了 Jason Ma,B 單元說我看到馬老師設置了 Jack Ma。
然而這種狀況不會發生的,A 提議說我建議把馬老師的英文名設置爲 Jason Ma 時,發現沒人迴應它。
由於出現了分區,其餘節點對它來講都是不可達的,因此這個提議被自動丟棄,A 內心也明白是本身分區了,會有主分區替本身完成寫入任務的。
一樣的,B 提出了將馬老師的英文名改爲 Jack Ma 後,大部分節點都響應了,因此 B 成功將 Jack Ma 寫入了馬老師的帳號記錄。
假如在寫權限轉交給單元 B 後 A 忽然恢復了,也不要緊,兩筆寫請求同時要求得到 (N/2)+1 個節點的事務鎖,經過 no-wait 設計,在 B 得到了鎖以後,其餘爭搶該鎖的事務都會由於失敗而回滾。
下面咱們分析下 OB 的 CAP:
分區容忍性:OB 節點之間是有互相通訊的(須要相互同步數據),因此存在分區問題,OB 經過僅同步到部分節點來保證可用性。這一點就說明 OB 作了分區容錯。
可用性分區容忍性:OB 事務只須要同步到 (N/2)+1 個節點,容許其他的一小半節點分區(宕機、斷網等),只要 (N/2)+1 個節點活着就是可用的。
極端狀況下,好比 5 個節點分紅 3 份(2:2:1),那就確實不可用了,只是這種狀況機率比較低。
一致性分區容忍性:分區狀況下意味着部分節點失聯了,一致性顯然是不知足的。但經過共識算法能夠保證當下只有一個值是合法的,而且最終會經過節點間的同步達到最終一致性。
因此 OB 仍然沒有逃脫 CAP 魔咒,產生分區的時候它變成 AP+最終一致性(C)。總體來講,它是 AP 的,即高可用和分區容忍。
結語
我的感受本文涉及到的知識面確實很多,每一個點單獨展開均可以討論半天。回到咱們緊扣的主旨來看,雙十一海量支付背後技術上大快人心的設計究竟是啥?
我想無非是如下幾點:
基於用戶分庫分表的 RZone 設計。每一個用戶羣獨佔一個單元給整個系統的容量帶來了爆發式增加。
RZone 在網絡分區或災備切換時 OB 的防腦裂設計(PAXOS)。咱們知道 RZone 是單腦的(讀寫都在一個單元對應的庫),而網絡分區或者災備時熱切換過程當中可能會產生多個腦,OB 解決了腦裂狀況下的共識問題(PAXOS 算法)。
基於 CZone 的本地讀設計。這一點保證了很大一部分有着「寫讀時間差」現象的公共數據能被高速本地訪問。
剩下的那一丟丟不能本地訪問只能實時訪問 GZone 的公共配置數據,也興不起什麼風,做不了什麼浪。
好比用戶建立這種 TPS,不會高到哪裏去。再好比對於實時庫存數據,能夠經過「頁面展現查詢走應用層緩存」+「實際下單時再校驗」的方式減小其 GZone 調用量。
而這就是螞蟻 LDC 的 CRG 架構,相信 54.4 萬筆/秒還遠沒到 LDC 的上限,這個數字能夠作到更高。
固然雙 11 海量支付的成功不僅僅是這麼一套設計所決定的,還有預熱削峯等運營+技術的手段,以及成百上千的兄弟姐妹共同奮戰,特此在這向各位雙 11 留守同窗致敬。
感謝你們的閱讀,文中可能存在不足或遺漏之處,歡迎批評指正。
參考文獻:
Practice of Cloud System Administration, The: DevOps and SRE Practices for Web Services, Volume 2. Thomas A. Limoncelli, Strata R. Chalup, Christina J. Hogan.
MySQL 5.7 半同步複製技術
https://www.cnblogs.com/zero-gg/p/9057092.html
BASE 理論分析
https://www.jianshu.com/p/f6157118e54b
Keepalived
https://baike.baidu.com/item/Keepalived/10346758?fr=aladdin
PAXOS
https://en.wikipedia.org/wiki/Paxos_(computer_science)
OceanBase 支撐 2135 億成交額背後的技術原理
https://www.cnblogs.com/antfin/articles/10299396.html
Backup
https://en.wikipedia.org/wiki/Backup