概述
事務(wù)是一組讀寫操作,這些操作被當作一個獨立的工作單元被執(zhí)行,操作的執(zhí)行結(jié)果要么全部成功,要么全部失敗,不允許部分成功、部分失敗的情況出現(xiàn)。
事務(wù)可以分為本地事物和分布式事物,本地事務(wù)的數(shù)據(jù)操作是在單個數(shù)據(jù)庫上執(zhí)行的,而分布式事務(wù)是在多個數(shù)據(jù)庫上執(zhí)行的,分布式事物由兩個或以上的本地事物構(gòu)成,它也被稱之為全局事物。
無論是本地事物,還是分布式事物它們的作用都是為了保障數(shù)據(jù)的一致性。
實列

在采用微服務(wù)架構(gòu)的應(yīng)用程序中,需要使用分布式事務(wù)技術(shù)的典型實列是:當訂單支付成功之后,需要同時跟新訂單狀態(tài)以及扣減庫存,而訂單數(shù)據(jù)和庫存數(shù)據(jù)位于不同的數(shù)據(jù)庫中。
此時,更新訂單狀態(tài)、扣減庫存這兩個操作同屬一個事務(wù),兩個操作作為一個執(zhí)行單元不能被分割,要么都執(zhí)行成功,要么都執(zhí)行事物,不允許出現(xiàn)一個成功另一個失敗的情況。
但是,在分布式的架構(gòu)中,網(wǎng)絡(luò)抖動、超時、服務(wù)故障等異常都是不可避免的。
比如,當上面的事物執(zhí)行時,可能訂單狀態(tài)的更新在訂單數(shù)據(jù)庫上執(zhí)行成功,但庫存的扣減因網(wǎng)絡(luò)原因在庫存數(shù)據(jù)庫中執(zhí)行失敗,這樣就造成了數(shù)據(jù)庫中的數(shù)據(jù)和我們期望的業(yè)務(wù)數(shù)據(jù)狀態(tài)不一致。

問題
分布式事務(wù)是一組操作序列,序列中的任意操作都有可能因故障導(dǎo)致該操作執(zhí)行失敗,從而導(dǎo)致數(shù)據(jù)不一致。
又因為,故障會發(fā)生在任意時刻而且是100%會發(fā)生,所以分布式的事務(wù)一定會出現(xiàn)事務(wù)不一致的情況,無論采用哪一種方案。
因此,我們的技術(shù)實現(xiàn)方案便面臨兩個核心問題:第一、如何減少事務(wù)執(zhí)行時發(fā)生故障的概率,第二、故障發(fā)生后出現(xiàn)數(shù)據(jù)不一致的情況如何解決。
方案
針對上面的第一個問題,可行的策略是在執(zhí)行事務(wù)之前,先確保網(wǎng)絡(luò)和數(shù)據(jù)庫都不存在故障,然后再開始執(zhí)行事務(wù)。
2PC(兩階段)、3PC(三階段)、TCC采用的都是這種策略,除了TCC在執(zhí)行失敗時會通過撤銷操作恢復(fù)數(shù)據(jù)的一致性外,其它的方式在事務(wù)執(zhí)行失敗時都沒有任何的恢復(fù)策略,但TCC也不是絕對能保障數(shù)據(jù)會被恢復(fù)。
針對上面的第二個問題,可行的策略便是在故障發(fā)生后,不斷地重試直至成功為主,或最大限度的保障其操作執(zhí)行成功;
基于消息的最終一致性方案便是采用這種策略,相較于上面"快速失敗"式的方案這種方案最大的問題是:數(shù)據(jù)從不一致恢復(fù)到一致的狀態(tài)時,中間間隔的時間可能會過長。
下面我們分別介紹一下上面的幾種方案。
2PC(兩階段)

在兩階段事務(wù)中,有兩個角色:事務(wù)協(xié)調(diào)者、資源管理者。
事務(wù)協(xié)調(diào)者負責控制全局事務(wù)中多個本地事務(wù)的執(zhí)行流程,它會將本地事務(wù)分發(fā)給對應(yīng)的資源管理器并觸發(fā)資源管理器執(zhí)行本地事務(wù)。

兩階段事務(wù)的執(zhí)行流程分成準備和提交兩個階段:
第一階段,主要是確保各個資源管理器(數(shù)據(jù)庫)處于正常狀態(tài),所以,事務(wù)管理器會先"試探"資源管理器:"我開始事務(wù)了,你準備一下?",
如果資源管理器準備就緒,那么它會告知事務(wù)管理器:"我準備好了。",否則資源管理器便處于異常狀態(tài);等所有的資源管理器都有回應(yīng)無論是準備就緒還是異常,事務(wù)處理流程便會進入第二個階段。
第二階段,主要是根據(jù)第一階段事務(wù)的準備結(jié)果來控制事務(wù)的流程,如果第一階段中所有的事務(wù)都準備就緒,那么事務(wù)管理器便會通知資源管理器提交事務(wù),否則取消事務(wù)。
兩階段的實現(xiàn)方式存在以下主要缺點:
同步阻塞:各個本地事務(wù)的執(zhí)行會占用數(shù)據(jù)庫的連接資源,連接的占用時長相當于整個事務(wù)的執(zhí)行時長而不是單個本地事務(wù)的時長。
協(xié)調(diào)者故障:協(xié)調(diào)者如果不是分布式的那么存在單點故障的問題,如果協(xié)調(diào)者在第二階段故障,那么有可能導(dǎo)致數(shù)據(jù)不一致。
數(shù)據(jù)不一致:當各個事務(wù)管理器收到提交指令后,便會將本地事務(wù)提交,但可能出現(xiàn)有些資源管理器在第二階段沒有收到提交指令,那么此時就會造成數(shù)據(jù)不一致情況。
3PC(三階段)

三階段將事務(wù)的執(zhí)行流程分成詢問、準備、提交兩個階段,比兩階段多出來詢問這個階段,其它兩個階段都是相同的。
詢問階段主要作用在于檢測網(wǎng)絡(luò),數(shù)據(jù)庫是否正常,避免直接進入準備階段因網(wǎng)絡(luò)故障導(dǎo)致的后續(xù)的回滾操作。
TCC(柔性事務(wù))

TCC也是一種兩階段事務(wù),它由第一階段的Try,以及第二階段的Cancel、Commit操作構(gòu)成。
TCC相較于2PC、3PC以及最終一致性來說,它的特點是:面向具體業(yè)務(wù)操作的事務(wù),而其它的分布式事務(wù)方案是面向數(shù)據(jù)操作的。
假如,你有一筆訂單支付成功了,現(xiàn)在需要同時更新訂單庫中訂單的狀態(tài),以及扣減庫存庫中商品庫存;
那么,你的訂單服務(wù)、庫存服務(wù)需要向事務(wù)協(xié)調(diào)者提供類似于下面的這些操作:
訂單服務(wù),Try:插入訂單狀態(tài)更新記錄;Confirm:跟新訂單狀態(tài);Cancel:取消訂單跟新;
庫存服務(wù),Try:鎖定庫存;Confirm:扣減庫存;Cancel:撤銷庫存更新;
TCC這種處理方式因為依賴業(yè)務(wù)操作來保障一致性,所以需要針對特定的事務(wù)編寫業(yè)務(wù)接口。
最終一致性
最終一致性方案常見的方案有兩種,分別是基于本地消息表的事務(wù)、基于消息中間件的事務(wù),雖然它們在實現(xiàn)上略微有所差異但基本原理還是一樣的,都采用異步非阻塞和重試技術(shù)來實現(xiàn)的,而且業(yè)務(wù)接口都需要支持冪等。
基于本地消息表

基于本地消息表的最終一致性方案,其實現(xiàn)原理是將分布式轉(zhuǎn)化成本地事務(wù)——將外部的本地事務(wù)轉(zhuǎn)化成一條消息,然后通過定時任務(wù)或消息中間件將消息發(fā)送出去。
基于消息中間件

基于消息中間件的最終一致性方案相較于基于本地消息表的事務(wù),省去了本地消息表、定時任務(wù)、重試,這些操作都由消息中間實現(xiàn)了。
總結(jié)
無論那種分布式事務(wù)實現(xiàn)方案都無法絕對保障數(shù)據(jù)的一致性,實現(xiàn)上的差別主要在數(shù)據(jù)不一致的時間長短、強弱上。
擴展閱讀
架構(gòu)設(shè)計思維篇之結(jié)構(gòu)
架構(gòu)設(shè)計事務(wù)篇之Mysql事務(wù)原理