歡迎光臨
每天分享高質量文章

NET Core微服務之路:再談分佈式系統中一致性問題分析

前言

一致性:很多時候表現在IT系統中,通常在分佈式系統中,必須(或最終)為多個節點的資料保持一致。世間萬物,也有存在相同的特征或相似,比如兒時的雙胞胎,一批工廠流水線的產品,當然,我們不去討論非IT以外的知識點。

註:我們一定要明白一個詞叫“信息不對稱”,不論是人、事、物,信息不對稱是永遠都存在的,要知道,在IT系統中,能引起信息不對稱的因素有很多,比如網絡上,有丟包、有延遲。硬體上,有不同性能的計算能力和處理能力。

在傳統的IT時代,一致性通常是指強一致性,比如一個單體的WEB程式中,從資料庫到快取,再到呈現出來的界面,資料均是相同的;

而在現在的互聯網時代,特別是分佈式架構下,一致性的含義遠遠超出她原有的含義,由於互聯網的特點,信息量巨大,每個人(或者不同的每個業務)獲取到的信息不對稱,最終都會造成不一致。

換句話說,傳統的單體應用無法滿足巨大的信息量,而轉向為多節點、多服務的分佈式架構模型(正如CPU,從單核變為多核來支撐更多的計算能力),多個服務多節點必然會產生資料不一致的問題,正如雖然人多力量大,可如果每個人都往不同方向使勁,這個力量就是分散的,不一致的,沒有任何作用的,所以,我們需要管理,需要告訴每個人使勁的方向,告訴每個人的排列順序等等一系列有效的分配和管理,於是乎,我們將這個話題轉化為互聯網思維就是“拆”(非拆遷辦)。

我們很多時候都在討論這個程式如何拆分,比如拆成業務層、資料層、提供層、UI層、快取層等等,而每一層的部署大部分情況下都不會存在於一個相同的宿主中(否者怎麼還叫拆),這樣稱為水平拆分(或橫向拆分),將這些不同的層部署成多個節點,多個節點如果具有相同的一致性功能,那麼再組成一個服務域,這樣,把這個服務域作為一個處理中心,在處理能力和計算能力上,遠遠會超過單體程式的整體性能。

這樣的優勢顯而易見,但是,最大的問題(不能說是缺點)是,由於拆分後的系統或者服務化的系統,存在多個元功能模塊,或者一個服務域中的多個節點,如何去保證她們資料的一致呢?

提出問題

直接用A,B,C等等常規化表述形式去描述不一致問題,網上文章很多,我們嘗試換一種方式,以實際的、你我都會接觸到的現實場景,來理解不同情況下所產生的不一致問題。

Q1.生活案例:意願

假如,別假如了,就拿筆者來說吧。當初跟妻子去買婚戒的時候,只想買個一般的對戒便可,因為日後的日子里用錢的地方太多(相信任何一個已婚男士都有這種體會),可妻子喜歡那種布靈布靈的,要知道,這種布靈布靈的,可比一般的要貴出許多許多,那麼,這個不一致就開始形成了,你的意願和她的意願並沒保持一致,日後生活問題會越來越大。當然,這隻是一個幾年前的例子,顯然我妻子已經接受了我當初的觀點。

Q2.銀行案例:轉賬

轉賬是不一致案例中最經典的例子。這麼來說,假如你想通過某個平臺,比如支付寶、微信、銀行進行轉賬,這個平臺的流程是這樣的:首先減去你賬戶上的餘額,然後加到你指定賬戶上去。如果平臺減去你的賬戶餘額成功了,而增加其他賬戶的的餘額失敗了,那麼你將損失這筆錢;如果減去你的賬戶餘額失敗,而增加其他賬戶的餘額成功,那麼銀行將損失這筆錢;這種資金上的不一致是絕不允許存在,否則這個平臺將面臨破產和倒閉。

Q3.常見案例:下單和扣庫存

不管是電商還是企業倉庫,都會存在一個經典案例。比如,你在某個界面上進行了下單,比如她顯示的庫存是100件,而你訂購了1件,並支付了應該的費用,可是,這個庫存數量並未任何變化,那麼,如果有101個人前來購買,那麼會出現超賣的情況,也就是這多出來的1個人將買不到這件商品,這就是下單和庫存不一致所導致的。反之,永遠下單不成功,而庫存卻在減少,這對企業來說都是增加運營成本的。

Q4.常見案例:掉單

掉單一般常見於協作工作的系統的流程中,分別對彼此的上游和下游。比如上面的案例,你已成功下單並支付,按照流程,平臺應該告訴物流我有物品需要配送,過來取單,可物流終究收不到這個收單請求,導致物品配送失敗,嚴重會導致賠付,這樣的不一致性通常是一個系統中的兩個請求不一致而造成的。

Q5.系統案例:系統狀態

這個案例跟上面的掉單案例類似,只是需要區分引起這個掉單的原因,其實就是兩個系統的狀態不一致所造成。比如上游及時收到請求並響應,而下游卻因為某個狀態(原因)而沒響應這個請求,最終導致不一致形成。

Q6.系統案例:本地快取和資料庫

現在儲存都依賴於資料庫,而關係型資料庫都具備ACID特性,但是在大規模高併發的互聯網系統里,一些特殊的場景對讀的性能要求極高,為止,服務在這個上面的資料庫將難以抗住大規模的讀流量,為了應對這樣的問題,一般都會在資料庫前增加快取,那麼快取和資料庫之間的資料是如何保持一致?是需要強一致還是若一致?

Q7.系統案例:節點快取

一個服務域上的多個節點為了滿足較高的性能要求,需要使用到本地快取,使用了本地快取,每個節點都會有一份快取資料的拷貝,如果這些資料是靜態的、不變的,那永遠都不會有任何問題。但如果這些資料是動態的、經常更新的。那麼問題就來了,當被更新的時候,各個節點的更新都會存在先後順序,而正是這先後順序的一瞬間,各個節點的資料將會不一致。想象一個高流量讀的場景中,一個請求拿到的資料是1,而另一個請求拿到的是0,這將導致災難性的後果。

Q8.系統案例:超時

服務化的系統間呼叫常常因為網絡問題導致系統間呼叫超時,這是在即便在網絡最好的機房下、在上億次的前提下,同步呼叫超時也是必然會存在的,正如上面提到的不同狀態類似,假如當下單和物流存在極高請求的情況下,物流並未及時反饋響應,而下單卻並不知道物流是否已經接到訂單,或者定已收到訂單,只是掉包了等現象,這樣的不一致,也是會導致災難性後果的。

解決模型

對於Q1問題,我們可以有兩套方案,一是不結了,不過顯然這是行不通的;二是慢慢的補償,先買個一般的,等手頭資金充裕了,或者某天中了500萬,或者天上掉個金塊,再給她個驚喜,於是,這個問題解決了,大家的意願都一致了,都開心了。可見,這樣的解決樣式會存在一個現象—過渡時期,當這個過渡時期到最終雙方都達成一致時,問題就解決了。因此,我們不能要求在買對戒的時候,雙方都達到強一致的要求,生活是必須要過下去的,不可能說散就散,那麼我們要考慮去補償她,盡最大的努力達到最終一致。

在化學單詞中,“ACID”是酸,我想這絕對不是巧合,原文可參考百科

(image form Wilfred Springer,)

因此,資料庫會從一個明確的狀態到另外一個明確的狀態,中間的臨時狀態是不會出現的,EF的追蹤功能也正是遵循這個原則進行設計,任何一個操作狀態在中途是不會存在改變,如果出現改變,將會給你提示錯誤,當然,你可以關掉它,不過這樣EF的核心點就不存在了。

  • A: Atomicity,原子性

  • C: Consistency,一致性

  • I: Isolation,隔離性

  • D: Durability,持久性

回到Q2和Q3的問題上,使用關係型資料庫可以解決這樣的強一致性需求,然而,單純通過強一致性的資料庫去面對不斷拆分的元組,是難以滿足互聯網高流量的需求的,或許你會說使用服務器固態硬碟和讀寫分離的樣式去應對,但這絕對只是一個應對方案而已。因此,在拆分的時候儘量把轉賬的相關賬戶放入一個資料庫分片,而Q3上,把訂單和存庫放入一個分片,因為中途不會存在任何改變,從0到100必須保證任何狀態都是原始的初始狀態。

但是,我們就Q2假設另外一個場景,假設賬戶的數量巨大,對賬戶儲存進行了拆分,關係型資料庫分為8個實體,每個實體8個庫,每個庫8張表,共512張表,假如轉賬的賬戶正好在一個庫里,這個問題依賴關係型資料庫的事務來保持強一致性,但是,如果兩個賬戶在不同的庫里,這個事務就無法封裝在同一個資料庫中的,這樣就會發生一個賬戶扣款成功,而另外一個庫的賬戶增加失敗的情況。

帽子理論證明,任何分佈式系統同時只可滿足兩點,沒法三者兼顧。

  • C:Consistency,一致性, 資料一致更新,所有資料變動都是同步的

  • A:Availability,可用性, 好的響應性能,完全的可用性指的是在任何故障模型下,服務都會在有限的時間處理響應

  • P:Partition tolerance,分割槽容錯性,可靠性

關係型資料庫由於關係型資料庫是單節點的,因此,不具有分割槽容錯性,但是具有一致性和可用性,而分佈式的服務化系統都需要滿足分割槽容錯性,那麼我們必須在一致性和可用性中進行權衡,具體表現在服務化系統處理的異常請求在某一個時間段內可能是不完全的,但是經過自動的或者手工的補償後,達到了最終的一致性。

而BASE理論的提出,解決了CAP在分佈式系統中的一致性和可用性不可兼得的問題。“BASE”在化學單詞中是指鹼,因此我們可以想到一個詞語叫“酸鹼平衡”,而在實際的場景中,我們可以分別使用ACID和BASE來解決分佈式服務化系統的一致性問題。BASE理論與ACID理論完全不同,她滿足CAP理論,通過犧牲強一致性而獲得可用性,一般應用在服務化系統的應用層,通過達到最終一致性來儘量滿足不同業務上的需求。

  • BA:Basically Available,基本可用

  • S:Soft State,軟狀態,狀態可以有一段時間不同步

  • E:Eventually Consistent,最終一致,最終資料是一致的就可以了,而不是時時保持強一致

按照BASE模型實現的系統,由於不保證強一致性,系統在處理請求的過程中,可以存在短暫的不一致狀態。系統在做每一步操作的時候,通過記錄每一個臨時狀態,在系統出現故障的時候,可以從這些臨時狀態中繼續完成未完成的請求處理,或者回退到原始狀態,最後達到一致的狀態。

例如Q1的轉賬狀態為例,我們把兩個賬戶的轉賬情況粗分為四個狀態:

  • 第一個狀態為準備狀態,用戶準備向另外一個進行用戶轉賬;

  • 第二個狀態為扣額狀態,系統將從轉賬用戶中扣去相應餘額;

  • 第三個狀態為加額狀態,系統將從收款用戶中增加相應餘額;

  • 第四個狀態為完成狀態,系統轉賬完成後的確認;

在這過程中,系統需要將每一步的操作狀態都進行記錄,一旦某個環節出現故障,系統能夠發現故障環節並繼續完成未完成的任務,最終完成任務,達到一致的最終狀態。在實際的業務生產環境中,通常每個階段的狀態都是通過持久化的執行任務,一旦出現了問題,定時任務會檢查未完成的任務,繼續執行未完成的任務,直到執行完成為止;再或者,該狀態是屬於取消狀態,跟資料庫事務執行方式一樣,那麼這個過程中已經完成的狀態應該回滾到原始狀態,整個過程還可以細化到如下更加詳細實際轉賬流程:

不過,由於這種方法在每個狀態執行的時候都需要記錄下來,而且需要更新資料庫中的狀態信息,一旦在大規模高併發下,性能將會是一個嚴重的瓶頸。

總結

  • 如果錢不是問題,那麼最最簡單的方式是使用向上擴展,利用強悍的硬體性能來運行專業的關係資料庫,能否保證強一致性,比如Orcele和DB2這樣符合工業標準的資料庫。

  • 如果錢是個問題,那麼相關的資料分到資料庫的同一個分片,能夠保證使用關係型資料庫實現強一致性,比如Mysql。

  • 如果是業務限制,無法將相關的資料分到同一個片,就需要實現最終一致性,通過記錄事務的狀態來判斷,一旦處理不一致,可通過自動化(如定時)或者人工干預來繼續執行,並修複不一致的情況。

下一篇我們再多介紹一下一致性的更多具體實現方案。

參考

《淺析分佈式一致性模型》http://loopjump.com/distributed_consistency_model/

 

原文地址:https://www.cnblogs.com/SteveLee/p/About_The_Distributed_Consistency.html

赞(0)

分享創造快樂