這里是碼農(nóng)充電第一站,回復“666”,獲取一份專屬大禮包來源?|?cnblogs.com/songbao/p/12015641.html一、背景
自2008年雙十一以來,在每年雙十一超大規(guī)模流量的沖擊上,螞蟻金服都會不斷突破現(xiàn)有技術的極限。2010年雙11的支付峰值為2萬筆/分鐘,全天1280萬筆支付,這個數(shù)字到2017雙11時變?yōu)榱?5.6萬筆/秒,全天14.8億筆。在如此之大的支付TPS背后除了削峰等錦上添花的應用級優(yōu)化,最解渴最實質的招數(shù)當數(shù)基于分庫分表的單元化了,螞蟻技術稱之為LDC(邏輯數(shù)據(jù)中心)。
本文不打算討論具體到代碼級的分析,而是嘗試用最簡單的描述來說明其中最大快人心的原理。
我想關心分布式系統(tǒng)設計的人都曾被下面這些問題所困擾過:如果你對這些感興趣,不妨看一場赤裸裸的論述,拒絕使用晦澀難懂的詞匯,直面最本質的邏輯。
LDC(logic data center)是相對于傳統(tǒng)的(Internet Data Center-IDC)提出的,邏輯數(shù)據(jù)中心所表達的中心思想是無論物理結構如何的分布,整個數(shù)據(jù)中心在邏輯上是協(xié)同和統(tǒng)一的。這句話暗含的是強大的體系設計,分布式系統(tǒng)的挑戰(zhàn)就在于整體協(xié)同工作(可用性,分區(qū)容忍性)和統(tǒng)一(一致性)。關注公眾號互聯(lián)網(wǎng)架構師,回復關鍵字2T,獲取最新架構視頻
單元化是大型互聯(lián)網(wǎng)系統(tǒng)的必然選擇趨勢,舉個最最通俗的例子來說明單元化。我們總是說TPS很難提升,確實任何一家互聯(lián)網(wǎng)(比如淘寶、攜程、新浪)它的交易TPS頂多以十萬計量(平均水平),很難往上串了,因為數(shù)據(jù)庫存儲層瓶頸的存在再多水平擴展的服務器都無法繞開這個瓶頸,而從整個互聯(lián)網(wǎng)的視角看,全世界電商的交易TPS可以輕松上億。這個例子帶給我們一些思考:為啥幾家互聯(lián)網(wǎng)的TPS之和可以那么大,服務的用戶數(shù)規(guī)模也極為嚇人,而單個互聯(lián)網(wǎng)的TPS卻很難提升?究其本質,每家互聯(lián)網(wǎng)都是一個獨立的大型單元,他們各自服務自己的用戶互不干擾。這就是單元化的基本特性,任何一家互聯(lián)網(wǎng)公司,其想要成倍的擴大自己系統(tǒng)的服務能力,都必然會走向單元化之路,它的本質是分治,我們把廣大的用戶分為若干部分,同時把系統(tǒng)復制多份,每一份都獨立部署,每一份系統(tǒng)都服務特定的一群用戶,以淘寶舉例,這樣之后,就會有很多個淘寶系統(tǒng)分別為不同的用戶服務,每個淘寶系統(tǒng)都做到十萬TPS的話,N個這樣的系統(tǒng)就可以輕松做到N*十萬的TPS了。LDC實現(xiàn)的關鍵就在于單元化系統(tǒng)架構設計,所以在螞蟻內(nèi)部,LDC和單元化是不分家的,這也是很多同學比較困擾的地方,看似沒啥關系,實則是單元化體系設計成就了LDC。小結:分庫分表解決的最大痛點是數(shù)據(jù)庫單點瓶頸,這個瓶頸的產(chǎn)生是由現(xiàn)代二進制數(shù)據(jù)存儲體系決定的(即I/O速度)。
單元化只是分庫分表后系統(tǒng)部署的一種方式,這種部署模式在災備方面也發(fā)揮了極大的優(yōu)勢。
2.1 系統(tǒng)架構演化史
幾乎任何規(guī)模的互聯(lián)網(wǎng)公司,都有自己的系統(tǒng)架構迭代和更新,大致的演化路徑都大同小異。最早一般為了業(yè)務快速上線,所有功能都會放到一個應用里,系統(tǒng)架構如圖1所示。
?
?這樣的架構顯然是有問題的,單機有著明顯的單點效應,單機的容量和性能都是很局限的,而使用中小型機會帶來大量的浪費。?隨著業(yè)務發(fā)展,這個矛盾逐漸轉變?yōu)橹饕埽虼斯こ處焸儾捎昧艘韵录軜嫛?/span>?
?這是整個公司第一次觸碰到分布式,也就是對某個應用進行了水平擴容,它將多個微機的計算能力團結了起來,可以完勝同等價格的中小型機器。慢慢的,大家發(fā)現(xiàn),應用服務器CPU都很正常了,但是還是有很多慢請求,究其原因,是因為單點數(shù)據(jù)庫帶來了性能瓶頸。于是程序員們決定使用主從結構的數(shù)據(jù)庫集群,如下圖所示。?
?其中大部分讀操作可以直接訪問從庫,從而減輕主庫的壓力。然而這種方式還是無法解決寫瓶頸,寫依舊需要主庫來處理,當業(yè)務量量級再次增高時,寫已經(jīng)變成刻不容緩的待處理瓶頸。這時候,分庫分表方案出現(xiàn)了。
?
分庫分表不僅可以對相同的庫進行拆分,還可以進行對相同的表進行拆分,對表進行拆分的方式叫做水平拆分。不同功能的表放到不同的庫里,一般對應的是垂直拆分(按照業(yè)務功能進行拆分),此時一般還對應了微服務化。這種方法做到極致基本能支撐TPS在萬級甚至更高的訪問量了。
然而隨著相同應用擴展的越多,每個數(shù)據(jù)庫的鏈接數(shù)也巨量增長,這讓數(shù)據(jù)庫本身的資源成為了瓶頸。這個問題產(chǎn)生的本質是全量數(shù)據(jù)無差別的分享了所有的應用資源,比如A用戶的請求在負載均衡的分配下可能分配到任意一個應用服務器上,因而所有應用全部都要鏈接A用戶所在的分庫,數(shù)據(jù)庫連接數(shù)就變成笛卡爾乘積了。在本質點說,這種模式的資源隔離性還不夠徹底。要解決這個問題,就需要把識別用戶分庫的邏輯往上層移動,從數(shù)據(jù)庫層移動到路由網(wǎng)關層。這樣一來,從應用服務器a進來的來自A客戶的所有請求必然落庫到DB-A,因此a也不用鏈接其他的數(shù)據(jù)庫實例了,這樣一個單元化的雛形就誕生了。思考一下:
應用間其實也存在交互(比如A轉賬給B),也就意味著,應用不需要鏈接其他的數(shù)據(jù)庫了,但是還需要鏈接其他應用。如果是常見的RPC框架如dubbo等,使用的是TCP/IP協(xié)議,那么等同于把之前與數(shù)據(jù)庫建立的鏈接,換成與其他應用之間的鏈接了。為啥這樣就消除瓶頸了呢?首先由于合理的設計,應用間的數(shù)據(jù)交互并不巨量,其次應用間的交互可以共享TCP鏈接,比如A->B之間的Socket鏈接可以被A中的多個線程復用,而一般的數(shù)據(jù)庫如MySQL則不行,所以MySQL才需要數(shù)據(jù)庫鏈接池。
?
如上圖所示,但我們把整套系統(tǒng)打包為單元化時,每一類的數(shù)據(jù)從進單元開始就注定在這個單元被消化,由于這種徹底的隔離性,整個單元可以輕松的部署到任意機房而依然能保證邏輯上的統(tǒng)一。
下圖為一個三地五機房的部署方式。
?
螞蟻支付寶應該是國內(nèi)最大的支付工具,其在雙十一等活動日當日的支付TPS可達幾十萬級,未來這個數(shù)字可能會更大,這決定了螞蟻單元化架構從容量要求上看必然從單機房走向多機房。另一方面,異地災備也決定了這些IDC機房必須是異地部署的。整體上支付寶也采用了三地五中心(IDC機房)來保障系統(tǒng)的可用性,跟2.1中描述的有所不同的是,支付寶將單元分成了三類(也稱CRG架構):- RZone(Region Zone):直譯可能有點反而不好理解。實際上就是所有可以分庫分表的業(yè)務系統(tǒng)整體部署的最小單元。每個RZone連上數(shù)據(jù)庫就可以撐起一片天空,把業(yè)務跑的溜溜的。
- GZone(Global Zone):全局單元,意味著全局只有一份。部署了不可拆分的數(shù)據(jù)和服務,比如系統(tǒng)配置等。實際情況下,GZone異地也會部署,不過僅是用于災備,同一時刻,只有一地GZone進行全局服務。GZone一般被RZone依賴,提供的大部分是讀取服務。
- CZone(City Zone):顧名思義,這是以城市為單位部署的單元。同樣部署了不可拆分的數(shù)據(jù)和服務,比如用戶賬號服務,客戶信息服務等。理論上CZone會被RZone以比訪問GZone高很多的頻率進行訪問。CZone是基于特定的GZone場景進行優(yōu)化的一種單元,它把GZone中有些有著”寫讀時間差現(xiàn)象”的數(shù)據(jù)和服務進行了的單獨部署,這樣RZone只需要訪問本地的CZone即可,而不是訪問異地的GZone。
“寫讀時間差現(xiàn)象”是螞蟻架構師們根據(jù)實踐統(tǒng)計總結的,他們發(fā)現(xiàn)大部分情況下,一個數(shù)據(jù)被寫入后,都會過足夠長的時間后才會被訪問。生活中這種例子很常見,我們辦完銀行卡后可能很久才會存第一筆錢;我們創(chuàng)建微博賬號后,可能想半天才會發(fā)微博;我們下載創(chuàng)建淘寶賬號后,可能得瀏覽好幾分鐘才會下單買東西。當然了這些例子中的時間差遠遠超過了系統(tǒng)同步時間。一般來說異地的延時在100ms以內(nèi),所以只要滿足某地CZone寫入數(shù)據(jù)后100ms以后才用這個數(shù)據(jù),這樣的數(shù)據(jù)和服務就適合放到CZone中。其實其背后對應的是不同性質的數(shù)據(jù),而服務不過是對數(shù)據(jù)的操作集。下面我們來根據(jù)數(shù)據(jù)性質的不同來解釋支付寶的CRG架構。當下幾乎所有互聯(lián)網(wǎng)公司的分庫分表規(guī)則都是根據(jù)用戶ID來制定的,而圍繞用戶來看整個系統(tǒng)的數(shù)據(jù)可以分為以下兩類:- 用戶流水型數(shù)據(jù):典型的有用戶的訂單、用戶發(fā)的評論、用戶的行為記錄等。這些數(shù)據(jù)都是用戶行為產(chǎn)生的流水型數(shù)據(jù),具備天然的用戶隔離性,比如A用戶的App上絕對看不到B用戶的訂單列表。所以此類數(shù)據(jù)非常適合分庫分表后獨立部署服務。
- 用戶間共享型數(shù)據(jù):這種類型的數(shù)據(jù)又分兩類。一類共享型數(shù)據(jù)是像賬號、個人博客等可能會被所有用戶請求訪問的用戶數(shù)據(jù),比如A向B轉賬,A給B發(fā)消息,這時候需要確認B賬號是否存在;又比如A想看B的個人博客之類的。另外一類是用戶無關型數(shù)據(jù),像商品、系統(tǒng)配置(匯率、優(yōu)惠政策)、財務統(tǒng)計等這些非用戶緯度的數(shù)據(jù),很難說跟具體的某一類用戶掛鉤,可能涉及到所有用戶。比如商品,假設按商品所在地來存放商品數(shù)據(jù)(這需要雙維度分庫分表),那么上海的用戶仍然需要訪問杭州的商品,這就又構成跨地跨zone訪問了,還是達不到單元化的理想狀態(tài),而且雙維度分庫分表會給整個LDC運維帶來復雜度提升。
注:網(wǎng)上和支付寶內(nèi)部有另外一些分法,比如流水型和狀態(tài)性,有時候還會分為三類:流水型、狀態(tài)型和配置型。個人覺得這些分法雖然嘗試去更高層次的抽象數(shù)據(jù)分類,但實際上邊界很模糊,適得其反。
直觀的類比,我們可以很輕易的將上述兩類數(shù)據(jù)對應的服務劃分為RZone和GZone,RZone包含的就是分庫分表后負責固定客戶群體的服務,GZone則包含了用戶間共享的公共數(shù)據(jù)對應的服務。到這里為止,一切都很完美,這也是主流的單元化話題了。對比支付寶的CRG架構,我們一眼就發(fā)現(xiàn)少了C(City Zone),CZone確實是螞蟻在單元化實踐領域的一個創(chuàng)新點。再來分析下GZone,GZone之所以只能單地部署,是因為其數(shù)據(jù)要求被所有用戶共享,無法分庫分表,而多地部署會帶來由異地延時引起的不一致,比如實時風控系統(tǒng),如果多地部署,某個RZone直接讀取本地的話,很容易讀取到舊的風控狀態(tài),這是很危險的。這時螞蟻架構師們問了自己一個問題——難道所有數(shù)據(jù)受不了延時么?這個問題像是打開了新世界的大門,通過對RZone已有業(yè)務的分析,架構師們發(fā)現(xiàn)80%甚至更高的場景下,數(shù)據(jù)更新后都不要求立馬被讀取到。也就是上文提到的”寫讀時間差現(xiàn)象”,那么這就好辦了,對于這類數(shù)據(jù),我們允許每個地區(qū)的RZone服務直接訪問本地,為了給這些RZone提供這些數(shù)據(jù)的本地訪問能力,螞蟻架構師設計出了CZone。在CZone的場景下,寫請求一般從GZone寫入公共數(shù)據(jù)所在庫,然后同步到整個OB集群,然后由CZone提供讀取服務。比如支付寶的會員服務就是如此。即便架構師們設計了完美的CRG,但即便在螞蟻的實際應用中,各個系統(tǒng)仍然存在不合理的CRG分類,尤其是CG不分的現(xiàn)象很常見。
三、支付寶單元化的異步多活和災備
3.1、流量挑撥技術探秘簡介
單元化后,異地多活只是多地部署而已。比如上海的兩個單元為ID范圍為[0019],[4059]的用戶服務,而杭州的兩個單元為ID為[20~39]和[60,79]的用戶服務,這樣上海和杭州就是異地雙活的。支付寶對單元化的基本要求是每個單元都具備服務所有用戶的能力,即——具體的那個單元服務哪些用戶是可以動態(tài)配置的。所以異地雙活的這些單元還充當了彼此的備份。發(fā)現(xiàn)工作中冷備熱備已經(jīng)被用的很亂了。最早冷備是指數(shù)據(jù)庫在備份數(shù)據(jù)時需要關閉后進行備份(也叫離線備份),防止數(shù)據(jù)備份過程中又修改了,不需要關閉即在運行過程中進行數(shù)據(jù)備份的方式叫做熱備(也叫在線備份)[7]。也不知道從哪一天開始,冷備在主備系統(tǒng)里代表了這臺備用機器是關閉狀態(tài)的,只有主服務器掛了之后,備服務器才會被啟動;而相同的熱備變成了備服務器也是啟動的,只是沒有流量而已,一旦主服務器掛了之后,流量自動打到備服務器上。本文不打算用第二種理解,因為感覺有點野、、、為了做到每個單元訪問哪些用戶變成可配置,支付寶要求單元化管理系統(tǒng)具備流量到單元的可配置以及單元到DB的可配置能力,如下圖所示:?
?其中spanner是螞蟻基于nginx自研的反向代理網(wǎng)關,也很好理解,有些請求我們希望在反向代理層就被轉發(fā)至其他IDC的spanner而無需進入后端服務,如圖箭頭2所示。那么對于應該在本IDC處理的請求,就直接映射到對應的RZ即可,如圖箭頭1。進入后端服務后,理論上如果請求只是讀取用戶流水型數(shù)據(jù),那么一般不會再進行路由了。然而,對于有些場景來說,A用戶的一個請求可能關聯(lián)了對B用戶數(shù)據(jù)的訪問,比如A轉賬給B,A扣完錢后要調(diào)用賬務系統(tǒng)去增加B的余額。這時候就涉及到再次的路由,同樣有兩個結果:跳轉到其他IDC(如圖箭頭3)或是跳轉到本IDC的其他RZone(如圖箭頭4)。RZone到DB數(shù)據(jù)分區(qū)的訪問這是事先配置好的,上圖中RZ和DB數(shù)據(jù)分區(qū)的關系為:RZ0* --> a
RZ1* --> b
RZ2* --> c
RZ3* --> d
下面我們舉個例子來說明整個流量挑撥的過程,假設C用戶所屬的數(shù)據(jù)分區(qū)是c,而C用戶在杭州訪問了cashier.alipay.com(隨便編的)。(1)目前支付寶默認會按照地域來路由流量,具體的實現(xiàn)承載者是自研的GLSB(Global Server Load Balancing)[https://developer.alipay.com/article/1889],它會根據(jù)請求者的IP,自動將cashier.alipay.com解析為杭州IDC的IP地址(或者跳轉到IDC所在的域名)。大家自己高過網(wǎng)站的化應該知道大部分DNS服務商的地址都是靠人去配置的,GLSB屬于動態(tài)配置域名的系統(tǒng),網(wǎng)上也有比較火的類似產(chǎn)品,比如花生殼之類(建過私站的同學應該很熟悉)的。(2)好了,到此為止,用戶的請求來到了IDC-1的Spanner集群服務器上,Spanner從內(nèi)存中讀取到了路由配置,知道了這個請求的主體用戶C所屬的RZ3*不再本IDC,于是直接轉到了IDC-2進行處理。(3)進入IDC-2之后,根據(jù)流量配比規(guī)則,該請求被分配到了RZ3B進行處理。(4)RZ3B得到請求后對數(shù)據(jù)分區(qū)c進行訪問。大家應該發(fā)現(xiàn)問題所在了,如果再來一個這樣的請求,豈不是每次都要跨地域進行調(diào)用和返回體傳遞?確實是存在這樣的問題的,對于這種問題,支付寶架構師們決定繼續(xù)把決策邏輯往用戶終端推移。比如,每個IDC機房都會有自己的域名(真實情況可能不是這樣命名的): IDC-1對應cashieridc-1.alipay.com IDC-2對應cashieridc-2.alipay.com 那么請求從IDC-1涮過一遍返回時會將前端請求跳轉到cashieridc-2.alipay.com去(如果是APP,只需要替換rest調(diào)用的接口域名),后面所有用戶的行為都會在這個域名上發(fā)生,就避免了走一遍IDC-1帶來的延時。3.2、支付寶災備機制
流量挑撥是災備切換的基礎和前提條件,發(fā)生災難后的通用方法就是把陷入災難的單元的流量重新打到正常的單元上去,這個流量切換的過程俗稱切流。支付寶LDC架構下的災備有三個層次:3.2.1、同機房單元間災備
災難發(fā)生可能性相對最高(但其實也很?。?。對LDC來說,最小的災難就是某個單元由于一些原因(局部插座斷開、線路老化、人為操作失誤)宕機了。從3.1節(jié)里的圖中可以看到每組RZ都有A,B兩個單元,這就是用來做同機房災備的,并且AB之間也是雙活雙備的,正常情況下AB兩個單元共同分擔所有的請求,一旦A單元掛了,B單元將自動承擔A單元的流量份額。這個災備方案是默認的。3.2.2、同城機房間災備
災難發(fā)生可能性相對更小。這種災難發(fā)生的原因一般是機房電線網(wǎng)線被挖斷,或者機房維護人員操作失誤導致的。在這種情況下,就需要人工的制定流量挑撥(切流)方案了。下面我們舉例說明這個過程,如下圖所示為上海的兩個IDC機房。?
整個切流配置過程分兩步,首先需要將陷入災難的機房中RZone對應的數(shù)據(jù)分區(qū)的訪問權配置進行修改;假設我們的方案是由IDC-2機房的RZ2和RZ3分別接管IDC-1中的RZ0和RZ1。那么首先要做的是把數(shù)據(jù)分區(qū)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%)
那么按照災備方案的要求,這個映射配置將變?yōu)椋?/span>[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中,期間部分已經(jīng)向IDC-1發(fā)起請求的用戶會收到失敗并重試的提示。?實際情況中,整個過程并不是災難發(fā)生后再去做的,整個切換的流程會以預案配置的形式事先準備好,推送給每個流量挑撥客戶端(集成到了所有的服務和spanner中)。這里可以思考下,為何先切數(shù)據(jù)庫映射,再切流量呢?這是因為如果先切流量,意味著大量注定失敗的請求會被打到新的正常單元上去,從而影響系統(tǒng)的穩(wěn)定性(數(shù)據(jù)庫還沒準備好)。
3.2.3、異地機房間災備
這個基本上跟同城機房間災備一致(這也是單元化的優(yōu)點),不再贅述。4.1、回顧CAP
4.1.1 CAP的定義
CAP原則是指任意一個分布式系統(tǒng),同時最多只能滿足其中的兩項,而無法同時滿足三項。所謂的分布式系統(tǒng),說白了就是一件事一個人做的,現(xiàn)在分給好幾個人一起干。我們先簡單回顧下CAP各個維度的含義[1]:- Consistency(一致性),這個理解起來很簡單,就是每時每刻每個節(jié)點上的同一份數(shù)據(jù)都是一致的。這就要求任何更新都是原子的,即要么全部成功,要么全部失敗。想象一下使用分布式事務來保證所有系統(tǒng)的原子性是多么低效的一個操作。
- Availability(可用性),這個可用性看起來很容易理解,但真正說清楚的不多。我更愿意把可用性解釋為:任意時刻系統(tǒng)都可以提供讀寫服務。那么舉個例子,當我們用事務將所有節(jié)點鎖住來進行某種寫操作時,如果某個節(jié)點發(fā)生不可用的情況,會讓整個系統(tǒng)不可用。對于分片式的NoSQL中間件集群(Redis,Memcached)來說,一旦一個分片歇菜了,整個系統(tǒng)的數(shù)據(jù)也就不完整了,讀取宕機分片的數(shù)據(jù)就會沒響應,也就是不可用了。需要說明一點,哪些選擇CP的分布式系統(tǒng),并不是代表可用性就完全沒有了,只是可用性沒有保障了。為了增加可用性保障,這類中間件往往都提供了”分片集群+復制集”的方案。
- Partition tolerance(分區(qū)容忍性),這個可能也是很多文章都沒說清楚的。P并不是像CA一樣是一個獨立的性質,它依托于CA來進行討論。參考文獻[1]中解釋道:”除非整個網(wǎng)絡癱瘓,否則任何時刻系統(tǒng)都能正常工作”,言下之意是小范圍的網(wǎng)絡癱瘓,節(jié)點宕機,都不會影響整個系統(tǒng)的CA。我感覺這個解釋聽著還是有點懵逼,所以個人更愿意解釋為”當節(jié)點之間網(wǎng)絡不通時(出現(xiàn)網(wǎng)絡分區(qū)),可用性和一致性仍然能得到保障”。從個人角度理解,分區(qū)容忍性又分為”可用性分區(qū)容忍性”和”一致性分區(qū)容忍性”?!背霈F(xiàn)分區(qū)時會不會影響可用性”的關鍵在于”需不需要所有節(jié)點互相溝通協(xié)作來完成一次事務”,不需要的話是鐵定不影響可用性的,慶幸的是應該不太會有分布式系統(tǒng)會被設計成完成一次事務需要所有節(jié)點聯(lián)動,一定要舉個例子的話,全同步復制技術下的Mysql是一個典型案例[2]。”出現(xiàn)分區(qū)時會不會影響一致性”的關鍵則在于出現(xiàn)腦裂時有沒有保證一致性的方案,這對主從同步型數(shù)據(jù)庫(MySQL、SQL Server)是致命的,一旦網(wǎng)絡出現(xiàn)分區(qū),產(chǎn)生腦裂,系統(tǒng)會出現(xiàn)一份數(shù)據(jù)兩個值的狀態(tài),誰都不覺得自己是錯的。需要說明的是,正常來說同一局域網(wǎng)內(nèi),網(wǎng)絡分區(qū)的概率非常低,這也是為啥我們最熟悉的數(shù)據(jù)庫(MySQL、SQL Server等)也是不考慮P的原因。
還有個需要說明的地方,其實分布式系統(tǒng)很難滿足CAP的前提條件是這個系統(tǒng)一定是有讀有寫的,如果只考慮讀,那么CAP很容易都滿足,比如一個計算器服務,接受表達式請求,返回計算結果,搞成水平擴展的分布式,顯然這樣的系統(tǒng)沒有一致性問題,網(wǎng)絡分區(qū)也不怕,可用性也是很穩(wěn)的,所以可以滿足CAP。4.1.2 CAP分析方法
先說下CA和P的關系,如果不考慮P的話,系統(tǒng)是可以輕松實現(xiàn)CA的。而P并不是一個單獨的性質,它代表的是目標分布式系統(tǒng)有沒有對網(wǎng)絡分區(qū)的情況做容錯處理。如果做了處理,就一定是帶有P的,接下來再考慮分區(qū)情況下到底選擇了A還是C。所以分析CAP,建議先確定有沒有對分區(qū)情況做容錯處理。?以下是個人總結的分析一個分布式系統(tǒng)CAP滿足情況的一般方法:if(?不存在分區(qū)的可能性?||?分區(qū)后不影響可用性或一致性?||?有影響但考慮了分區(qū)情況-P){
????if(可用性分區(qū)容忍性-A?under?P))
??????return?"AP";
????else?if(一致性分區(qū)容忍性-C?under?P)
??????return?"CP";
}
else?{??//分區(qū)有影響但沒考慮分區(qū)情況下的容錯
?????if(具備可用性-A?&&?具備一致性-C){
?????????return?AC;
?????}
}
這里說明下,如果考慮了分區(qū)容忍性,就不需要考慮不分區(qū)情況下的可用性和一致性了(大多是滿足的)。4.2 水平擴展應用+單數(shù)據(jù)庫實例的CAP分析
讓我們再來回顧下分布式應用系統(tǒng)的來由,早年每個應用都是單體的,跑在一個服務器上,服務器一掛,服務就不可用了。另外一方面,單體應用由于業(yè)務功能復雜,對機器的要求也逐漸變高,普通的微機無法滿足這種性能和容量的要求。所以要拆!還在IBM大賣小型商用機的年代,阿里巴巴就提出要以分布式微機替代小型機。所以我們發(fā)現(xiàn),分布式系統(tǒng)解決的最大的痛點,就是單體單機系統(tǒng)的可用性問題。要想高可用,必須分布式。?一家互聯(lián)網(wǎng)公司的發(fā)展之路上,第一次與分布式相遇應該都是在單體應用的水平擴展上。也就是同一個應用啟動了多個實例,連接著相同的數(shù)據(jù)庫(為了簡化問題,先不考慮數(shù)據(jù)庫是否單點),如下圖所示。
這樣的系統(tǒng)天然具有的就是AP(可用性和分區(qū)容忍性),一方面解決了單點導致的低可用性問題,另一方面無論這些水平擴展的機器間網(wǎng)絡是否出現(xiàn)分區(qū),這些服務器都可以各自提供服務,因為他們之間不需要進行溝通。然而,這樣的系統(tǒng)是沒有一致性可言的,想象一下每個實例都可以往數(shù)據(jù)庫insert和update(注意這里還沒討論到事務),那還不亂了套。于是我們轉向了讓DB去做這個事,這時候”數(shù)據(jù)庫事務”就被用上了。用大部分公司會選擇的Mysql來舉例,用了事務之后會發(fā)現(xiàn)數(shù)據(jù)庫又變成了單點和瓶頸。單點就像單機一樣(本例子中不考慮從庫模式),理論上就不叫分布式了,如果一定要分析其CAP的話,根據(jù)4.1.2的步驟分析過程應該是這樣的:- 分區(qū)容忍性:先看有沒有考慮分區(qū)容忍性,或者分區(qū)后是否會有影響。單臺MySQL無法構成分區(qū),要么整個系統(tǒng)掛了,要么就活著。
- 可用性分區(qū)容忍性:分區(qū)情況下,假設恰好是該節(jié)點掛了,系統(tǒng)也就不可用了,所以可用性分區(qū)容忍性不滿足。
- 一致性分區(qū)容忍性:分區(qū)情況下,只要可用,單點單機的最大好處就是一致性可以得到保障。
因此這樣的一個系統(tǒng),個人認為只是滿足了CP。A有但不出色,從這點可以看出,CAP并不是非黑即白的。包括常說的BASE[3](最終一致性)方案,其實只是C不出色,但最終也是達到一致性的,BASE在一致性上選擇了退讓。
關于分布式應用+單點數(shù)據(jù)庫的模式算不算純正的分布式系統(tǒng),這個可能每個人看法有點差異,上述只是我個人的一種理解,是不是分布式系統(tǒng)不重要,重要的是分析過程。其實我們討論分布式,就是希望系統(tǒng)的可用性是多個系統(tǒng)多活的,一個掛了另外的也能頂上,顯然單機單點的系統(tǒng)不具備這樣的高可用特性。所以在我看來,廣義的說CAP也適用于單點單機系統(tǒng),單機系統(tǒng)是CP的。說到這里,大家似乎也發(fā)現(xiàn)了,水平擴展的服務應用+數(shù)據(jù)庫這樣的系統(tǒng)的CAP魔咒主要發(fā)生在數(shù)據(jù)庫層,因為大部分這樣的服務應用都只是承擔了計算的任務(像計算器那樣),本身不需要互相協(xié)作,所有寫請求帶來的數(shù)據(jù)的一致性問題下沉到了數(shù)據(jù)庫層去解決。想象一下,如果沒有數(shù)據(jù)庫層,而是應用自己來保障數(shù)據(jù)一致性,那么這樣的應用之間就涉及到狀態(tài)的同步和交互了,Zookeeper就是這么一個典型的例子。4.3 水平擴展應用+主從數(shù)據(jù)庫集群的CAP分析
上一節(jié)我們討論了多應用實例+單數(shù)據(jù)庫實例的模式,這種模式是分布式系統(tǒng)也好,不是分布式系統(tǒng)也罷,整體是偏CP的?,F(xiàn)實中,技術人員們也會很快發(fā)現(xiàn)這種架構的不合理性——可用性太低了。于是如下圖所示的模式成為了當下大部分中小公司所使用的架構:
從上圖我可以看到三個數(shù)據(jù)庫實例中只有一個是主庫,其他是從庫。一定程度上,這種架構極大的緩解了”讀可用性”問題,而這樣的架構一般會做讀寫分離來達到更高的”讀可用性”,幸運的是大部分互聯(lián)網(wǎng)場景中讀都占了80%以上,所以這樣的架構能得到較長時間的廣泛應用?!睂懣捎眯浴笨梢酝ㄟ^keepalived[4]這種HA(高可用)框架來保證主庫是活著的,但仔細一想就可以明白,這種方式并沒有帶來性能上的可用性提升。還好,至少系統(tǒng)不會因為某個實例掛了就都不可用了??捎眯悦銖娺_標了,這時候的CAP分析如下:- 分區(qū)容忍性:依舊先看分區(qū)容忍性,主從結構的數(shù)據(jù)庫存在節(jié)點之間的通信,他們之間需要通過心跳來保證只有一個Master。然而一旦發(fā)生分區(qū),每個分區(qū)會自己選取一個新的Master,這樣就出現(xiàn)了腦裂,常見的主從數(shù)據(jù)庫(MySQL,Oracle等)并沒有自帶解決腦裂的方案。所以分區(qū)容忍性是沒考慮的。
- 一致性:不考慮分區(qū),由于任意時刻只有一個主庫,所以一致性是滿足的。
- 可用性:不考慮分區(qū),HA機制的存在可以保證可用性,所以可用性顯然也是滿足的。
所以這樣的一個系統(tǒng),我們認為它是AC的。我們再深入研究下,如果發(fā)生腦裂產(chǎn)生數(shù)據(jù)不一致后有一種方式可以仲裁一致性問題,是不是就可以滿足P了呢。還真有嘗試通過預先設置規(guī)則來解決這種多主庫帶來的一致性問題的系統(tǒng),比如CouchDB,它通過版本管理來支持多庫寫入,在其仲裁階段會通過DBA配置的仲裁規(guī)則(也就是合并規(guī)則,比如誰的時間戳最晚誰的生效)進行自動仲裁(自動合并),從而保障最終一致性(BASE),自動規(guī)則無法合并的情況則只能依賴人工決策了。4.4 螞蟻單元化LDC架構CAP分析
4.4.1 戰(zhàn)勝分區(qū)容忍性
在討論螞蟻LDC架構的CAP之前,我們再來想想分區(qū)容忍性有啥值得一提的,為啥很多大名鼎鼎的BASE(最終一致性)體系系統(tǒng)都選擇損失實時一致性,而不是丟棄分區(qū)容忍性呢?- 某臺機器宕機了,過一會兒又重啟了,看起來就像失聯(lián)了一段時間,像是網(wǎng)絡不可達一樣。
- 異地部署情況下,異地多活意味著每一地都可能會產(chǎn)生數(shù)據(jù)寫入,而異地之間偶爾的網(wǎng)絡延時尖刺(網(wǎng)絡延時曲線圖陡增)、網(wǎng)絡故障都會導致小范圍的網(wǎng)絡分區(qū)產(chǎn)生。前文也提到過,如果一個分布式系統(tǒng)是部署在一個局域網(wǎng)內(nèi)的(一個物理機房內(nèi)),那么個人認為分區(qū)的概率極低,即便有復雜的拓撲,也很少會有在同一個機房里出現(xiàn)網(wǎng)絡分區(qū)的情況。而異地這個概率會大大增高,所以螞蟻的三地五中心必須需要思考這樣的問題,分區(qū)容忍不能丟!同樣的情況還會發(fā)生在不同ISP的機房之間(想象一下你和朋友組隊玩DOTA,他在電信,你在聯(lián)通)。為了應對某一時刻某個機房突發(fā)的網(wǎng)絡延時尖刺活著間歇性失聯(lián),一個好的分布式系統(tǒng)一定能處理好這種情況下的一致性問題。
那么螞蟻是怎么解決這個問題的呢?我們在4.2的備注部分討論過,其實LDC機房的各個單元都由兩部分組成:負責業(yè)務邏輯計算的應用服務器和負責數(shù)據(jù)持久化的數(shù)據(jù)庫。大部分應用服務器就像一個個計算器,自身是不對寫一致性負責的,這個任務被下沉到了數(shù)據(jù)庫。所以螞蟻解決分布式一致性問題的關鍵就在于數(shù)據(jù)庫!想必螞蟻的讀者大概猜到下面的討論重點了——OceanBase(下文簡稱OB),中國第一款自主研發(fā)的分布式數(shù)據(jù)庫,一時間也確實獲得了很多光環(huán)。在討論OB前,我們先來想想Why not MySQL?首先,就像CAP三角圖中指出的,MySQL是一款滿足AC但不滿足P的分布式系統(tǒng)。試想一下,一個MySQL主從結構的數(shù)據(jù)庫集群,當出現(xiàn)分區(qū)時,問題分區(qū)內(nèi)的Slave會認為主已經(jīng)掛了,所以自己成為本分區(qū)的master(腦裂),等分區(qū)問題恢復后,會產(chǎn)生2個主庫的數(shù)據(jù),而無法確定誰是正確的,也就是分區(qū)導致了一致性被破壞。這樣的結果是嚴重的,這也是螞蟻寧愿自研OceanBase的原動力之一。那么如何才能讓分布式系統(tǒng)具備分區(qū)容忍性呢?按照老慣例,我們從”可用性分區(qū)容忍”和”一致性分區(qū)容忍”兩個方面來討論。可用性分區(qū)容忍的關鍵在于別讓一個事務以來所有節(jié)點來完成,這個很簡單,別要求所有節(jié)點共同同時參與某個事務即可。老實說,都產(chǎn)生分區(qū)了,哪還可能獲得實時一致性。但要保證最終一致性也不簡單,一旦產(chǎn)生分區(qū),如何保證同一時刻只會產(chǎn)生一份提議呢?換句話說,如何保障仍然只有一個腦呢?下面我們來看下PAXOS算法是如何解決腦裂問題的。這里可以發(fā)散下,所謂的”腦”其實就是具備寫能力的系統(tǒng),”非腦”就是只具備讀能力的系統(tǒng),對應了MySQL集群中的從庫。Paxos is a family of protocols for solving consensus in a network of unreliable processors (that is, processors that may fail).大致意思就是說,PAXOS是在一群不是特別可靠的節(jié)點組成的集群中的一種共識機制。Paxos要求任何一個提議,至少有(N/2)+1的系統(tǒng)節(jié)點認可,才被認為是可信的,這背后的一個基礎理論是少數(shù)服從多數(shù)。想象一下,如果多數(shù)節(jié)點認可后,整個系統(tǒng)宕機了,重啟后,仍然可以通過一次投票知道哪個值是合法的(多數(shù)節(jié)點保留的那個值)。這樣的設定也巧妙的解決了分區(qū)情況下的共識問題,因為一旦產(chǎn)生分區(qū),勢必最多只有一個分區(qū)內(nèi)的節(jié)點數(shù)量會大于等于(N/2)+1。通過這樣的設計就可以巧妙的避開腦裂,當然MySQL集群的腦裂問題也是可以通過其他方法來解決的,比如同時Ping一個公共的IP,成功者繼續(xù)為腦,顯然這就又制造了另外一個單點。如果你了解過比特幣或者區(qū)塊鏈,你就知道區(qū)塊鏈的基礎理論也是PAXOS。區(qū)塊鏈借助PAXOS對最終一致性的貢獻來抵御惡意篡改。而本文涉及的分布式應用系統(tǒng)則是通過PAXOS來解決分區(qū)容忍性。再說本質一點,一個是抵御部分節(jié)點變壞,一個是防范部分節(jié)點失聯(lián)。
大家一聽說過這樣的描述——PAXOS是唯一能解決分布式一致性問題的解法。這句話越是理解越發(fā)覺得詭異,這會讓人以為PAXOS逃離于CAP約束了,所以個人更愿意理解為——PAXOS是唯一一種保障分布式系統(tǒng)最終一致性的共識算法(所謂共識算法,就是大家都按照這個算法來操作,大家最后的結果一定相同)。PAXOS并沒有逃離CAP魔咒,畢竟達成共識是(N/2)+1的節(jié)點之間的事,剩下的(N/2)-1的節(jié)點上的數(shù)據(jù)還是舊的,這時候仍然是不一致的,所以PAXOS對一致性的貢獻在于經(jīng)過一次事務后,這個集群里已經(jīng)有部分節(jié)點保有了本次事務正確的結果(共識的結果),這個結果隨后會被異步的同步到其他節(jié)點上,從而保證最終一致性。以下摘自維基百科[5]:Quorums express the safety (or consistency) properties of Paxos by ensuring at least some surviving processor retains knowledge of the results.另外PAXOS不要求對所有節(jié)點做實時同步,實質上是考慮到了分區(qū)情況下的可用性,通過減少完成一次事務需要的參與者個數(shù),來保障系統(tǒng)的可用性。4.4.2 OceanBase的CAP分析
上文提到過,單元化架構中的成千山萬的應用就像是計算器,本身無CAP限制,其CAP限制下沉到了其數(shù)據(jù)庫層,也就是螞蟻自研的分布式數(shù)據(jù)庫OceanBase(本節(jié)簡稱OB)[6]。在OB體系中,每個數(shù)據(jù)庫實例都具備讀寫能力,具體是讀是寫可以動態(tài)配置(參考2.2部分)。實際情況下大部分時候,對于某一類數(shù)據(jù)(固定用戶號段的數(shù)據(jù))任意時刻只有一個單元會負責寫入某個節(jié)點,其他節(jié)點要么是實時庫間同步,要么是異步數(shù)據(jù)同步。OB也采用了PAXOS共識協(xié)議。實時庫間同步的節(jié)點(包含自己)個數(shù)至少需要(N/2)+1個,這樣就可以解決分區(qū)容忍性問題。下面我們舉個馬老師改英文名的例子來說明OB設計的精妙之處。假設數(shù)據(jù)庫按照用戶ID分庫分表,馬老師的用戶ID對應的數(shù)據(jù)段在[0-9],開始由單元A負責數(shù)據(jù)寫入,假如馬老師(用戶ID假設為000)正在用支付寶APP修改自己的英文名,馬老師一開始打錯了,打成了Jason Ma,A單元收到了這個請求。這時候發(fā)生了分區(qū)(比如A網(wǎng)絡斷開了),我們將單元A對數(shù)據(jù)段[0,9]的寫入權限轉交給單元B(更改映射),馬老師這次寫對了,為Jack Ma。而在網(wǎng)絡斷開前請求已經(jīng)進入了A,寫權限轉交給單元B生效后,A和B同時對[0,9]數(shù)據(jù)段進行寫入馬老師的英文名。假如這時候都允許寫入的話就會出現(xiàn)不一致,A單元說我看到馬老師設置了Jason Ma,B單元說我看到馬老師設置了Jack Ma。然而這種情況不會發(fā)生的,A提議說我建議把馬老師的英文名設置為Jason Ma時,發(fā)現(xiàn)沒人回應它,因為出現(xiàn)了分區(qū),其他節(jié)點對它來說都是不可達的,所以這個提議被自動丟棄,A心里也明白是自己分區(qū)了,會有主分區(qū)替自己完成寫入任務的。同樣的,B提出了將馬老師的英文名改成Jack Ma后,大部分節(jié)點都響應了,所以B成功將Jack Ma寫入了馬老師的賬號記錄。假如在寫權限轉交給單元B后A突然恢復了,也沒關系,兩筆寫請求同時要求獲得(N/2)+1個節(jié)點的事務鎖,通過no-wait設計,在B獲得了鎖之后,其他掙強該鎖的事務都會因為失敗而回滾。關注公眾號互聯(lián)網(wǎng)架構師,回復關鍵字2T,獲取最新架構視頻分區(qū)容忍性:OB節(jié)點之間是有互相通信的(需要相互同步數(shù)據(jù)),所以存在分區(qū)問題,OB通過僅同步到部分節(jié)點來保證可用性。這一點就說明OB做了分區(qū)容錯。可用性分區(qū)容忍性:OB事務只需要同步到(N/2)+1個節(jié)點,允許其余的一小半節(jié)點分區(qū)(宕機、斷網(wǎng)等),只要(N/2)+1個節(jié)點活著就是可用的。極端情況下,比如5個節(jié)點分成3份(2:2:1),那就確實不可用了,只是這種情況概率比較低。一致性分區(qū)容忍性:分區(qū)情況下意味著部分節(jié)點失聯(lián)了,一致性顯然是不滿足的。但通過共識算法可以保證當下只有一個值是合法的,并且最終會通過節(jié)點間的同步達到最終一致性。
所以OB仍然沒有逃脫CAP魔咒,產(chǎn)生分區(qū)的時候它變成AP+最終一致性(C)。整體來說,它是AP的,即高可用和分區(qū)容忍。
個人感覺本文涉及到的知識面確實不少,每個點單獨展開都可以討論半天?;氐轿覀兙o扣的主旨來看,雙十一海量支付背后技術上大快人心的設計到底是啥?我想無非是以下幾點:
基于用戶分庫分表的RZone設計。每個用戶群獨占一個單元給整個系統(tǒng)的容量帶來了爆發(fā)式增長。- RZone在網(wǎng)絡分區(qū)或災備切換時OB的防腦裂設計(PAXOS)。我們知道RZone是單腦的(讀寫都在一個單元對應的庫),而網(wǎng)絡分區(qū)或者災備時熱切換過程中可能會產(chǎn)生多個腦,OB解決了腦裂情況下的共識問題(PAXOS算法)。
- 基于CZone的本地讀設計。這一點保證了很大一部分有著“寫讀時間差”現(xiàn)象的公共數(shù)據(jù)能被高速本地訪問。
- 剩下的那一丟丟不能本地訪問只能實時訪問GZone的公共配置數(shù)據(jù),也興不起什么風,作不了什么浪。比如用戶創(chuàng)建這種TPS,不會高到哪里去。再比如對于實時庫存數(shù)據(jù),可以通過“頁面展示查詢走應用層緩存”+“實際下單時再校驗”的方式減少其GZone調(diào)用量。
而這就是螞蟻LDC的CRG架構,相信54.4萬筆/秒還遠沒到LDC的上限,這個數(shù)字可以做到更高。當然雙十一海量支付的成功不單單是這么一套設計所決定的,還有預熱削峰等運營+技術的手段,以及成百上千的兄弟姐妹共同奮戰(zhàn),特此在這向各位雙十一留守同學致敬。感謝大家的閱讀,文中可能存在不足或遺漏之處,歡迎批評指正。參考文獻
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 半同步復制技術 ?cnblogs.com/zero-gg/p/9057092.html
BASE 理論分析 ?jianshu.com/p/f6157118e54b
Keepalived ?baike.baidu.com/item/Keepalived/10346758?fr=aladdin
PAXOS ?en.wikipedia.org/wiki/Paxos_(computer_science)
OceanBase 支撐 2135 億成交額背后的技術原理 ?cnblogs.com/antfin/articles/10299396.html
Backup ?en.wikipedia.org/wiki/Backup