現在很火的微服務架構所設計的系統是分布式系統。分布式系統有一個著名的CAP理論,即一個分布式系統要同時滿足一致性(Consistency)、可用性(Availablility)和分區容錯(Partition Tolerance)三個特性是一件不可能的事情。
CAP理論的簡介
CAP理論是由Eric Brewer在2000年的PODC會議上提出的,該理論在兩年后被證明成立。
CAP理論告訴架構師不要妄想設計出同時滿足三者的系統,應該有所取舍,設計出適合業務的系統。
一致性(Consistency):一致性指的是數據的強一致性。每次的讀操作都是讀取的最新數據。即如果寫入某個數據成功的話,之后的讀取都應該讀的是新寫入的數據;如果寫入失敗的話,之后讀取的都不應該是寫入失敗的數據。
可用性(Availability):可用性指的是服務的可用性。即每個請求都能在合理的時間內獲得符合預期的響應結果。
分區容錯性(Partition Tolerance):分區容錯性指的是當節點之間的網絡出現問題之后,系統仍然能夠正常提供服務。
在分布式的系統中,P是基本要求,而單體應用則是CA系統。微服務系統通常是一個AP系統,即同時滿足可用性和分區容錯性。這樣就有了一個在分布式系統中保證數據強一致性的難題,這個難題的一個解決方案就是分布式事務。
分布式事務的解決方案
在微服務系統中,每個服務都是獨立的進程單元,每個服務都有自己的數據庫。在通常情況下,只有關系型數據庫在特定的數據引擎下才會支持事務(本地事務),而大多數非關系型數據庫是不支持事務的,比如MongDB完全不支持事務,而Redis是支持事務的,雖然支持得不完整。
兩階段提交
網上購物在日常生活中是一個非常普通的場景,假設靜靜在某寶上購買了一個玩具,需要從靜靜的賬戶中扣除200塊錢,同時玩具的庫存數量需要減1,賣家的賬戶中增加200塊錢。
如果這是一個單體應用,並且使用支持事務的數據庫(比如InnoDB數據庫引擎的MySQL、Oracle和SQL Server等),我們可能是這樣寫代碼的:
@Transactional public void update() throws RuntimeException{ updateAccountTable(); // 更新賬戶表 updateGoodsTable(); // 更新商品表 }
如果是微服務架構的話,賬戶是一個服務,商品是一個服務,兩個獨立的服務就不能使用數據庫自帶的事務了,因為這兩個數據表可能並不在一個數據庫中,這就需要用到兩階段提交的解決方案。
第一階段,service-account發起一個分布式事務,交給事務式事務TC處理,事務協調器TC向所有參與事務的節點發送處理事務操作的准備操作。所有參與節點執行准備操作,將Undo和Redo信息寫入日志中,並向事務管理器返回准備操作是否成功的消息。
第二階段,事務管理器收集所有節點的准備操作是否都成功,如果都成功的話則通知所有的節點執行提交操作,如果有一個失敗則執行回滾操作。
兩階段提交將事務分成了兩個部分,大大提高了分布式事務的成功概率。然而,如果在第一階段都成功了,而執行第二階段的某一個節點失敗,仍然會導致數據不准確。這種情況下一般需要人工去處理,這也是為什么要在第一步記錄日志的原因。
另外,如果分布式事務涉及的節點很多,一旦某一個節點的網絡出現異常,就會導致整個事務處於阻塞狀態,大大降低數據庫的性能。因此如果不是必要的話,建議是盡量少用分布式事務,有些時候過度設計反而會造成相反的效果。
三階段提交
三階段提交在兩階段提交的步驟中間加了一層預提交事務階段。
1.CanCommit階段。這個階段和上面說的兩階段提交的准備階段類似,不同的地方就是並沒有進行諸如將Undo和Redo的信息寫入事務日志的其他操作。
2.PreCommit階段。這個階段是一個緩沖,目的是推遲Commit的決定,只有保證所有參與者都知道了Commit的決定之后,才會真正發出Commit的決定。所有的參與者都會在這個階段記錄Undo和Redo的信息,並且當協調者發生故障之后,所有的參與者還能互相通信來確定事務是提交還是終止。
3.DoCommit階段。這個階段就是事務的真正提交,如果所有的參與者都向協調者發送了ACK響應,那么協調者就會完成事務,否則中斷事務。
三階段提交的方案引入了對參與者的超時機制,相比於兩階段提交只有協調者擁有超時的機制,三階段提交解決了協調者突然掛掉引起的參與者一直阻塞的問題。
本質上來說,三階段提交避免了狀態停滯的問題。在兩階段提交的過程中有可能會因為各種原因產生狀態停滯的問題,最明顯的就是協調者突然宕機的情況。但是三階段提交即使是協調者宕機也會讓狀態繼續下去,參與者們也會互相通信確定事務是提交還是終止,從而使狀態繼續下去,哪怕狀態是錯的。
"在心碎中認清遺憾,生命漫長也短暫。"