知其所以(yi)然~分布式(shi)事務(wu)cap
背景
一致性是一個抽象的、具有多重含義的計算機術語,在不同應用場景下,有不同的定義和含義。在傳統的IT時代,一致性通常指強一致性,強一致性通常體現在你中有我、我中有你、渾然一體;而在互聯網時代,一致性的含義遠遠超出了它原有的含義,在我們討論互聯網時代的一致性之前,我們先了解一下互聯網時代的特點,互聯網時代信息量巨大、需要計算能力巨大,不但對用戶響應速度要求快,而且吞吐量指標也要向外擴展(既:水平伸縮),于是單節點的服務器無法滿足需求,服務節點開始池化,想想那個經典的故事,一只筷子一折就斷,一把筷子怎么都折不斷,可見人多力量大的思想是多么的重要,但是人多也不一定能解決所有事情,還得進行有序、合理的分配任務,進行有效的管理,于是互聯網時代談論最多的話題就是拆分,拆分一般分為“水平拆分”和“垂直拆分”(大家不要對應到數據庫或者緩存拆分,這里主要表達一種邏輯)。這里,“水平拆分”指的是同一個功能由于單機節點無法滿足性能需求,需要擴展成為多節點,多個節點具有一致的功能,組成一個服務池,一個節點服務一部分的請求量,團結起來共同處理大規模高并發的請求量。“垂直拆分”指的是按照功能拆分,秉著“專業的人干專業的事兒”的原則,把一個復雜的功能拆分到多個單一的簡單的元功能,不同的元功能組合在一起,和未拆分前完成的功能是一致的,由于每個元功能職責單一、功能簡單,讓維護和變更都變得更簡單、安全,更易于產品版本的迭代,在這樣的一個互聯網的時代和環境,一致性指分布式服務化系統之間的弱一致性,包括應用系統一致性和數據一致性。
無論是水平拆分還是垂直拆分,都解決了特定場景下的特定問題,凡事有好的一面,都會有壞的一面,拆分后的系統或者服務化的系統最大的問題就是一致性問題,這么多個具有元功能的模塊,或者同一個功能池中的多個節點之間,如何保證他們的信息是一致的、工作步伐是一致的、狀態是一致的、互相協調有序的工作呢?
本文根據作者在互聯網企業的實際項目經驗,對服務化系統中最難解決的一致性問題進行研究和探討,試圖從實踐經驗中找到規律,抽象出模式,分享給大家,希望對大家的項目實施有所幫助,在對實踐的總結中也會對相關的一致性術語做最樸實的解釋,希望能幫助大家徹底理解一致性的本質,并能將其應用到實踐,解決讀者現實中遇到的服務化系統的一致性問題,本文使用理論與實踐相結合的方法,突出在實踐中解決問題的模式,因此叫做《分布式服務化系統一致性的“最佳實干”》。
實例,產生不一致的例子
- 案例1:買房
假如你想要享受生活的隨意,只想買個兩居,不想讓房貸有太大壓力,而你媳婦卻想要買個三居,還得帶花園的,那么你們就不一致了,不一致導致生活不愉快、不協調,嚴重情況下還會吵架,可見生活中的不一致問題影響很大。 - 案例2:轉賬
轉賬是經典的不一致案例,設想一下銀行為你處理一筆轉賬,扣減你賬戶上的余額,然后增加別人賬戶的余額;如果扣減你的賬戶余額成功,增加別人賬戶余額失敗,那么你就會損失這筆資金。反過來,如果扣減你的賬戶余額失敗,增加別人賬戶余額成功,那么銀行就會損失這筆資金,銀行需要賠付。對于資金處理系統來說,上面任何一種場景都是不允許發生的,一旦發生就會有資金損失,后果是不堪設想的,嚴重情況會讓一個公司瞬間倒閉,可參考案例( - 案例3:下訂單和扣庫存
電商系統中也有一個經典的案例,下訂單和扣庫存如何保持一致,如果先下訂單,扣庫存失敗,那么將會導致超賣;如果下訂單沒有成功,扣庫存成功,那么會導致少賣。兩種情況都會導致運營成本的增加,嚴重情況下需要賠付。 - 案例4:同步超時
服務化的系統間調用常常因為網絡問題導致系統間調用超時,即使是網絡很好的機房,在億次流量的基數下,同步調用超時也是家常便飯。系統A同步調用系統B超時,系統A可以明確得到超時反饋,但是無法確定系統B是否已經完成了預定的功能或者沒有完成預定的功能。于是,系統A就迷茫了,不知道應該繼續做什么,如何反饋給使用方。(曾經的一個B2B產品的客戶要求接口超時重新通知他們,這個在技術上是難以實現的,因為服務器本身可能并不知道自己超時,可能會繼續正常的返回數據,只是客戶端并沒有接受到結果罷了,因此這不是一個合理的解決方案)。 - 案例5:異步回調超時
此案例和上一個同步超時案例類似,不過這個場景使用了異步回調,系統A同步調用系統B發起指令,系統B采用受理模式,受理后則返回受理成功,然后系統B異步通知系統A。在這個過程中,如果系統A由于某種原因遲遲沒有收到回調結果,那么兩個系統間的狀態就不一致,互相認知不同會導致系統間發生錯誤,嚴重情況下會影響核心事務,甚至會導致資金損失。 - 案例6:掉單
分布式系統中,兩個系統協作處理一個流程,分別為對方的上下游,如果一個系統中存在一個請求,通常指訂單,另外一個系統不存在,則導致掉單,掉單的后果很嚴重,有時候也會導致資金損失。 - 案例7:系統間狀態不一致
這個案例與上面掉單案例類似,不同的是兩個系統間都存在請求,但是請求的狀態不一致。 - 案例8:緩存和數據庫不一致
交易相關系統基本離不開關系型數據庫,依賴關系型數據庫提供的ACID特性(后面介紹),但是在大規模高并發的互聯網系統里,一些特殊的場景對讀的性能要求極高,服務于交易的數據庫難以抗住大規模的讀流量,通常需要在數據庫前墊緩存,那么緩存和數據庫之間的數據如何保持一致性?是要保持強一致呢還是弱一致性呢? - 案例9:本地緩存節點間不一致
一個服務池上的多個節點為了滿足較高的性能需求,需要使用本地緩存,使用了本地緩存,每個節點都會有一份緩存數據的拷貝,如果這些數據是靜態的、不變的,那永遠都不會有問題,但是如果這些數據是半靜態的或者常被更新的,當被更新的時候,各個節點更新是有先后順序的,在更新的瞬間,各個節點的數據是不一致的,如果這些數據是為某一個開關服務的,想象一下重復的請求走進了不同的節點(在failover或者補償導致的場景下,重復請求是一定會發生的,也是服務化系統必須處理的),一個請求走了開關打開的邏輯,同時另外一個請求走了開關關閉的邏輯,這導致請求被處理兩次,最壞的情況下會導致災難性的后果,就是資金損失。 - 案例10:緩存數據結構不一致
這個案例會時有發生,某系統需要種某一數據結構的緩存,這一數據結構有多個數據元素組成,其中,某個數據元素都需要從數據庫中或者服務中獲取,如果一部分數據元素獲取失敗,由于程序處理不正確,仍然將不完全的數據結構存入緩存,那么緩存的消費者消費的時候很有可能因為沒有合理處理異常情況而出錯。
ACID(酸)
如何保證強一致性呢?計算機專業的童鞋在學習關系型數據庫的時候都學習了ACID原理,這里對ACID做個簡單的介紹。如果想全面的學習ACID原理,請參考ACID(
關系型數據庫天生就是解決具有復雜事務場景的問題,關系型數據庫完全滿足ACID的特性。
ACID指的是:
- A: Atomicity,原子性
- C: Consistency,一致性
- I: Isolation,隔離性
- D: Durability,持久性
具有ACID的特性的數據庫支持強一致性,強一致性代表數據庫本身不會出現不一致,每個事務是原子的,或者成功或者失敗,事物間是隔離的,互相完全不影響,而且最終狀態是持久落盤的,因此,數據庫會從一個明確的狀態到另外一個明確的狀態,中間的臨時狀態是不會出現的,如果出現也會及時的自動的修復,因此是強一致的。
3個典型的關系型數據庫Oracle、Mysql、Db2都能保證強一致性,Oracle和Mysql使用多版本控制協議實現,而DB2使用改進的兩階段提交協議來實現。
如果你在為交易相關系統做技術選型,交易的存儲應該只考慮關系型數據庫,對于核心系統,如果需要較好的性能,可以考慮使用更強悍的硬件,這種向上擴展(升級硬件)雖然成本較高,但是是最簡單粗暴有效的方式,另外,Nosql完全不適合交易場景,Nosql主要用來做數據分析、ETL、報表、數據挖掘、推薦、日志處理等非交易場景。
前面提到的案例2-轉賬和案例3-下訂單和扣庫存都可以利用關系型數據庫的強一致性解決。
然而,前面提到,互聯網項目多數具有大規模高并發的特性,必須應用拆分的理念,對高并發的壓力采取“大而化小、小而化了”的方法,否則難以滿足動輒億級流量的需求,即使使用關系型數據庫,單機也難以滿足存儲和TPS上的需求。為了保證案例2-轉賬可以利用關系型數據庫的強一致性,在拆分的時候盡量的把轉賬相關的賬戶放入一個數據庫分片,對于案例3,盡量的保證把訂單和庫存放入同一個數據庫分片,這樣通過關系型數據庫自然就解決了不一致的問題。
然而,有些時候事與愿違,由于業務規則的限制,無法將相關的數據分到同一個數據庫分片,這個時候我們就需要實現最終一致性。
對于案例2-轉賬場景,假設賬戶數量巨大,對賬戶存儲進行了拆分,關系型數據庫一共分了8個實例,每個實例8個庫,每個庫8個表,共512張表,假如要轉賬的兩個賬戶正好落在了一個庫里,那么可以依賴關系型數據庫的事務保持強一致性。
如果要轉賬的兩個賬戶正好落在了不同的庫里,轉賬操作是無法封裝在同一個數據庫事務中的,這個時候會發生一個庫的賬戶扣減余額成功,另外一個庫的賬戶增加余額失敗的情況。
對于這種情況,我們需要繼續(xu)探(tan)討解決之道,CAP原理(li)和BASE原理(li),BASE原理(li)通(tong)過記(ji)錄事務的(de)中間(jian)的(de)臨(lin)時狀態,實現最終(zhong)一致(zhi)性。
CAP(帽子理論)
如果想深入的學習CAP理論,請參考CAP(
由于對(dui)系統(tong)或者數據進行了拆分,我們的系統(tong)不(bu)再是(shi)單機系統(tong),而是(shi)分布式(shi)系統(tong),針對(dui)分布式(shi)系的帽(mao)子(zi)理論包(bao)含三個元素:
- C:Consistency,一致性, 數據一致更新,所有數據變動都是同步的
- A:Availability,可用性, 好的響應性能,完全的可用性指的是在任何故障模型下,服務都會在有限的時間處理響應
- P:Partition tolerance,分區容錯性,可靠性
帽子理(li)論證明,任何分布式系(xi)統(tong)只可同(tong)時(shi)滿(man)足二點,沒法(fa)三者(zhe)(zhe)兼顧。關系(xi)型數據庫(ku)由(you)于(yu)關系(xi)型數據庫(ku)是單節點的(de)(de),因此,不具有分區(qu)容(rong)(rong)錯(cuo)性(xing)(xing)(xing),但(dan)是具有一致性(xing)(xing)(xing)和可用(yong)性(xing)(xing)(xing),而分布式的(de)(de)服務化(hua)系(xi)統(tong)都(dou)需要(yao)滿(man)足分區(qu)容(rong)(rong)錯(cuo)性(xing)(xing)(xing),那(nei)么我們必(bi)須在(zai)一致性(xing)(xing)(xing)和可用(yong)性(xing)(xing)(xing)中進行權衡,具體表(biao)現在(zai)服務化(hua)系(xi)統(tong)處理(li)的(de)(de)異常請(qing)求在(zai)某(mou)一個時(shi)間(jian)段內可能是不完(wan)全的(de)(de),但(dan)是經過自動的(de)(de)或者(zhe)(zhe)手工的(de)(de)補償后(hou),達到了最終的(de)(de)一致性(xing)(xing)(xing)。
BASE(堿)
BASE理論解決CAP理論提出了分布式系統的一致性和可用性不能兼得的問題,如果想全面的學習BASE原理,請參考Eventual consistency(
BASE在英文中有“堿”的意思,對應本節開頭的ACID在英文中“酸”的意思,基于這兩個名詞提出了酸堿平衡的結論,簡單來說是在不同的場景下,可以分別利用ACID和BASE來解決分布式服務化系統的一致性問題。
BASE模(mo)型(xing)與ACID模(mo)型(xing)截(jie)然不同(tong),滿(man)足CAP理論,通過犧(xi)牲強一致性(xing)(xing),獲(huo)得可用(yong)(yong)(yong)性(xing)(xing),一般應(ying)(ying)用(yong)(yong)(yong)在服(fu)務化系統的應(ying)(ying)用(yong)(yong)(yong)層或者大數據(ju)處理系統,通過達到最終一致性(xing)(xing)來盡量滿(man)足業務的絕大部分需(xu)求。
BASE模型包(bao)含個三個元素:
- BA:Basically Available,基本可用
- S:Soft State,軟狀態,狀態可以有一段時間不同步
- E:Eventually Consistent,最終一致,最終數據是一致的就可以了,而不是時時保持強一致
BASE模型的軟狀態是實現BASE理論的方法,基本可用和最終一致是目標。按照BASE模型實現的系統,由于不保證強一致性,系統在處理請求的過程中,可以存在短暫的不一致,在短暫的不一致窗口請求處理處在臨時狀態中,系統在做每步操作的時候,通過記錄每一個臨時狀態,在系統出現故障的時候,可以從這些中間狀態繼續未完成的請求處理或者退回到原始狀態,最后達到一致的狀態。
以案例1-轉賬為例,我們把用戶A給用戶B轉賬分成四個階段,第一個階段用戶A準備轉賬,第二個階段從用戶A賬戶扣減余額,第三個階段對用戶B增加余額,第四個階段完成轉賬。系統需要記錄操作過程中每一步驟的狀態,一旦系統出現故障,系統能夠自動發現沒有完成的任務,然后,根據任務所處的狀態,繼續執行任務,最終完成任務,達到一致的最終狀態。
在實際應用(yong)(yong)中(zhong),上面這個(ge)過程通常是(shi)通過持久(jiu)化(hua)執(zhi)(zhi)行(xing)(xing)任(ren)(ren)(ren)務(wu)的(de)(de)(de)(de)(de)狀(zhuang)(zhuang)(zhuang)態(tai)(tai)和環境(jing)信息,一(yi)(yi)旦出現(xian)問(wen)題,定時任(ren)(ren)(ren)務(wu)會撈取(qu)(qu)未執(zhi)(zhi)行(xing)(xing)完(wan)的(de)(de)(de)(de)(de)任(ren)(ren)(ren)務(wu),繼續(xu)未執(zhi)(zhi)行(xing)(xing)完(wan)的(de)(de)(de)(de)(de)任(ren)(ren)(ren)務(wu),直(zhi)到(dao)(dao)(dao)執(zhi)(zhi)行(xing)(xing)完(wan)成為止,或者(zhe)取(qu)(qu)消(xiao)已(yi)經完(wan)成的(de)(de)(de)(de)(de)部分操(cao)(cao)作(zuo)回(hui)到(dao)(dao)(dao)原始狀(zhuang)(zhuang)(zhuang)態(tai)(tai)。這種方法在任(ren)(ren)(ren)務(wu)完(wan)成每個(ge)階(jie)段(duan)的(de)(de)(de)(de)(de)時候,都要更新數(shu)據(ju)庫(ku)中(zhong)任(ren)(ren)(ren)務(wu)的(de)(de)(de)(de)(de)狀(zhuang)(zhuang)(zhuang)態(tai)(tai),這在大規模高并(bing)發系統(tong)中(zhong)不會有太好(hao)(hao)的(de)(de)(de)(de)(de)性能,一(yi)(yi)個(ge)更好(hao)(hao)的(de)(de)(de)(de)(de)辦(ban)法是(shi)用(yong)(yong)Write-Ahead Log(寫前日(ri)志),這和數(shu)據(ju)庫(ku)的(de)(de)(de)(de)(de)Bin Log(操(cao)(cao)作(zuo)日(ri)志)相似,在做每一(yi)(yi)個(ge)操(cao)(cao)作(zuo)步驟(zou),都先寫入日(ri)志,如果操(cao)(cao)作(zuo)遇到(dao)(dao)(dao)問(wen)題而停止的(de)(de)(de)(de)(de)時候,可(ke)(ke)以(yi)(yi)讀取(qu)(qu)日(ri)志按照(zhao)步驟(zou)進行(xing)(xing)恢(hui)復,并(bing)且繼續(xu)執(zhi)(zhi)行(xing)(xing)未完(wan)成的(de)(de)(de)(de)(de)工作(zuo),最后達到(dao)(dao)(dao)一(yi)(yi)致。寫前日(ri)志可(ke)(ke)以(yi)(yi)利(li)用(yong)(yong)機(ji)械硬盤(pan)的(de)(de)(de)(de)(de)追(zhui)加寫而達到(dao)(dao)(dao)較好(hao)(hao)性能,因此,這是(shi)一(yi)(yi)種專(zhuan)業化(hua)的(de)(de)(de)(de)(de)實現(xian)方式,多數(shu)業務(wu)系系統(tong)還(huan)是(shi)使用(yong)(yong)數(shu)據(ju)庫(ku)記錄(lu)的(de)(de)(de)(de)(de)字段(duan)來記錄(lu)任(ren)(ren)(ren)務(wu)的(de)(de)(de)(de)(de)執(zhi)(zhi)行(xing)(xing)狀(zhuang)(zhuang)(zhuang)態(tai)(tai),也就是(shi)記錄(lu)中(zhong)間的(de)(de)(de)(de)(de)“軟狀(zhuang)(zhuang)(zhuang)態(tai)(tai)”,一(yi)(yi)個(ge)任(ren)(ren)(ren)務(wu)的(de)(de)(de)(de)(de)狀(zhuang)(zhuang)(zhuang)態(tai)(tai)流(liu)轉一(yi)(yi)般(ban)可(ke)(ke)以(yi)(yi)通過數(shu)據(ju)庫(ku)的(de)(de)(de)(de)(de)行(xing)(xing)級鎖來實現(xian),這比使用(yong)(yong)Write-Ahead Log實現(xian)更簡單、更快(kuai)速(su)。