關於分佈式系統數據一致性的那些事

近些年,隨着SOA、微服務架構的流行,分佈式系統數據一致性問題也隨之而來成爲你們熱門關注的一個問題。其實,這個問題在很早以前就存在,由於在現實生活中,不少系統都不多是一個大而全的單機系統,都或多或少須要跟其餘系統集成,這種狀況就必須須要考慮分佈式系統數據一致性。html

關於分佈式系統數據一致性的那些事

記得以前作微服務的session時,不少同窗也有問到在微服務架構下如何保證數據一致性的問題。這裏把我對這個問題的理解記錄下來,歡迎討論。java

傳統單機系統

在討論分佈式系統以前,須要先知道傳統單機系統是如何保證數據一致性的。spring

關於分佈式系統數據一致性的那些事

對於傳統的單機系統,數據的一致性每每能夠經過數據庫的事務來保證。數據庫的事務具備ACID特性,自然就是用來保證數據的一致性。ACID的含義以下:數據庫

  • A:原子性(Atomicity)緩存

  • C:一致性(Consistency)session

  • I:隔離性(Isolation)架構

  • D:持久性(Durability)

具體數據庫的事務是如何實現的,有興趣的童鞋能夠網上搜索。app

分佈式系統

關於分佈式系統的概念,我的以爲這篇文章(第一次有人把「分佈式事務」講的這麼簡單明瞭)解釋的很好。簡單來說,能夠分紅以下兩類:運維

  • 一個系統一個service關聯多個數據庫

這類系統包含一個業務處理service,可是一個service鏈接多個數據庫。在分庫分表的場景下,常可能會出現這類系統架構。以前作過一個不宕機實現數據遷移的案例,其中有一步是實現雙寫,就須要一個service同時鏈接兩個數據庫,也是屬於這一類,可參考以前我寫的一篇文章(如何不宕機實現數據庫遷移)。分佈式

關於分佈式系統數據一致性的那些事

  • 一個系統多個service關聯各自數據庫

這類系統由多個service組成,而每一個service鏈接各自獨立的數據庫。如今流行的SOA、微服務架構系統就屬於這一類。

關於分佈式系統數據一致性的那些事

對於傳統單機系統,能夠簡單的經過數據庫的事務特性來保證數據一致性,那麼對於分佈式系統,則能夠經過分佈式事務來保證。

在討論分佈式事務以前,須要先了解分佈式事務的兩個基礎理論:CAP和BASE。

CAP是分佈式系統的三個基本特性,每一個分佈式系統至多隻能同時知足其中兩個:

  • C:一致性

  • A:可用性

  • P:分區容錯性

BASE是對CAP的AP特性的一個擴展:

  • BA:Basically Available(基本可用)

  • S:Soft state(軟狀態)

  • E:Eventually consistent (最終一致性)

基於這個理論的分佈式系統,對數據一致性的要求是最終一致性,不像傳統的數據庫事務對數據的強一致性保證。

分佈式事務

網上有不少關於分佈式事務方案的討論,這裏有一篇文章寫的很好很全面(https://www.javaworld.com/article/2077963/distributed-transactions-in-spring--with-and-without-xa.html)。基於這篇文章,能夠把分佈式事務方案分爲兩類:用XA協議和不用XA協議

1、XA分佈式事務協議

XA是Oracle提出的一種分佈式事務協議,主要有兩階段提交(2PC:2 Phase Commits),三階段提交(3PC:3 Phase Commits)實現,具體原理解釋,能夠參考這篇文章(漫畫:什麼是分佈式事務?)。

這種方式能夠保證相對強一致性,但須要數據庫支持,性能消耗比較大,而且並不能徹底保證一致性,在一些狀況也可能致使數據不一致問題。

對於XA分佈式事務協議,Spring已經有很好的支持,能夠參考JtaTransactionManager。

2、不用XA分佈式事務協議

不用XA分佈式事務協議,文中也提到了不少方式,這裏只討論Best Effort 1PC,其實如今不少互聯網系統基本上都是基於這種方案,便是基於BASE理論,保證數據最終一致性。

下面分享幾個基於這種方案實現的具體案例:

第一個案例,以前作過一個不宕機實現數據遷移的案例,具體可參考以前寫的文章(如何不宕機實現數據庫遷移)。其中關鍵一步是在service中實現雙寫,即須要service同時鏈接兩個數據庫。在這個案例中,當有一個寫數據請求達到service,如何保證數據同時在兩個數據庫寫成功呢?

其實這個場景就屬於第一類的分佈式系統:一個系統一個service關聯多個數據庫,如何保證這個分佈式系統的數據一致性呢?

這裏採用的方案就是Best Effort 1PC,具體實現用的就是java的ChainedTransactionManager。

第二個案例,以前作過一個項目,基於微服務架構構建。整個系統有幾個微服務,而關鍵的幾個微服務之間採用RabbitMQ消息中間件通信。這裏爲了描述方便,簡化系統原型爲:一個電商網站,銷售訂單是一個微服務,發貨訂單是一個微服務,各個微服務鏈接本身專有的數據庫,銷售訂單和發貨訂單兩個微服務經過消息中間件通信。當一個銷售訂單變成「已支付」狀態時,銷售訂單微服務發送一個event給消息中間件,發貨訂單微服務收到event建立一個發貨訂單。在這個場景下,如何保證銷售訂單狀態改變和發貨訂單建立的同時成功或者同時失敗呢?

其實這個場景就屬於第二類的分佈式系統:一個系統多個service關聯各自數據庫,如何保證這個分佈式系統的數據一致性呢?

這裏的方案也是Best Effort 1PC,採用的原則是:at least once delivery + idempotent + auto automatic dead letter reprocessing。參見https://eng.uber.com/reliable-reprocessing/。

意思是說,對於銷售訂單微服務,至少保證一次發送event,這樣能夠保證原始數據在銷售訂單這個微服務不丟失。對於發貨訂單微服務,若是發生建立發貨訂單失敗,則開啓自動重試機制,好比說能夠利用死信隊列DLX;若是建立發貨訂單成功,可是event沒有被確認commit,則當再次收到這個event作建立發貨訂單的時候,須要保證發貨訂單不會被再次建立,即保證業務處理的冪等性(idempotent)。

第三個案例,以前作過的一個項目,採用的是人工處理的方式來解決分佈式系統數據不一致的狀況。原型是:一個分佈式的緩存系統,有一個主節點,一個從節點,寫的數據先到主節點,而後經過消息中間件同步到從節點。記得在這個案例中,偶爾會發生主從數據不一致的狀況。那麼採起的辦法是:有一個數據比對的monitoring工具,按期檢查數據一致性,一旦發生不一致性的狀況,發郵件給運維人員,人工處理。

延伸案例,我以前生活中遇到過一個真實場景,騎共享單車的時候,掃碼開鎖,app上顯示開鎖失敗,可是實際上車的鎖打開了,這其實就是一個分佈式系統數據不一致性的案例。這個問題應該是app backend發出開鎖消息到自行車上,可是由於某些緣由沒收到response,而自行車端收到開鎖消息了,而且成功執行了開鎖,可是成功開鎖的ack由於某些緣由沒發到app backend。

這個問題從業務上來說,對騎行的人沒有影響,由於app上顯示開鎖失敗,也不用付錢;可是對於單車運營方來講是個損失,從他們的角度怎麼保證一致性呢?有童鞋建議,能夠先在app backend更新鎖狀態(把狀態設成開鎖成功),而後發開鎖的消息,而且在app中提供從新開鎖的功能(開鎖失敗,讓用戶來觸發從新開鎖)來處理不一致的狀況。可是這也會帶來一些新的問題,好比backend不知道開鎖是真失敗(鎖沒打開)仍是假失敗(鎖打開了,可是打開的ack沒成功發送到backend),若是是真失敗,app上一來就顯示開鎖成功,對用戶來講,用戶體驗不是很好,用戶只能經過申訴鎖沒打開來解除開鎖成功狀態。而且,若是是假失敗,用戶一樣能夠採起申訴把開鎖成功解除掉。這樣這種方案帶來了一些新的問題。記得這好像就是最開始小黃人的解決方案。若是你們有其餘好的idea,歡迎討論。

總結

在討論分佈式數據一致性問題時,必定要根據業務需求來討論方案架構,由於不一樣的業務需求對數據的一致性要求不同,採用的系統方案架構也就不同,進而須要的技術成本和帶給用戶的體驗也不同。

References

相關文章
相關標籤/搜索