阿里大牛深入分析分散式柔性事務
ACID
什麼是ACID?
原子性(Atomicity )
一個事務中所有操作都必須全部完成,要麼全部不完成
一致性( Consistency )
在操作過程中不會破壞數據的完整性
拿轉賬為例,A有500元,B有300元,如果在一個事務里A成功轉給B50元,那麼不管並發多少,不管發生什麼,只要事務執行成功了,那麼最後A賬戶一定是450元,B賬戶一定是350元
隔離性或獨立性( Isolation)
事務與事務之間不會互相影響,事務將假定只有它自己在操作資料庫,其它事務不知曉
持久性(Durabilily)
一單事務完成了,那麼事務對數據所做的變更就完全保存在了資料庫中,即使發生停電,系統宕機也是如此
簡稱就是ACID
ACID是如何保證單機事務,比如DB突然斷電如何保證數據一致性?
使用SQL Server來舉例,SQL Server資料庫是由兩個文件組成的,一個資料庫文件和一個日誌文件,通常情況下,日誌文件都要比資料庫文件大很多。資料庫進行任何寫入操作的時候都是要先寫日誌的,同樣的道理,我們在執行事務的時候資料庫首先會記錄下這個事務的redo操作日誌,然後才開始真正操作資料庫,在操作之前首先會把日誌文件寫入磁碟,那麼當突然斷電的時候,即使操作沒有完成,在重新啟動資料庫時候,資料庫會根據當前數據的情況進行undo回滾或者是redo前滾,這樣就保證了數據的強一致性
CAP定理
ACID是針對單庫下保證事務的理論,如果是多庫即分散式下ACID將沒有能力,這時就要使用CAP
什麼是CAP定理?
什麼是CAP?CAP原則或者叫CAP定理,都是指他
如果服務要求高可用性就需要採用分散式模式,來冗餘數據寫多份,寫多份就會帶來一致性問題,一致性問題又會帶來性能問題,那麼就此陷入了無解的死循環;所以只能取之中兩個。
一致性(Consistency):數據是一致更新的,所有數據節點的變動都是同步的,同時發生,同時生效
根據一致性的強弱程度不同,可以將一致性級別5種,參考:zookeeper.noteZookeeper強一致性
可用性(Availability):性能好+可靠性,在集群中一部分節點故障後,集群整體是否還能響應客戶端的讀寫請求
分區容錯性(Partition tolerance):系統可以跨網路分區線性的伸縮和擴展,一個分散式系統裡面,節點組成的網路本來應該是連通的。然而可能因為一些故障,使得有些節點之間不連通了,整個網路就分成了幾塊區域。數據就散布在了這些不連通的區域中。這就叫分區。
分區相當於對通信的時限要求。系統如果不能在時限內達成數據一致性,就意味著發生了分區的故障,必須就當前操作在C和A之間做出選擇
該理論已被證明:任何分散式系統只可同時滿足兩點,無法三者兼顧;所以應該根據應用場景進行適當取捨
當你一個數據項只在一個節點中保存,那麼分區出現後,和這個節點不連通的部分就訪問不到這個數據了。這時分區就是無法容忍的。(分區容錯性差)
要保證一致,每次寫操作就都要等待全部節點寫成功,
而這等待又會帶來可用性的問題,即效率不好,要等嘛。(分區容錯性好,一致性好,可用性就差)
總的來說就是,數據存在的節點越多,分區容忍性越高,但要複製更新的數據就越多,一致性就越難保證。為了保證一致性,更新所有節點數據所需要的時間就越長,可用性就會降低
注意:在分散式系統中,在任何資料庫設計中,一個Web應用至多只能同時支持上面的兩個屬性。顯然,任何橫向擴展策略都要依賴於數據分區。因此,設計人員必須在一致性與可用性之間做出選擇。原因已在上面說明了
Zookeeper保證的是CP,對於服務發現而言,可用性比數據一致性更加重要,而Eureka設計則遵循AP原則
BASE理論
犧牲CAP定理中所說的高一致性,獲得可用性
在分散式系統中,我們往往追求的是可用性,它的重要程序比一致性要高,那麼如何實現高可用性呢?前人已經給我們提出來了另外一個理論,就是BASE理論,它是用來對CAP定理進行進一步擴充的
什麼是BASE理論?
Basically Available(基本業務可用性(支持分區失敗))
Soft state(軟狀態,狀態允許有短時間不同步,非同步)
Eventuallyconsistent(最終一致性(最終數據是一致的,但不是實時一致))
BASE理論是對CAP中的一致性和可用性進行一個權衡的結果,理論的核心思想就是:我們無法做到強一致,但每個應用都可以根據自身的業務特點,採用適當的方式來使系統達到最終一致性(Eventual consistency)
酸鹼平衡(ACID-BASEBalance)
真實系統應當是ACID與BASE的混合體,因此真實系統應當是酸鹼平衡的
DTP模型——暫未做細節了解
X/Open DTP(X/OpenDistributed Transaction Processing Reference Model)是X/Open這個組織定義的一套分散式事務的標準
什麼是分散式事務?
簡單理解,多機事務
多數據源事務,對應多個DB / MQ操作需要保持一致的事務
分散式事務的重要性
隨著微服務分散式架構的使用普及,分散式事務越來越成為一個繞不過去的問題,只要系統使用分散式架構,分散式事務問題或遲或早它就會存在
分散式事務解決方案
>兩階段提交(2PC)
優點:盡量保證了數據的強一致,適合對數據強一致要求很高的關鍵領域
缺點:實現複雜,犧牲了可用性(犧牲了一部分可用性來換取的一致性),對性能影響較大,不適合高並發高性能場景
>補償事務(TCC)
優點:跟2PC比起來,實現以及流程相對簡單了一些,但數據的一致性比2PC也要差一些
缺點:缺點還是比較明顯的,在事務發起方遠程調用事務參與方的confirm、cancel方法中都有可能失敗。TCC屬於應用層的一種補償方式,所以需要程序員在實現的時候多寫很多補償的代碼,在一些場景中,一些業務流程可能用TCC不太好定義及處理
>本地消息表(非同步確保)——遵循BASE理論,採用的是最終一致性
也就是使用獨立消息服務+ MQ實現最終消息一致性,這種實現方式應該是業界使用最多的
這個方案即不會出現像2PC那樣複雜的實現(當調用鏈很長的時候,2PC的可用性是非常低的),
也不會像TCC那樣可能出現確認或者回滾不了的情況
優點:一種非常經典的實現,避免了分散式事務,實現了最終一致性
缺點:消息表會耦合到業務系統中,如果沒有封裝好的解決方案,會有很多雜活需要處理
什麼是剛性事務,什麼是柔性事務?
剛性事務
剛性事務滿足ACID理論
工作在數據資源層,也就是比如資料庫,MQ中間件
實現方式以xa 2pc為代表
柔性事務
柔性事務滿足BASE理論,基本上是可用的,數據最終會一致
工作在數據業務層,也就是比如應用服務、代碼層面上
實現方式以可靠消息最終一致性非同步確保型最大努力通知型TCC補償型為代表
剛性事務的優缺點、特點及適用場景
特點
屬於標準的分散式解決方案,全局事務提交型,
XA 2PC兩階段提交這些方式都會在事務整個過程全局鎖定資源,對資源佔用大
優點
高強度保證ACID
缺點
效率低,也就是性能低,全局資源的鎖定,造成所有參與的數據資源鎖定狀態貫穿整個事務過程,對資源的佔用時間跨度大,成本高,
另外對資源有要求,數據源只有實現了XA規範才能接入,目前並不是所有的資源都支持XA規範
柔性事務的優缺點、特點及適用場景
特點
屬於用代碼控制處理邏輯的方式來保證分散式事務
可靠消息最終一致性非同步確保型最大努力通知型 TCC補償型這些方式基本使用定期詢問、檢查、重發、小粒度鎖等方式實現分散式事務,對資源佔用做到最低
優點
對資源佔用低
缺點
為了實現鬆弛的約束下事務一致,需要添加多種子系統用於定期詢問、檢查、重發等工作,整個系統體積會增大
柔性事務
>柔性事務的服務模式
服務模式屬於在各種分散式事務解決方案中需要實現的一些能力,比如可查詢操作表示要提供查詢介面,冪等操作表示要實現冪等性支持重複消費下不影響業務數據
可查詢操作
冪等操作
TCC操作
兩階段型操作!=兩階段提交協議2PC操作
TCC也屬於一種兩階段型操作
可補償操作
>柔性事務的解決方案
***可靠消息最終一致(非同步確保型)——遵循BASE理論
PS:其實很多分散式事務場景可以通過這種方式實現事務上的最終一致
消息中間件的作用很多,例如非同步通訊、解耦、並發緩衝、流量消峰等,但是傳統的使用方式下消息中間件是不可靠的,原因是發送方MQ監聽方三者是通過網路連接的,有網路的地方就是不穩定的。MQ集群只是解決MQ組件自身的高可用,降低消息進來後消息丟失的風險。
問題描述
MQ是網路連接的,不可靠,業務操作成功後一定要保證消息發出去,並且給對方消費,否則就屬於事務不一致
兩個應用系統A、B,B通過MQ消費A發送的消息
假設A是訂單系統處理完自己的業務後,數據存儲到A-DB然後發送消息到MQ讓會計系統B聯動記錄會計憑證
又假設A做完訂單業務後,發送MQ時因為網路問題沒發成功或者MQ持久化出問題等原因消息丟了,
那麼這一訂單就丟失了會計憑證數據,A和B屬於事務不一致,這是不允許發生的事
有一種說法是先發送MQ消息,成功後回頭再做業務,這問題就更大了,如果消息ok了,回頭業務做失敗了,就等於記錄了會計憑證而沒有實際訂單業務,問題更加嚴重
方案流程(需要引入三個子系統消息狀態確認子系統消息恢復子系統消息服務管理子系統)
先提一個設想,如果我們能先發一個預消息到MQ,但是此時消息狀態是暫存,只有狀態為待發送才能投遞到監聽端去消費,回頭等業務做成功了再修改MQ消息狀態為待發送,這樣MQ才支持投遞消息到消費端去消費,這樣確實可行。但目前開源的常用MQ產品沒有這種功能,好像RocketMQ提供,沒有去確認,開發難度大,因此使用自建獨立消息服務方式進行實現
執行業務前發送預消息到消息服務做消息入庫,此時消息狀態為待發送
業務系統得到消息服務的正確返回後再做業務,業務做完就將業務狀態走dubbo非同步通信方式通知到消息服務,消息服務根據狀態決定消息是發送MQ還是刪除或只是標記消息狀態,業務庫不用記錄發送消息業務的狀態,這裡存在的異常點是可能因為業務處理失敗或者業務處理成功而因為網路原因沒能成功將業務狀態發送給消息服務還或者業務處理成功而調用消息服務發送業務狀態時因消息服務處理時間長而讀取超時等,都會造成消息服務對應的消息庫中堆積大量的消息狀態為待發送的消息,這個異常點的解決都由消息狀態確認子系統在消息服務中定期查詢消息庫中超過指定時間並且消息狀態不是消費成功的消息,再調用業務系統的查詢介面驗證消息對應的業務狀態,此時如果業務成功,就同步消息庫的業務狀態同時發送MQ,此時消息狀態為發送中,如果業務失敗,根據業務決定是否保留消息,這裡一定要考慮數據增長問題,提前做好拆分的設計,使用分區、分表等方式在數據量龐大的時候也能快速讀寫,不影響性能
如果消息服務受到業務系統的業務狀態是成功,那麼馬上將對應消息發送給MQ,以RabbitMQ為例,這裡消息服務和MQ的交互可能遇上這幾類問題:
1、消息直接無法到達MQ
比如網路斷了就會引起,這時直接走時間階梯等待後重發即可,如果出現connectionclosed錯誤,直接增加connection數即可
connectionFactory.setChannelCacheSize(100);
2、消息已經發送到達MQ,但返回的時候出現異常
rabbitmq提供了確認ack機制,可以用來確認消息是否有返回,因此可以根據發送回執ack決定是否要時間階梯重發
/**confirmcallback用來確認消息是否有送達消息隊列*/rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
如果消息沒有到exchange,則ack=false
如果消息到達exchange,則ack=true
如果發送時根本找不到exchange,則會觸發returncallback函數if (!ack) { //調用時間階梯重發邏輯} else { //可以記錄消息服務中的消息狀態為投遞成功,此時基本認為消息會被監聽方消費掉,監聽方拿到消息就
//auto簽收掉,如果消費失敗,就寄希望於消息狀態確認子系統下一次再將消息發送到mq中} });/**若消息找不到對應的Exchange會觸發returncallback */rabbitTemplate.setReturnCallback((message, replyCode, replyText, tmpExchange,tmpRoutingKey) -> { try { Thread.sleep(Constants.ONE_SECOND); } catch(InterruptedException e) { e.printStackTrace(); } log.info("send messagefailed: " + replyCode + " " + replyText);
//等待一段時間,再發rabbitTemplate.send(message); });
3、消息送達後,消息服務自己掛了
可能消息發送到MQ成功後,正當回傳給消息服務時,消息服務自己掛了,導致監聽方可能消費成功而消息服務還認為發送失敗處於待發送狀態
這其實沒有關係,反正監聽方是冪等的,哪怕消息服務重發消息,也不會出問題,而且監聽方消費成功後會直接跨調消息服務告訴這條消息消費成功,同樣可以覆蓋掉這個問題,並沒有關係,不用處理
4、監聽方消費失敗
這就依靠於消息狀態確認子系統重發消息解決就好了
然後講MQ監聽方環節,監聽方拿到消息就auto簽收,MQ消息隨即remove,監聽方調具體的消費服務,這裡監聽方相當於是一個MQ的監聽網關,自身可以做更多的服務編排工作,具體消費調用後面的消費服務,消費服務處理成功後監聽方反過來調用消息服務,標記消息成功消費,如果消費服務出了任何問題包括網路問題導致消費失敗
都由消息恢復子系統定時將消息庫中超時的並且狀態為發送中的消息拿出來再丟到MQ中,等於是重發機制
每次重發要比之前的間隔更長,提供可配置的時間階梯,這樣更加有利於等待被動方系統故障恢復,消費服務一定要是冪等的,這樣重複消費也沒問題,如果消息超過重發次數就移到死亡隊列表中,這裡可以添加消息服務管理子系統查看死亡消息有多少,可能有些是因為消費方系統故障原因,Bug修復後可以操作重發這些死亡消息,也可以針對某一類型的死亡消息批量重發等等,如果監聽方這邊有性能問題,可以考慮用redis記錄消息狀態,這樣監聽方校驗消息是否消費成功就更高效
最後一點,業務方對消息服務dubbo發送業務狀態需要使用非同步方式,因為如果消息服務處理成功後因為網路返回超時,將會導致業務方事務回滾,這樣就不一致了,注意預發送消息不能用非同步,因為它不僅證明此時鏈路沒問題,而且它在消費服務中的結果要為後面的業務服務的。綜上所述業務方調消息放要使用dubbo的非同步方式
這樣就能實現在不改造困難大的MQ的前提下實現可靠消息最終一致,
缺點或者不足方面:各調度子系統周期觸發間的數據不一致延遲,及使用了多個服務和資料庫有不少的開發成本增加了系統的體積,但它是一勞永逸,復用的
注意點及優化建議
>如果業務方和消費方是這種訂單-快遞的場景,調用方式就是1:1,消費方性能問題倒還好,如果壓力大可考慮消息服務對應的庫和消費方的消息庫使用redis等內存庫,如果用redis,特別注意,寧願犧牲大部分性能也要配置aof持久化為always,最大程度保證數據不丟
>消息服務對應的消息表一定要考慮數據增長問題,提前做好拆分的設計,使用分區、分表等方式在數據量龐大的時候也能快速讀寫,不影響性能
>業務系統傳遞業務操作狀態給消息服務時,一定要用非同步方式,比如是dubbo協議就用dubbo非同步通信
>如果消息監聽消費服務中為了實現冪等而開銷比較大的話,可以考慮在消費端資料庫中也增加一份消息表,記錄消息處理狀態,消費時直接獲取消息狀態就能決定知否直接返回結果
>各種消息子系統其實都是定時任務,調度,這裡建議使用分散式定時框架技術
***最大努力通知(定期校對,通知型)
和可靠消息最終一致方案針對解決的問題一樣,可靠消息最終一致方案最終數據會一致,最多只是存在時間上的延遲而已,最大努力通知方案的思想是儘可能的進行多次通知被動方系統,不保證被動方數據一定會一致,同時不管被動方數據是否一致反正主動方數據變更後就不動了,最多只是定期或者每天提供一份數據對賬的途經,可能是http查詢介面,可能是下載對賬文件供被動方ftp拿下來對賬,以便被動方同步數據,糾正不一致的錯誤數據
適合的場景
首先他不適合那種被動方對數據一致性有較高強度要求的場景,因為有可能通知不到被動方造成數據不一致
同時主動方系統一定要有數據標準的能力,因為兩邊出現差異時都是以主動方數據為準進行同步修復的
適合用在對於業務最終一致性的時間敏感度低的場景下,也就是說能忍受一段時間內可能一天的數據不一致,同時比較適合跨企業級的系統,因為不太可能進行大量改造添加組件來滿足一致性的要求,銀行日清對賬文件就是這種方案的體現
方案流程
主動方處理完業務流程後通過服務http方式或者MQ通知被動方,如果失敗再按照設置的時間階梯型通知規則,N次失敗後記錄成死亡消息,同時主動方也要為被動方提供業務數據的查詢介面或者每個周期後產生一個對賬文件,提供給被動方用於數據不一致時進行校正,被動方也是一樣要實現冪等
***TCC(兩階段型、補償型)
TCC其實就是採用的補償機制,其核心思想是:針對每個操作,都要註冊一個與其對應的確認和補償(撤銷)操作
TCC分別指的是trying、confirming、canceling三個階段
Try是先把多個應用中的業務資源預留和鎖定住,為後續的確認提交打下基礎
Confirm是將Try操作中涉及的所有應用的所有鎖定資源全部提交處理
Cancel是將Try操作中涉及的所有應用的所有鎖定資源全部回滾處理
TRYING階段只要完全執行成功,默認就是要高強度能保證CONFIRMING階段不能出錯,
也就是說要在業務邏輯層面在trying環節盡最大努力做到各種檢查和鎖定,以確保confirming不出問題
TCC三個過程就像資料庫對事務的處理過程,對應資料庫的lock、commit、rollback
適合的場景
TCC方案對事務控制的特點是准實時的,適合用在對實時性要求較高的場景下
比如訂單支付環節,用戶一旦下單付款成功,訂單系統的訂單狀態將馬上顯示支付完成,同時對應的賬戶系統的賬戶餘額就要對應減少,不能出現太長的先後順序,也不能允許因為任何環節出錯而等待消息重發、對賬等方式再進行數據同步修復,這樣的話,這一段時間的數據將不一致,這種場景下是不能允許的
上面這種場景就不適合可靠消息最終一致性方案,使用TCC比較適合
TCC方案的優點/特點
對資源的鎖定程度較少,儘可能的直接將需要的數據部分划出去鎖定起來,這是軟鎖,與XA兩階段提交做全局鎖的方案是不同的——這將直接在數據資源層面形成等待、阻塞,引起吞吐能力下降,造成系統性能不好
TCC事務的缺點
TCC的Try、Confirm和Cancel操作功能需業務提供,並且開發成本高
TCC方案的原理——個人總結
TCC實質上是應用層的2PC(2 PhaseCommit,兩階段提交),好比把XA兩階段提交那種在數據資源層做的事務管理工作提到了數據應用層,每個應用可以看作一個資源管理器,他們的工作對應如下的描述
1、Try:嘗試執行業務
完成所有業務檢查(一致性)
預留鎖定必須的業務資源(准隔離性)
2、Confirm:確認執行業務
不再做業務檢查
記錄成功,並只使用Try階段預留鎖定的業務資源做確認操作
默認Confirm階段是不會出錯的。即:只要Try成功,Confirm一定得成功,也就是只能通過在Try階段進行強有力的邏輯控制,保障Confirm不出事
3、Cancel:取消執行業務
記錄回滾,並釋放Try階段預留鎖定的業務資源
一個完整的TCC事務參與方包括三部分
主業務服務
主業務服務為整個業務活動的發起方,如訂單支付中訂單支付系統屬於主業務服務,其他的積分、賬戶都屬於從業務服務
從業務服務
從業務服務負責提供TCC業務操作,是整個業務活動的操作方。從業務服務必須實現Try、Confirm和Cancel三個介面,供主業務服務調用。由於Confirm和Cancel操作可能被重複調用,故要求Confirm和Cancel兩個介面必須是冪等的
業務活動管理器
業務活動管理器管理控制整個業務活動,包括記錄維護TCC全局事務的事務狀態和每個從業務服務的子事務狀態,在業務活動提交時確認所有的TCC型操作的confirm操作,
在業務活動取消時調用所有TCC型操作的cancel操作
整個TCC事務對於主業務服務來說是透明的,其中業務活動管理器和從業務服務各自幹了一部分工作
一個案例理解
接下來將以賬務拆分為例,對TCC事務的流程做一個描述,業務場景如下,
分別位於三個不同分庫的帳戶A、B、C,A和B一起向C轉帳共80元:
1、Try:嘗試執行業務
完成所有業務檢查(一致性):檢查A、B、C的帳戶狀態是否正常,帳戶A的餘額是否不少於30元,帳戶B的餘額是否不少於50元。
預留必須業務資源(准隔離性):帳戶A的凍結金額增加30元,帳戶B的凍結金額增加50元,這樣就保證不會出現其他並發進程扣減了這兩個帳戶的餘額而導致在後續的真正轉帳操作過程中,帳戶A和B的可用餘額不夠的情況。
2、Confirm:確認執行業務
真正執行業務:如果Try階段帳戶A、B、C狀態正常,且帳戶A、B餘額夠用,則執行帳戶A給賬戶C轉賬30元、帳戶B給賬戶C轉賬50元的轉帳操作。
不做任何業務檢查:這時已經不需要做業務檢查,Try階段已經完成了業務檢查。
只使用Try階段預留的業務資源:只需要使用Try階段帳戶A和帳戶B凍結的金額即可。
3、Cancel:取消執行業務
釋放Try階段預留的業務資源:如果Try階段部分成功,比如帳戶A的餘額夠用,且凍結相應金額成功,帳戶B的餘額不夠而凍結失敗,則需要對帳戶A做Cancel操作,將帳戶A被凍結的金額解凍掉。
冪等性的實現方式
1、通過唯一鍵值做處理,即每次調用的時候傳入唯一鍵值,通過唯一鍵值判斷業務是否被操作,如果已被操作,則不再重複操作
2、通過狀態機處理,給業務數據設置狀態,通過業務狀態判斷是否需要重複執行
如果你對Java大型伺服器的分散式拆分、高並發及高可用設計、動態高擴展、深入淺出JVM調優。存儲層高可用設計。DB級資料庫性能調優、Spring生態圈技術深入研究,MyBatis源碼層理解,Netty源碼分析和大數據等多個知識點感興趣可以加我高級架構進階群,群內有多位大牛包括阿里P7級,大家一起交流分享


※MySQL「必知必會」的36個知識點
※DB2資料庫雙活方案設計要點
TAG:架構師技術聯盟 |