一、分布式事務
分布式事務是指事務的參與者、支持事務的服務器、資源服務器以及事務管理器分別位于不同的分布式系統的不同節(jié)點之上。
二、什么時候需要用到分布式事務
就是指不是單個服務或者單個數據庫架構下產生的事務,例如:
- 跨數據源的分布式事務
- 跨服務的分布式事務
三、分布式理論
CAP定理
在一個分布式系統中,以下三點特性無法同時滿足,「魚與熊掌不可兼得」
- 一致性(C):在分布式系統中的所有數據備份,「在同一時刻是否擁有同樣的值」 。(等同于所有節(jié)點訪問同一份最新的數據副本)
- 可用性(A):系統能夠在任何時候處理請求并返回非錯誤響應,即系統對于用戶請求是可用的。
- 分區(qū)容錯性(P):系統能夠容忍網絡中的分區(qū)故障,即網絡中的節(jié)點之間的通信可能被延遲或丟失。
具體地講在分布式系統中,在任何數據庫設計中,一個Web應用「至多只能同時支持上面的兩個屬性」 。顯然,任何橫向擴展策略都要依賴于數據分區(qū)。因此,設計人員必須在一致性與可用性之間做出選擇。
BASE理論
BASE理論是對CAP中AP的一個擴展,通過犧牲強一致性來獲得可用性,當出現故障允許部分不可用但要保證 核心功能可用,允許數據在一段時間內是不一致的,但最終達到一致狀態(tài)
。滿足BASE理論的事務,我們稱之為 “柔性事務
”。
- 基本可用「Basically Available(基本可用)」:分布式系統在出現故障時,允許損失部分可用功能,保證核心功能可用。如,電商網站交易付款出 現問題了,商品依然可以正常瀏覽。
- 軟狀態(tài)「Soft state(軟狀態(tài))」:由于不要求強一致性,所以BASE允許系統中存在中間狀態(tài)(也叫軟狀態(tài)),這個狀態(tài)不影響系統可用 性,如訂單的"支付中"、“數據同步中”等狀態(tài),待數據最終一致后狀態(tài)改為“成功”狀態(tài)。
- 最終一致「Eventually consistent(最終一致性)」:最終一致是指經過一段時間后,所有節(jié)點數據都將會達到一致。如訂單的"支付中"狀態(tài),最終會變 為“支付成功”或者"支付失敗",使訂單狀態(tài)與實際交易結果達成一致,但需要一定時間的延遲、等待。
事務有剛性事務和柔性事務之分。
剛性事務(如單數據庫中的本地事務)完全遵循 ACID 規(guī)范,即數據庫事務正確執(zhí)行的四個基本要素:
- 原子性(Atomicity)
- 一致性(Consistency)
- 隔離性(Isolation)
- 持久性(Durability)
柔性事務,主要就是只分布式事務了,柔性事務為了滿足可用性、性能與降級服務的需要,降低一致性(Consistency)與隔離性(Isolation)的要求,遵守 BASE 理論:
- 基本業(yè)務可用性(Basic Availability)
- 柔性狀態(tài)(Soft state)
- 最終一致性(Eventual consistency)
四、分布式事務解決方案
分布式事務解決方案大致分為如下3種:
- 剛性事務
- 2PC
- 3PC
- 補償事務
- TCC
- 基于消息隊列的最終一致性
- 最大努力通知
- 本地消息表
- 消息事務
剛性事務
2PC
2PC也就是兩階段提交:
- 第一階段:準備階段
- 第二階段:提交階段
第一階段:
- 協調者 向所有的 參與者 發(fā)送事務預處理請求,稱之為Prepare,并開始等待各 參與者 的響應。
- 各個 參與者 節(jié)點執(zhí)行本地事務操作,但在執(zhí)行完成后并不會commit數據庫本地事務,而是先向 協調者 報告說:我準備好提交了Yes或者我沒準備好弄no
第一階段執(zhí)行完后,會有兩種可能。1、所有都返回Yes. 2、有一個或者多個返回No。
第二階段:
- 如果所有參與者都返回yes,那么協調者向所有參與者發(fā)送commit命令,參與者都本地commit,在完成提交之后釋放整個事務執(zhí)行期間占用的事務資源。
- 如果其中有參與者返回no或者超時沒有返回,則協調者向所有參與者發(fā)送rollback請求,也就是撤銷,將本地事務回滾,不commit。
3PC
3PC把2PC的準備階段再次一分為二,這樣三階段提交就有CanCommit、PreCommit、DoCommit三個階段。
第一階段(CanCommit):
- 事務詢問 協調者向參與者發(fā)送CanCommit請求。詢問是否可以執(zhí)行事務提交操作。然后開始等待參與者的響應。
- 響應反饋 參與者接到CanCommit請求之后,正常情況下,如果其自身認為可以順利執(zhí)行事務,則返回Yes響應,并進入預備狀態(tài)。否則反饋No
第二階段(PreCommit):
根據參與者返回的請求,來決定是否進入PreCommit
-
假如協調者從所有的參與者獲得的反饋都是Yes響應,那么就會執(zhí)行事務的預執(zhí)行。
1.發(fā)送預提交請求 發(fā)送一個prepare-commit請求,進入PreCommit狀態(tài)
2.事務預提交 參與者接收到PreCommit請求后,會執(zhí)行事務操作,并將undo和redo信息記錄到事務日志中。
3.響應反饋 如果參與者成功的執(zhí)行了事務操作,則返回ACK響應,同時開始等待最終指令。 -
假如有任何一個參與者向協調者發(fā)送了No響應,或者等待超時之后,協調者都沒有接到參與者的響應,那么就執(zhí)行事務的中斷。
1.發(fā)送中斷請求 協調者向所有參與者發(fā)送abort請求。
2.中斷事務 參與者收到來自協調者的abort請求之后(或超時之后,仍未收到協調者的請求),執(zhí)行事務的中斷。
第三階段(DoCommit):
進行真正的事務提交
-
執(zhí)行提交
1.發(fā)送提交請求 協調接收到參與者發(fā)送的ACK響應,那么他將從預提交狀態(tài)進入到提交狀態(tài)。并向所有參與者發(fā)送doCommit請求。
2.事務提交 參與者接收到doCommit請求之后,執(zhí)行正式的事務提交。并在完成事務提交之后釋放所有事務資源。
3.響應反饋 事務提交完之后,向協調者發(fā)送Ack響應。
4.完成事務 協調者接收到所有參與者的ack響應之后,完成事務。 -
中斷事務 協調者沒有接收到參與者發(fā)送的ACK響應(可能是接受者發(fā)送的不是ACK響應,也可能響應超時),那么就會執(zhí)行中斷事務。
1.發(fā)送中斷請求 協調者向所有參與者發(fā)送abort請求
2.事務回滾 參與者接收到abort請求之后,利用其在階段二記錄的undo信息來執(zhí)行事務的回滾操作,并在完成回滾之后釋放所有的事務資源。
3.反饋結果 參與者完成事務回滾之后,向協調者發(fā)送ACK消息
4.中斷事務 協調者接收到參與者反饋ACK消息之后,執(zhí)行事務的中斷。
2PC和3PC對比
-
阻塞問題:2PC在第二階段(提交階段)可能會出現阻塞,因為協調者需要等待所有參與者的響應才能決定是否提交事務。這種阻塞可能導致整個系統的性能下降。3PC通過引入預提交階段,可以在某些情況下減少阻塞問題,使得參與者可以在預提交階段確認是否可以提交事務。
-
可靠性和一致性:2PC和3PC都是剛性事務解決方案,強調事務的強一致性和可靠性。在2PC中,所有參與者在接收到準備請求后都將執(zhí)行事務,并在接收到提交請求時進行事務的提交。在3PC中,預提交階段可以使得參與者在準備階段時就能夠拒絕事務,從而增加了可靠性和靈活性。
-
單點故障:2PC中存在單點故障問題,即協調者的故障可能導致整個事務無法完成。3PC通過引入預提交階段來減少單點故障的影響,當協調者故障時,參與者可以根據預提交消息自行決策是否提交事務。
總的來說,3PC相對于2PC引入了預提交階段,以減少阻塞問題和單點故障的影響,增加了系統的可靠性和靈活性。然而,3PC也帶來了額外的復雜性和通信開銷。在實際應用中,需要根據具體的場景和需求來選擇2PC還是3PC作為分布式事務解決方案。
TCC
TCC分別是Try、Confirm、Cancle的簡稱。大致包含2個節(jié)點,一是Try,二是Confirm/Cancle。
- Try 階段(一階段):嘗試執(zhí)行,完成所有業(yè)務檢查(一致性), 預留必須業(yè)務資源(占庫存)。
- Confirm 階段(二階段):確認執(zhí)行真正執(zhí)行業(yè)務,不作任何業(yè)務檢查,只使用 Try 階段預留的業(yè)務資源,Confirm 操作滿足需要滿足冪等性,Confirm 執(zhí)行失敗后需要進行重試。
- Cancel 階段:取消執(zhí)行,釋放 Try 階段預留的業(yè)務資源,Cancel 操作也需要滿足冪等性。Cancel 階段的異常和 Confirm 階段異常處理方案基本上一致。
TCC的優(yōu)缺點:
優(yōu)點:
-
靈活性:TCC允許開發(fā)人員在業(yè)務邏輯中顯式地定義事務的嘗試(Try)、確認(Confirm)和取消(Cancel)階段。這種顯式的編程模型使得開發(fā)人員可以更加精確地控制事務的行為,適應各種復雜的業(yè)務場景。
-
高并發(fā)性:TCC的嘗試階段通常是在本地執(zhí)行,不涉及資源鎖定或網絡通信。這使得嘗試階段可以以高并發(fā)的方式執(zhí)行,提高了系統的吞吐量和性能。
-
可擴展性:TCC對于分布式系統的擴展性較好。每個參與者負責自己的事務邏輯,可以獨立地進行水平擴展,減少了對中心化事務協調器的依賴。
-
容錯性:TCC可以通過取消階段來處理各種異常情況,如參與者失敗、網絡故障或超時。在出現異常時,可以執(zhí)行取消操作,回滾之前的嘗試操作,確保數據的一致性。
缺點:TCC 的 Try、Confirm 和 Cancel 操作功能要按具體業(yè)務來實現,業(yè)務耦合度較高,提高了開發(fā)成本。
基于消息隊列的最終一致性
最大努力通知
所謂最大努力通知,換句話說就是并不保證100%通知到。這種分布式事務的方案,通常也是借助異步消息進行通知的。
發(fā)送者將消息發(fā)送給消息隊列,接收者從消息隊列中消費消息。在這個過程中,如果出現了網絡通信故障或者消息隊列發(fā)生了故障,就有可能導致消息傳遞失敗,即消息被丟失。因此,最大努力通知無法保證每個接收者都能成功接收到消息,但是可以盡最大努力去通知。
下面是一個簡單的例子來說明最大努力通知的過程。假設有一個在線商城系統,需要將訂單狀態(tài)更新的消息通知給相關的用戶,讓他們及時了解訂單狀態(tài)的變化。商城系統通過一個消息隊列將消息發(fā)送給用戶,用戶從消息隊列中消費消息。具體的流程如下:
- 商城系統產生了一個訂單狀態(tài)變化的消息,將消息發(fā)送到消息隊列中
- 用戶從消息隊列中獲取到該消息,更新訂單狀態(tài),并顯示最新的狀態(tài)給用戶.
- 如果消息傳遞成功,就完成了整個通知過程。但是如果出現了消息傳遞失敗的情況,比如消息隊列發(fā)生了故障,那么就需要盡最大努力去通知。
- 商城系統會定期檢查消息隊列中是否存在未被消費的消息,如果存在就重新發(fā)送該消息,直到消息被消費為
需要注意的是,在最大努力通知的過程中,可能會出現消息重復發(fā)送的情況,也可能會出現消息丟失的情況。因此,在設計最大努力通知系統時,需要根據實際業(yè)務需求和風險承受能力來確定最大努力通知的策略和重試次數以及對消息進行去重等處理。
最大努力通知這種事務實現方案,一般用在消息通知這種場景中,因為這種場景中如果存在一些不一致影響也不大
本地消息表
發(fā)送消息方:
-
需要有一個消息表,記錄著消息狀態(tài)相關信息。
-
業(yè)務數據和消息表在同一個數據庫,要保證它倆在同一個本地事務。
-
在本地事務中處理完業(yè)務數據和寫消息表操作后,通過寫消息到 MQ 消息隊列。
-
消息會發(fā)到消息消費方,如果發(fā)送失敗,即進行重試。
消息消費方:
-
處理消息隊列中的消息,完成自己的業(yè)務邏輯。
-
如果本地事務處理成功,則表明已經處理成功了。
-
如果本地事務處理失敗,那么就會重試執(zhí)行。
-
如果是業(yè)務層面的失敗,給消息生產方發(fā)送一個業(yè)務補償消息,通知進行回滾等操作。
開發(fā)中使用的消息中間件并不支持事務消息的功能,那么本地消息表是一種不錯的最終一致性解決方案
消息事務
1.事務發(fā)起方首先發(fā)送半消息到MQ;
2.MQ通知發(fā)送方消息發(fā)送成功;
3.在發(fā)送半消息成功后執(zhí)行本地事務;
4.根據本地事務執(zhí)行結果返回commit或者是rollback;
5.如果消息是rollback, MQ將丟棄該消息不投遞;如果是commit,MQ將會消息發(fā)送給消息訂閱方;
6.訂閱方根據消息執(zhí)行本地事務;
7.訂閱方執(zhí)行本地事務成功后再從MQ中將該消息標記為已消費;
8.如果執(zhí)行本地事務過程中,執(zhí)行端掛掉,或者超時,MQ服務器端將不停的詢問producer來獲取事務狀態(tài);
9.Consumer端的消費成功機制有MQ保證;
最大努力通知和本地消息表對比
本地消息表相對于最大努力通知而言,引入了本地消息表,通過本地事務來保證消息可以發(fā)送成功。相對來說,具有更強的可靠性,可以在一定程度上保證消息的傳遞不丟失。但是,本地消息表也會帶來額外的存儲開銷和網絡通信成本。
而最大努力通知這種方案比較簡單,但是可能存在丟消息的情況。其實,一般業(yè)務中,也會通過對賬來解決的,并不會完全放任消息丟失,只不過對賬的機制會有一定的延時,并且可能需要人工介入。
MQ事務消息和本地消息表對比
MQ事務消息:
需要MQ支持半消息機制或者類似特性,在重復投遞上具有比較好的去重處理;
具有比較大的業(yè)務侵入性,需要業(yè)務方進行改造,提供對應的本地操作成功的回查功能;
DB本地消息表:
使用了數據庫來存儲事務消息,降低了對MQ的要求,但是增加了存儲成本;
事務消息使用了異步投遞,增大了消息重復投遞的可能性;
各方案常見使用場景總結
- 2PC/3PC:依賴于數據庫,能夠很好的提供強一致性和強事務性,但延遲比較高,比較適合傳統的單體應用,在同一個方法中存在跨庫操作的情況,不適合高并發(fā)和高性能要求的場景。
- TCC:適用于執(zhí)行時間確定且較短,實時性要求高,對數據一致性要求高,比如互聯網金融企業(yè)最核心的三個服務:交易、支付、賬務。
- 最大努力通知/本地消息表/MQ 事務:
最大努力通知:
- 使用最大努力通知時,事務參與者在完成本地事務后盡力發(fā)送通知給其他參與者,但無法保證所有參與者都會成功接收到通知。
- 適用于對一致性要求較低,可以容忍一定程度的通知丟失或延遲的場景。
- 通常通過異步方式發(fā)送通知,具有較低的延遲和較高的吞吐量。
本地消息表(本地消息事務):
- 在本地消息表模式中,事務參與者將通知信息寫入本地數據庫的消息表中,并將消息表的狀態(tài)與本地事務綁定。
- 在事務提交之后,消息會異步地被消費者讀取和處理。如果消費者處理失敗,可以進行重試。
- 適用于對一致性要求較高,可以容忍較小的延遲的場景,因為消息的可靠性取決于本地數據庫的持久性和可恢復性。
MQ事務消息(分布式消息事務):
- 在MQ事務消息模式中,事務參與者將消息發(fā)送到消息中間件(如RabbitMQ、Kafka、RocketMQ等)中,并與本地事務綁定。
- 消息中間件會將消息保存在事務日志中,并在事務提交后將消息發(fā)送給消費者。如果事務回滾,消息將被丟棄。
- 適用于對一致性要求較高,同時對消息的可靠性和順序性要求較高的場景。
五、Seata 1.6.1測試
參考:https://blog.csdn.net/weixin_40777510/article/details/129685726
1.配置seata
1.1下載seater-server 1.6.1版本
github地址:https://github.com/seata/seata/releases
1.2配置seater-server
conf目錄下有一個application.example.yml
,這是模板配置文件,還有一個application.yml
,這是真正的配置文件,修改此文件,增加nacos的config和registry配置:
server:
port: 7091
spring:
application:
name: seata-server
logging:
config: classpath:logback-spring.xml
file:
path: ${user.home}/logs/seata
extend:
logstash-appender:
destination: 127.0.0.1:4560
kafka-appender:
bootstrap-servers: 127.0.0.1:9092
topic: logback_to_logstash
console:
user:
username: seata
password: seata
seata:
config:
# support: nacos, consul, apollo, zk, etcd3
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: 29abe988-e6d6-4ca3-85e4-188e3b7e41dd
group: DEFAULT_GROUP
username: nacos
password: nacos
data-id: seataServer.properties
registry:
# support: nacos, eureka, redis, zk, consul, etcd3, sofa
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group: DEFAULT_GROUP
namespace: 29abe988-e6d6-4ca3-85e4-188e3b7e41dd
cluster: default
username: nacos
password: nacos
store:
# support: file 、 db 、 redis
mode: file
# server:
# service-port: 8091 #If not configured, the default is '${server.port} + 1000'
security:
secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
tokenValidityInMilliseconds: 1800000
ignore:
urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
注意:
- seata.config.type 與seata.registry.type 都要修改為nacos
- 修改config與registry中nacos的配置,其中namespace與group須提前在nacos中進行配置
2.配置nacos
2.1 新建namespace
新建namespace命名空間,需與上述seata-server的application.yml中配置一致
2.1 新建seata的seataServer.properties配置文件
在上述namespace下新建application.yml中seata.config.nacos.data-id提到的配置文件:seataServer.properties
#For details about configuration items, see https://seata.io/zh-cn/docs/user/configurations.html
#Transport configuration, for client and server
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableTmClientBatchSendRequest=false
transport.enableRmClientBatchSendRequest=true
transport.enableTcServerBatchSendResponse=false
transport.rpcRmRequestTimeout=30000
transport.rpcTmRequestTimeout=30000
transport.rpcTcRequestTimeout=30000
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.bossThreadSize=1
transport.threadFactory.workerThreadSize=default
transport.shutdown.wait=3
transport.serialization=seata
transport.compressor=none
#Transaction routing rules configuration, only for the client
# 此處的mygroup名字可以自定義,只修改這個值即可
service.vgroup_mapping.stock-service-group=default
service.vgroup_mapping.order-service-group=default
#If you use a registry, you can ignore it
service.default.grouplist=127.0.0.1:8091
service.enableDegrade=false
service.disableGlobalTransaction=false
#Transaction rule configuration, only for the client
client.rm.asyncCommitBufferLimit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.reportRetryCount=5
client.rm.tableMetaCheckEnable=true
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.rm.sagaJsonParser=fastjson
client.rm.tccActionInterceptorOrder=-2147482648
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
client.tm.interceptorOrder=-2147482648
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.onlyCareUpdateColumns=true
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
#For TCC transaction mode
tcc.fence.logTableName=tcc_fence_log
tcc.fence.cleanPeriod=1h
#Log rule configuration, for client and server
log.exceptionRate=100
#Transaction storage configuration, only for the server. The file, db, and redis configuration values are optional.
# 默認為file,一定要改為db,我們自己的服務啟動會連接不到seata
store.mode=db
store.lock.mode=db
store.session.mode=db
#Used for password encryption
#These configurations are required if the `store mode` is `db`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `db`, you can remove the configuration block.
# 修改mysql的配置
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
# 指定seata的數據庫,下面會提
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=123456
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.distributedLockTable=distributed_lock
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
#Transaction rule configuration, only for the server
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.distributedLockExpireTime=10000
server.xaerNotaRetryTimeout=60000
server.session.branchAsyncQueueSize=5000
server.session.enableBranchAsyncRemove=false
server.enableParallelRequestHandle=false
#Metrics configuration, only for the server
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
注意:
- 修改service.vgroupMapping.mygroup=default該值,其中mygroup可以自定義,后面我們自己的服務啟動時,配置文件中需要指定該group。
- 修改store.mode store.lock.mode store.session.mode這三個值為db,才能讓seata連接到下面的數據庫中。
- 修改store.db配置項下的配置,連接到自己的數據庫。
- 該文件參數的具體作用參考官網:http://seata.io/zh-cn/docs/user/configurations.html
- 該配置源文件存放在seata目錄下:seata/script/config-center/config.txt
3.配置MYSQL
3.1 新建配置表
在seata數據庫中新建查詢,執(zhí)行如下sql,sql文件存放在:seata/script/server/db/mysql.sql中
-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
CREATE TABLE IF NOT EXISTS `global_table`
(
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`status` TINYINT NOT NULL,
`application_id` VARCHAR(32),
`transaction_service_group` VARCHAR(32),
`transaction_name` VARCHAR(128),
`timeout` INT,
`begin_time` BIGINT,
`application_data` VARCHAR(2000),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`xid`),
KEY `idx_status_gmt_modified` (`status` , `gmt_modified`),
KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(
`branch_id` BIGINT NOT NULL,
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`resource_group_id` VARCHAR(32),
`resource_id` VARCHAR(256),
`branch_type` VARCHAR(8),
`status` TINYINT,
`client_id` VARCHAR(64),
`application_data` VARCHAR(2000),
`gmt_create` DATETIME(6),
`gmt_modified` DATETIME(6),
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
`row_key` VARCHAR(128) NOT NULL,
`xid` VARCHAR(128),
`transaction_id` BIGINT,
`branch_id` BIGINT NOT NULL,
`resource_id` VARCHAR(256),
`table_name` VARCHAR(32),
`pk` VARCHAR(36),
`status` TINYINT NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`row_key`),
KEY `idx_status` (`status`),
KEY `idx_branch_id` (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
CREATE TABLE IF NOT EXISTS `distributed_lock`
(
`lock_key` CHAR(20) NOT NULL,
`lock_value` VARCHAR(20) NOT NULL,
`expire` BIGINT,
primary key (`lock_key`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);
3.2 業(yè)務數據庫添加undo_log表
-- 注意此處0.3.0+ 增加唯一索引 ux_undo_log
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
4.啟動seata-server
雙擊:seata-server.bat 啟動服務
查看兩件事:
1.seata-server是否注冊進nacos中
2.訪問http://127.0.0.1:7091/#/login 能否進入seata的webui,用戶名與密碼默認為seata
5.測試seata的AT模式
項目結構如下:
參考官網的版本信息:https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E
我用的是
- seata1.6.1
- Spring Cloud Alibaba 2021.0.5.0
- Spring Boot 2.6.13
- Nacos 2.2.0
父pom.xml
<properties>
<java.version>1.8</java.version>
<spring-cloud-alibaba.version>2021.0.5.0</spring-cloud-alibaba.version>
<springboot.version>2.6.13</springboot.version>
<lombok.version>1.18.8</lombok.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<sikpTests>true</sikpTests>
<maven-source-plugin.version>3.0.1</maven-source-plugin.version>
<maven-surefire-plugin.version>2.22.1</maven-surefire-plugin.version>
<seata.version>1.6.1</seata.version>
<mybatis-plus-boot-starter.version>3.3.0</mybatis-plus-boot-starter.version>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-loadbalancer -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- Seata -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>${seata.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.seata/seata-spring-boot-starter -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
<version>3.0.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus-boot-starter.version}</version>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-slf4j</artifactId>
<version>11.8</version>
</dependency>
<!--feign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${springboot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--Spring Cloud Alibaba-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
stock和order的pom.xml一樣
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2021.0.5</spring-cloud.version>
</properties>
<dependencies>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
order的配置文件
spring.application.name=order-service
server.port=9091
# Nacos 注冊中心地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.cloud.loadbalancer.ribbon.enabled: false
# seata 服務分組,要與服務端nacos-config.txt中service.vgroup_mapping的后綴對應
spring.cloud.alibaba.seata.tx-service-group=order-service-group
spring.seata.order-service-group=default
logging.level.io.seata=info
# 數據源配置
spring.datasource.url=jdbc:mysql://localhost:3306/seata_order?allowMultiQueries=true
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
stock的配置文件
spring.application.name=stock-service
server.port=9092
# Nacos 注冊中心地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
# seata 服務分組,要與服務端nacos-config.txt中service.vgroup_mapping的后綴對應
spring.cloud.alibaba.seata.tx-service-group=stock-service-group
spring.seata.stock-service-group=default
logging.level.io.seata=info
# 數據源配置
spring.datasource.url=jdbc:mysql://localhost:3306/seata_stock?allowMultiQueries=true
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
參考官網的業(yè)務代碼:https://github.com/seata/seata-samples/tree/master/springcloud-nacos-seata
啟動項目測試:
瀏覽器輸入:http://localhost:9091/order/placeOrder/commit
,數據正??蹨p
打上斷點,可以查看seata往unlog中記錄的數據
查看控制臺日志,能看出是2PC模式
瀏覽器輸入:http://localhost:9091/order/placeOrder/rollback
,測試回滾文章來源:http://www.zghlxwxcb.cn/news/detail-456987.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-456987.html
到了這里,關于分布式事務及Seata 1.6.1案例的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!