分布式事務(wù)解決方案Seata之Saga模式
背景:
?????? 隨著各行各業(yè)數(shù)字化轉(zhuǎn)型的深入,以及技術(shù)的持續(xù)迭代更新,互聯(lián)網(wǎng)公司的技術(shù)也逐漸落地到傳統(tǒng)行業(yè),例如金融業(yè)。由于業(yè)務(wù)的快速增長以及為了快速搶占市場等因素,以前單體架構(gòu)的應(yīng)用變得不再符合需求。因此,由以前的傳統(tǒng)單體架構(gòu)的系統(tǒng)或應(yīng)用,演變成面向服務(wù)架構(gòu)的分布式系統(tǒng)。
?????? 構(gòu)建分布式系統(tǒng),除了根據(jù)業(yè)務(wù)劃分服務(wù)等偏業(yè)務(wù)問題,還有隨之而來的技術(shù)難點(diǎn),例如分布式事務(wù)。在金融行業(yè)中,業(yè)務(wù)系統(tǒng)往往需要聚合多個(gè)下游系統(tǒng)或者多個(gè)本系統(tǒng)服務(wù),這種長事務(wù)如何保證數(shù)據(jù)一致性。市面上有許多解決方案,但是阿里開源的分布式事務(wù)解決方案seata是最優(yōu)秀和最知名的,其中的saga模式即可解決長事務(wù)場景。
?
分布式事務(wù)
???? 1.事務(wù)
????????? 事務(wù)是指由一組操作組成的一個(gè)工作單元,這個(gè)工作單元具有原子性(atomicity)、一致性(consistency)、隔離性(isolation)和持久性(durability)。
????????原子性:執(zhí)行單元中的操作要么全部執(zhí)行成功,要么全部失敗。如果有一部分成功一部分失敗那么成功的操作要全部回滾到執(zhí)行前的狀態(tài)。
????????一致性:執(zhí)行一次事務(wù)會使用數(shù)據(jù)從一個(gè)正確的狀態(tài)轉(zhuǎn)換到另一個(gè)正確的狀態(tài),執(zhí)行前后數(shù)據(jù)都是完整的。
????????隔離性:在該事務(wù)執(zhí)行的過程中,任何數(shù)據(jù)的改變只存在于該事務(wù)之中,對外界沒有影響,事務(wù)與事務(wù)之間是完全的隔離的。只有事務(wù)提交后其它事務(wù)才可以查詢到最新的數(shù)據(jù)。
????????持久性:事務(wù)完成后對數(shù)據(jù)的改變會永久性的存儲起來,即使發(fā)生斷電宕機(jī)數(shù)據(jù)依然在。
???? 2.分布式事務(wù)
?????? 在分布式系統(tǒng)中一次操作由多個(gè)系統(tǒng)協(xié)同完成,這種一次事務(wù)操作涉及多個(gè)系統(tǒng)通過網(wǎng)絡(luò)協(xié)同完成的過程稱為分布式事務(wù)。這里強(qiáng)調(diào)的是多個(gè)系統(tǒng)通過網(wǎng)絡(luò)協(xié)同完成一個(gè)事務(wù)的過程,并不強(qiáng)調(diào)多個(gè)系統(tǒng)訪問了不同的數(shù)據(jù)庫,即使多個(gè)系統(tǒng)訪問的是同一個(gè)數(shù)據(jù)庫也是分布式事務(wù)。
???? 3.分布式事務(wù)有哪些場景
????????? 電商系統(tǒng)中的下單扣庫存場景,在電商系統(tǒng)中,訂單系統(tǒng)和庫存系統(tǒng)是兩個(gè)系統(tǒng),一次下單的操作由兩個(gè)系統(tǒng)協(xié)同完成。
?????????金融系統(tǒng)中的銀行卡充值場景,在金融系統(tǒng)中通過銀行卡向平臺充值需要通過銀行系統(tǒng)和金融系統(tǒng)協(xié)同完成。
?????????教育系統(tǒng)中下單選課業(yè)務(wù)場景,在線教育系統(tǒng)中,用戶購買課程,下單支付成功后學(xué)生選課成功,此事務(wù)由訂單系統(tǒng)和選課系統(tǒng)協(xié)同完成。
?????????SNS系統(tǒng)的消息發(fā)送場景,在社交系統(tǒng)中發(fā)送站內(nèi)消息同時(shí)發(fā)送手機(jī)短信,一次消息發(fā)送由站內(nèi)消息系統(tǒng)和手機(jī)通信系統(tǒng)協(xié)同完成。
?
Seata
?????? Seata 是一款開源的分布式事務(wù)解決方案,致力于提供高性能和簡單易用的分布式事務(wù)服務(wù)。Seata 將為用戶提供了 AT、TCC、SAGA 和 XA 事務(wù)模式,為用戶打造一站式的分布式解決方案。
?
Saga模式
?????? 概述:Saga模式是SEATA提供的長事務(wù)解決方案,在Saga模式中,業(yè)務(wù)流程中每個(gè)參與者都提交本地事務(wù),當(dāng)出現(xiàn)某一個(gè)參與者失敗則補(bǔ)償前面已經(jīng)成功的參與者,一階段正向服務(wù)和二階段補(bǔ)償服務(wù)都由業(yè)務(wù)開發(fā)實(shí)現(xiàn)。
??????

?????? 從上圖可知,每個(gè)正常節(jié)點(diǎn)事務(wù),對應(yīng)一個(gè)補(bǔ)償節(jié)點(diǎn)事務(wù),一個(gè)事務(wù)可由多個(gè)節(jié)點(diǎn)組成,在某個(gè)節(jié)點(diǎn)事務(wù)失敗,補(bǔ)償事務(wù)從失敗節(jié)點(diǎn)對應(yīng)補(bǔ)償節(jié)點(diǎn)執(zhí)行,將此前成功的事務(wù)全部補(bǔ)償,保證數(shù)據(jù)最終一致。
適用場景:
?????? ●業(yè)務(wù)流程長、業(yè)務(wù)流程多
?????? ●參與者包含其它公司或遺留系統(tǒng)服務(wù),無法提供 TCC 模式要求的三個(gè)接口
優(yōu)勢:
?????? ●一階段提交本地事務(wù),無鎖,高性能
?????? ●事件驅(qū)動架構(gòu),參與者可異步執(zhí)行,高吞吐
?????? ●補(bǔ)償服務(wù)易于實(shí)現(xiàn)
缺點(diǎn):
?????? ●不保證隔離性
?
Saga實(shí)現(xiàn):
基于狀態(tài)機(jī)引擎的 Saga 實(shí)現(xiàn):
目前SEATA提供的Saga模式是基于狀態(tài)機(jī)引擎來實(shí)現(xiàn)的,機(jī)制是:
通過狀態(tài)圖來定義服務(wù)調(diào)用的流程并生成 json 狀態(tài)語言定義文件
狀態(tài)圖中一個(gè)節(jié)點(diǎn)可以是調(diào)用一個(gè)服務(wù),節(jié)點(diǎn)可以配置它的補(bǔ)償節(jié)點(diǎn)
狀態(tài)圖 json 由狀態(tài)機(jī)引擎驅(qū)動執(zhí)行,當(dāng)出現(xiàn)異常時(shí)狀態(tài)引擎反向執(zhí)行已成功節(jié)點(diǎn)對應(yīng)的補(bǔ)償節(jié)點(diǎn)將事務(wù)回滾
注意: 異常發(fā)生時(shí)是否進(jìn)行補(bǔ)償也可由用戶自定義決定
可以實(shí)現(xiàn)服務(wù)編排需求,支持單項(xiàng)選擇、并發(fā)、子流程、參數(shù)轉(zhuǎn)換、參數(shù)映射、服務(wù)執(zhí)行狀態(tài)判斷、異常捕獲等功能
?????? 如下示例狀態(tài)圖:
?

狀態(tài)機(jī)設(shè)計(jì)器
?????? Seata Saga 提供了一個(gè)可視化的狀態(tài)機(jī)設(shè)計(jì)器方便用戶使用,代碼和運(yùn)行指南請參考:https://github.com/seata/seata/tree/develop/saga/seata-saga-statemachine-designer,外網(wǎng)用戶可通過如下地址設(shè)計(jì):
狀態(tài)機(jī)設(shè)計(jì)器演示地址:http://seata.io/saga_designer/index.html
?
Saga 服務(wù)設(shè)計(jì)的實(shí)踐經(jīng)驗(yàn)
允許空補(bǔ)償
空補(bǔ)償:原服務(wù)未執(zhí)行,補(bǔ)償服務(wù)執(zhí)行了
出現(xiàn)原因:
原服務(wù) 超時(shí)(丟包)
Saga 事務(wù)觸發(fā) 回滾
未收到 原服務(wù)請求,先收到 補(bǔ)償請求
所以服務(wù)設(shè)計(jì)時(shí)需要允許空補(bǔ)償, 即沒有找到要補(bǔ)償?shù)臉I(yè)務(wù)主鍵時(shí)返回補(bǔ)償成功并將原業(yè)務(wù)主鍵記錄下來
防懸掛控制
懸掛:補(bǔ)償服務(wù) 比 原服務(wù) 先執(zhí)行
出現(xiàn)原因:
原服務(wù) 超時(shí)(擁堵)
Saga 事務(wù)回滾,觸發(fā) 回滾
擁堵的 原服務(wù) 到達(dá)
所以要檢查當(dāng)前業(yè)務(wù)主鍵是否已經(jīng)在空補(bǔ)償記錄下來的業(yè)務(wù)主鍵中存在,如果存在則要拒絕服務(wù)的執(zhí)行
冪等控制
原服務(wù)與補(bǔ)償服務(wù)都需要保證冪等性, 由于網(wǎng)絡(luò)可能超時(shí), 可以設(shè)置重試策略,重試發(fā)生時(shí)要通過冪等控制避免業(yè)務(wù)數(shù)據(jù)重復(fù)更新
缺乏隔離性的應(yīng)對
由于 Saga 事務(wù)不保證隔離性, 在極端情況下可能由于臟寫無法完成回滾操作, 比如舉一個(gè)極端的例子, 分布式事務(wù)內(nèi)先給用戶A充值, 然后給用戶B扣減余額, 如果在給A用戶充值成功, 在事務(wù)提交以前, A用戶把余額消費(fèi)掉了, 如果事務(wù)發(fā)生回滾, 這時(shí)則沒有辦法進(jìn)行補(bǔ)償了。這就是缺乏隔離性造成的典型的問題, 實(shí)踐中一般的應(yīng)對方法是:
業(yè)務(wù)流程設(shè)計(jì)時(shí)遵循“寧可長款, 不可短款”的原則, 長款意思是客戶少了錢機(jī)構(gòu)多了錢, 以機(jī)構(gòu)信譽(yù)可以給客戶退款, 反之則是短款, 少的錢可能追不回來了。所以在業(yè)務(wù)流程設(shè)計(jì)上一定是先扣款。
有些業(yè)務(wù)場景可以允許讓業(yè)務(wù)最終成功, 在回滾不了的情況下可以繼續(xù)重試完成后面的流程, 所以狀態(tài)機(jī)引擎除了提供“回滾”能力還需要提供“向前”恢復(fù)上下文繼續(xù)執(zhí)行的能力, 讓業(yè)務(wù)最終執(zhí)行成功, 達(dá)到最終一致性的目的。
?
Saga示例
????? 示例說明:
?????? 本示例使用的技術(shù)棧為螞蟻金服的開源版sofa框架、druid、mysql、sofaboot和Seata。實(shí)現(xiàn)扣減金額同時(shí)扣減庫存需求。例如,扣減一個(gè)庫存同時(shí)扣減一塊錢。Saga實(shí)現(xiàn)里的狀態(tài)機(jī)示例圖為本demo的示例圖。
?????? 環(huán)境準(zhǔn)備:啟動seata-sever,官網(wǎng)可下載,啟動zookeeper。
?????? 工程結(jié)構(gòu)如下:
?????????????

????????????? 其中狀態(tài)機(jī)使用json文件在statelang目錄下,provider下是服務(wù)提供文件,將服務(wù)注冊到zk,consumer是消費(fèi)端文件。
?????? 數(shù)據(jù)庫表如下:
??????

?????? 其中seata狀態(tài)機(jī)初始化和運(yùn)行三張表,業(yè)務(wù)處理的庫存表和金額表。
?????? 正常運(yùn)行示例如下:
?????? 首先啟動SofaRpcSagaProviderApplication類,將服務(wù)注冊到zk上,然后啟動starter木下的消費(fèi)類。


?????? 扣減金額時(shí)異常情況,開始補(bǔ)償扣減金額事務(wù),再執(zhí)行補(bǔ)償庫存扣減事務(wù),金額和庫存數(shù)據(jù)均無變化。


此demo均已上傳github,地址為:https://github.com/zbf2016/sofa-seata-saga-example.git
