SpringCloud之AlibabaSeata(分布式事務(wù)組件)

  1. Seata簡介
  2. 下載Seata服務(wù)器
  3. 示例(在項(xiàng)目中集成Seata)

處理分布式(微服務(wù))架構(gòu)的事務(wù)問題彬呻。

例(用戶在某電商系統(tǒng)下單購買了一件商品蜓竹,電商系統(tǒng)會執(zhí)行下4步)
  1. 調(diào)用訂單服務(wù)創(chuàng)建訂單數(shù)據(jù);
  2. 調(diào)用庫存服務(wù)扣減庫存;
  3. 調(diào)用賬戶服務(wù)扣減賬戶金額;
  4. 最后調(diào)用訂單服務(wù)修改訂單狀態(tài);
在分布式微服務(wù)架構(gòu)中蛙婴,幾乎所有業(yè)務(wù)操作都需要多個(gè)服務(wù)協(xié)作才能完成。對于單個(gè)服務(wù)的數(shù)據(jù)一致性可以交由其自身數(shù)據(jù)庫事務(wù)來保證,但對于整個(gè)分布式微服務(wù)架構(gòu)航夺,各服務(wù)自身的事務(wù)特性無法保證全局?jǐn)?shù)據(jù)的正確性和一致性(要么全部成功,要么全部失敶尥俊)阳掐,這需要通過分布式事務(wù)框架(如:Seata)來解決。

/*
分布式系統(tǒng)(distributed system)由多臺計(jì)算機(jī)和通信的軟件組件通過計(jì)算機(jī)網(wǎng)絡(luò)連接(本地網(wǎng)絡(luò)或廣域網(wǎng))組成冷蚂。分布式系統(tǒng)是建立在網(wǎng)絡(luò)之上的軟件系統(tǒng)缭保。正是因?yàn)檐浖奶匦裕苑植际较到y(tǒng)具有高度的內(nèi)聚性和透明性蝙茶。因此艺骂,網(wǎng)絡(luò)和分布式系統(tǒng)之間的區(qū)別更多的在于高層軟件(特別是操作系統(tǒng)),而不是硬件隆夯。分布式系統(tǒng)可以應(yīng)用在不同的平臺上如:Pc钳恕、工作站别伏、局域網(wǎng)和廣域網(wǎng)上等。

分布式的優(yōu)點(diǎn)
  1. 可靠性(容錯(cuò)) 
    一臺服務(wù)器的系統(tǒng)崩潰并不影響到其余的服務(wù)器忧额。
  2. 可擴(kuò)展性
    可以根據(jù)需要增加更多的機(jī)器厘肮。
  3. 資源共享
    共享數(shù)據(jù)(銀行,預(yù)訂系統(tǒng))睦番。
  4. 靈活性
    很容易安裝类茂,實(shí)施和調(diào)試新的服務(wù)。
  5. 更快的速度
    可以有多臺計(jì)算機(jī)的計(jì)算能力托嚣,使得它比其他系統(tǒng)有更快的處理速度大咱。
  6. 開放系統(tǒng)
    本地或者遠(yuǎn)程都可以訪問到該服務(wù)。
  7. 更高的性能
    相較于集中式計(jì)算機(jī)網(wǎng)絡(luò)集群可以提供更高的性能(及更好的性價(jià)比)注益。

分布式的缺點(diǎn)
  1. 故障排除
    故障排除和診斷問題。
  2. 軟件
    更少的軟件支持溯捆。
  3. 網(wǎng)絡(luò)
    網(wǎng)絡(luò)基礎(chǔ)設(shè)施的問題丑搔,包括:傳輸問題,高負(fù)載提揍,信息丟失等啤月。
  4. 安全性
    開放系統(tǒng)的特性讓分布式計(jì)算系統(tǒng)存在著數(shù)據(jù)的安全性和共享的風(fēng)險(xiǎn)等問題。
*/
  1. Seata簡介
Seata(分布式事務(wù)框架)
  由阿里巴巴和螞蟻金服共同開發(fā)的開源分布式事務(wù)解決方案劳跃。
  發(fā)展歷程
    2014年發(fā)布TXC(Taobao Transaction Constructor)谎仲,為內(nèi)部應(yīng)用提供分布式事務(wù)服務(wù)。
    2016年TXC改造為GTS(Global Transaction Service)刨仑,作為阿里云的云上分布式事務(wù)解決方案郑诺,為外部客戶提供服務(wù)。
    2019年(基于TXC和GTS)創(chuàng)建了開源項(xiàng)目Fescar(Fast & EaSy Commit And Rollback, FESCAR)分布式事務(wù)解決方案杉武。Fescar被重命名為了Seata(simple extensiable autonomous transaction architecture)辙诞。

分布式事務(wù)相關(guān)概念
  1. 事務(wù):由一組操作構(gòu)成的可靠、獨(dú)立的工作單元轻抱,事務(wù)具備ACID特性(即:原子性飞涂、一致性、隔離性祈搜、持久性)较店。
  2. 本地事務(wù):本地事務(wù)由本地資源管理器(通常指數(shù)據(jù)庫管理系統(tǒng)DBMS,如:MySQL容燕、Oracle等)管理梁呈,嚴(yán)格支持ACID特性。本地事務(wù)不具備分布式事務(wù)的處理能力缰趋,隔離的最小單位受限于資源管理器捧杉,即本地事務(wù)只能對自己數(shù)據(jù)庫的操作進(jìn)行控制陕见,對于其他數(shù)據(jù)庫的操作則無能為力。
  3. 全局事務(wù)/分布式事務(wù):協(xié)調(diào)其管轄的各個(gè)分支事務(wù)達(dá)成一致(要么一起成功提交味抖,要么一起失敗回滾)评甜。
  4. 分支事務(wù):受全局事務(wù)管轄和協(xié)調(diào)的本地事務(wù)。

Seata對分布式事務(wù)的協(xié)調(diào)和控制仔涩,是通過XID和3個(gè)相互協(xié)作的核心組件(TC忍坷、TM、RM)實(shí)現(xiàn)的熔脂。
  TC以Seata服務(wù)器形式獨(dú)立部署佩研,TM、RM則以SeataClient形式集成在微服務(wù)中運(yùn)行霞揉。
  1. XID(全局事務(wù)的唯一標(biāo)識)
    可以在服務(wù)的調(diào)用鏈路中傳遞旬薯,綁定到服務(wù)的事務(wù)上下文中。
  2. TC(Transaction Coordinator事務(wù)協(xié)調(diào)器)
    事務(wù)的協(xié)調(diào)者(即Seata服務(wù)器)适秩,負(fù)責(zé)維護(hù)全局事務(wù)和分支事務(wù)的狀態(tài)绊序,驅(qū)動(dòng)全局事務(wù)提交或回滾。
  3. TM(Transaction Manager事務(wù)管理器)
    事務(wù)的發(fā)起者秽荞,負(fù)責(zé)定義全局事務(wù)的范圍骤公,并根據(jù)TC維護(hù)的全局事務(wù)和分支事務(wù)狀態(tài),做出開始事務(wù)扬跋、提交事務(wù)阶捆、回滾事務(wù)的決議。
  4. RM(Resource Manager資源管理器)
    資源的管理者(各服務(wù)使用的數(shù)據(jù)庫)钦听,負(fù)責(zé)管理分支事務(wù)上的資源洒试,向TC注冊分支事務(wù),匯報(bào)分支事務(wù)狀態(tài)朴上,驅(qū)動(dòng)分支事務(wù)的提交或回滾儡司。

工作流程:
  1. TM向TC申請開啟一個(gè)全局事務(wù),全局事務(wù)創(chuàng)建成功后余指,TC會針對這個(gè)全局事務(wù)生成一個(gè)全局唯一的 XID并返回給TM捕犬;
  2. XID通過服務(wù)的調(diào)用鏈傳遞到其他服務(wù);
  3. RM向TC注冊一個(gè)分支事務(wù),并將其納入XID對應(yīng)全局事務(wù)的管轄酵镜;
  4. TM根據(jù)TC收集的各個(gè)分支事務(wù)的執(zhí)行結(jié)果碉碉,向TC發(fā)起全局事務(wù)提交或回滾決議;
  5. TC調(diào)度XID下管轄的所有分支事務(wù)完成提交或回滾操作淮韭。
Seata的工作流程
Seata提供了4種事務(wù)模式(快速有效地對分布式事務(wù)進(jìn)行控制):AT(使用最多垢粮,無業(yè)務(wù)入侵,使開發(fā)員更專注于業(yè)務(wù)邏輯開發(fā))靠粪、TCC蜡吧、SAGA毫蚓、XA 。

AT模式的前提
    項(xiàng)目必須是使用JDBC訪問(支持本地ACID事務(wù)特性的)關(guān)系型數(shù)據(jù)庫(如:MySQL昔善、Oracle)的JAVA應(yīng)用元潘。
    此外,還需要針對業(yè)務(wù)中涉及的各個(gè)數(shù)據(jù)庫表君仆,分別創(chuàng)建一個(gè)UNDO_LOG(回滾日志)表(不同數(shù)據(jù)庫創(chuàng)建UNDO_LOG表時(shí)會略有不同)翩概。例(MySQL下創(chuàng)建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,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

AT模式的工作機(jī)制(兩個(gè)階段)
  假設(shè)某數(shù)據(jù)庫中存在一張名為webset的表:
    1. id bigint(20) 主鍵
    2. name varchar(255)     
    3. url varchar(255)
  在某次分支事務(wù)中,需要在webset表中執(zhí)行以下操作
    update webset set url = 'www.baidu.com' where name = '百度';
一階段:
  1. 獲取SQL的基本信息:Seata攔截并解析業(yè)務(wù)SQL返咱,得到SQL的操作類型(UPDATE)钥庇、表名(webset)、判斷條件(where name = '百度')等相關(guān)信息咖摹。
  2. 查詢前鏡像:根據(jù)得到的業(yè)務(wù) SQL 信息评姨,生成“前鏡像查詢語句”。 
    select id,name,url from webset where  name='百度';
    執(zhí)行“前鏡像查詢語句”萤晴,得到即將執(zhí)行操作的數(shù)據(jù)参咙,并將其保存為“前鏡像數(shù)據(jù)(beforeImage)”。
  3. 執(zhí)行業(yè)務(wù)SQL(update webset set url = 'www.baidu.com' where name = '百度';)硫眯。
  4. 查詢后鏡像:根據(jù)“前鏡像數(shù)據(jù)”的主鍵(id : 1),生成“后鏡像查詢語句”择同。 
    select id,name,url from webset where  id= 1;
    執(zhí)行“后鏡像查詢語句”两入,得到執(zhí)行業(yè)務(wù)操作后的數(shù)據(jù),并將其保存為“后鏡像數(shù)據(jù)(afterImage)”敲才。
  5. 插入回滾日志:將前后鏡像數(shù)據(jù)和業(yè)務(wù)SQL的信息組成一條回滾日志記錄裹纳,插入到UNDO_LOG表中。
  6. 注冊分支事務(wù)紧武,生成行鎖:在這次業(yè)務(wù)操作的本地事務(wù)提交前剃氧,RM會向TC注冊分支事務(wù),并針對主鍵id為1的記錄生成行鎖阻星。
  以上所有操作均在同一個(gè)數(shù)據(jù)庫事務(wù)內(nèi)完成朋鞍,可以保證一階段的操作的原子性。
  7. 本地事務(wù)提交:將業(yè)務(wù)數(shù)據(jù)的更新和前面生成的UNDO_LOG一并提交妥箕。
  8. 上報(bào)執(zhí)行結(jié)果:將本地事務(wù)提交的結(jié)果上報(bào)給TC滥酥。
二階段:提交
  當(dāng)所有的RM都將自己分支事務(wù)的提交結(jié)果上報(bào)給TC后,TM根據(jù)TC收集的各個(gè)分支事務(wù)的執(zhí)行結(jié)果畦幢,來決定向TC發(fā)起全局事務(wù)的提交或回滾坎吻。
  若所有分支事務(wù)都執(zhí)行成功,TM向TC發(fā)起全局事務(wù)的提交宇葱,并批量刪除各個(gè)RM保存的UNDO_LOG記錄和行鎖瘦真;否則全局事務(wù)回滾刊头。 
二階段:回滾
  若全局事務(wù)中的任何一個(gè)分支事務(wù)失敗,則TM向TC發(fā)起全局事務(wù)的回滾诸尽,并開啟一個(gè)本地事務(wù)原杂,執(zhí)行如下操作。
  1. 查找UNDO_LOG記錄:通過XID和分支事務(wù) ID(Branch ID) 查找所有的UNDO_LOG記錄弦讽。
  2. 數(shù)據(jù)校驗(yàn):將UNDO_LOG中的后鏡像數(shù)據(jù)(afterImage)與當(dāng)前數(shù)據(jù)進(jìn)行比較污尉,如果有不同,則說明數(shù)據(jù)被當(dāng)前全局事務(wù)之外的動(dòng)作所修改往产,需要人工對這些數(shù)據(jù)進(jìn)行處理被碗。
  3. 生成回滾語句:根據(jù)UNDO_LOG中的前鏡像(beforeImage)和業(yè)務(wù) SQL 的相關(guān)信息生成回滾語句:update webset set url= 'www.baidu.com' where id = 1;
  4. 還原數(shù)據(jù):執(zhí)行回滾語句,并將前鏡像數(shù)據(jù)仿村、后鏡像數(shù)據(jù)以及行鎖刪除锐朴。
  5. 提交事務(wù):提交本地事務(wù),并把本地事務(wù)的執(zhí)行結(jié)果(即分支事務(wù)回滾的結(jié)果)上報(bào)給TC蔼囊。
Seata AT 模式一階段
  1. 下載Seata服務(wù)器

從github下載Seata服務(wù)器(壓縮包+SourceCode)

Seata服務(wù)器的目錄說明:
  1. bin目錄(存放:可執(zhí)行命令)
  2. conf目錄(存放:配置文件)
  3. lib目錄(存放:依賴Jar包)
  4. logs目錄(存放:日志)
  1. Seata配置:配置中心(存放配置文件焚志,客戶端可以根據(jù)需要獲取指定的配置文件)
支持:
  file (本地文件,包含 conf畏鼓、properties酱酬、yml等配置文件)、nacos云矫、consul膳沽、apollo、etcd让禀、zookeeper等多種配置中心挑社。

Seata整合Nacos配置中心
  1. SeataServer(Seata服務(wù)器)配置
  將SeataServer的config目錄下的registry.conf文件中的config.type配置方式改為Nacos,并對Nacos配置中心的相關(guān)信息進(jìn)行配置:
    config {
      #配置方式
      type = "nacos"
      nacos {
        #nacos服務(wù)器地址
        serverAddr = "127.0.0.1:1111"
        #配置中心的命名空間
        namespace = ""
        #配置中心所在的分組
        group = "SEATA_GROUP"
        #Nacos配置中心的用戶名
        username = "nacos"
        #Nacos配置中心的密碼
        password = "nacos"
      }
    }
  2. SeataClient(即微服務(wù)架構(gòu)中的服務(wù))配置
    1. 在pom.xml文件添加依賴
    <dependency>
        <groupId>io.seata</groupId>
        <artifactId>seata-spring-boot-starter</artifactId>
        <version>最新版</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba.nacos</groupId>
        <artifactId>nacos-client</artifactId>
        <version>1.2.0及以上版本</version>
    </dependency>
    如果是SpringCloud項(xiàng)目中巡揍,通常只需要添加 
    <!--引入 seata 依賴-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    </dependency>
    2. 在application.yml配置文件對Nacos配置中心進(jìn)行配置
seata:
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:1111 # Nacos 配置中心的地址
      group : "SEATA_GROUP"  #分組
      namespace: ""
      username: "nacos"   #Nacos 配置中心的用于名
      password: "nacos"  #Nacos 配置中心的密碼
  3. 上傳配置到Nacos配置中心
    1. 根據(jù)需要修改SeataServer源碼/script/config-center目錄中的config.txt痛阻。  
    2. 跳轉(zhuǎn)到SeataServer源碼/script/config-center/nacos目錄(確保 Nacos 服務(wù)器已啟動(dòng))
      Linux:直接終端執(zhí)行如下命令:
      Windows:右鍵鼠標(biāo)選擇Git Bush Here,并在彈出的 Git 命令窗口中執(zhí)行如下命令:
        sh nacos-config.sh -h 127.0.0.1 -p 1111  -g SEATA_GROUP -u nacos -w nacos命令腮敌。
        說明:
          -h:Nacos 的 host阱当,默認(rèn)取值為 localhost
          -p:端口號,默認(rèn)取值為 8848
          -g:Nacos 配置的分組糜工,默認(rèn)取值為 SEATA_GROUP
          -u:Nacos 用戶名
          -w:Nacos 密碼
  4. 驗(yàn)證Nacos配置中心
    啟動(dòng)NacosServer斗这,登陸Nacos控制臺(http://localhost:1111/nacos/)查看配置列表。
  1. Seata配置:服務(wù)注冊中心(存放:服務(wù)與服務(wù)地址的映射關(guān)系)
支持: 
  file (直連)啤斗、eureka表箭、consul、nacos、etcd免钻、zookeeper彼水、sofa、redis等多種服務(wù)注冊中心极舔。

Seata整合Nacos注冊中心
  1. SeataServer配置
    將SeataServer的config目錄下的registry.conf文件的registry.type注冊方式改為Nacos凤覆,并對Nacos注冊中心的相關(guān)信息進(jìn)行配置:
    registry {
      #注冊方式
      type = "nacos"
      nacos {
        application = "seata-server"
        # 修改 nacos 注冊中心的地址
        serverAddr = "127.0.0.1:1111"
        group = "SEATA_GROUP"
        namespace = ""
        cluster = "default"
        username = "nacos"
        password = "nacos"
      }
    }
  2. SeataClient配置
    1. 在pom.xml文件添加依賴(同上)
    2. 在application.yml配置文件中對Nacos注冊中心進(jìn)行配置
seata:
  registry:
    type: nacos
    nacos:
      application: seata-server  
      server-addr: 127.0.0.1:1111 # Nacos 注冊中心的地址
      group : "SEATA_GROUP" #分組
      namespace: ""   
      username: "nacos"   #Nacos 注冊中心的用戶名
      password: "nacos"   # Nacos 注冊中心的密碼
  3. 驗(yàn)證Nacos注冊中心
    啟動(dòng)NacosServer、SeataServer拆魏,登錄Nacos控制臺查看服務(wù)列表盯桦。
  1. Seata事務(wù)分組
Seata通過事務(wù)分組查找獲取TC服務(wù),流程如下:
    1. 在應(yīng)用中配置事務(wù)分組渤刃。
    2. 應(yīng)用通過配置中心去查找配置:service.vgroupMapping.{事務(wù)分組} 獲取TC集群名拥峦。
    3. 獲得集群名稱后,應(yīng)用通過一定的前后綴 + 集群名稱去構(gòu)造服務(wù)名卖子。
    4. 得到服務(wù)名后略号,去注冊中心去拉取服務(wù)列表,獲得后端真實(shí)的 TC 服務(wù)列表洋闽。

1. SeataServer配置
  在SeataServer的config目錄下的registry.conf文件的nacos中添加cluster = "com.sst.cx"設(shè)置TC集群名玄柠。
2. SeataClient配置
  在application.yml中配置
spring:
     alibaba:
      seata:
        tx-service-group: service-order-group  #事務(wù)分組名,默認(rèn)值為:服務(wù)名-fescar-service-group
seata:
  registry:
    type: nacos   #從 Nacos 獲取 TC 服務(wù)
    nacos:
      server-addr: 127.0.0.1:1111
  config:
    type: nacos   #使用 Nacos 作為配置中心
    nacos:
      server-addr: 127.0.0.1:1111
3. 上傳配置到Nacos
  修改SeataServer源碼/script/config-center目錄中的config.txt(事務(wù)分組名service-order-group诫舅、TC集群名com.sst.cx):
    service.vgroupMapping.service-order-group=com.sst.cx
4.  獲取事務(wù)分組
  1. 依次啟動(dòng)Nacos集群羽利、SeataServer、SeataClient刊懈。
  2. SeataClient在啟動(dòng)時(shí)这弧,會根據(jù)application.yml中的spring.cloud.alibaba.seata.tx-service-group獲取事務(wù)分組名:service-order-group。
5. 獲取TC集群名
  使用事務(wù)分組名“service-order-group”拼接成“service.vgroupMapping.service-order-group”俏讹,并從 Nacos配置中心獲取該配置的取值(TC集群名):com.sst.cx。
6. 查找TC服務(wù)
  根據(jù)TC集群名畜吊、Nacos注冊中心的地址(server-addr)以及命名空間(namespace)泽疆,在Nacos注冊中心找到真實(shí)的TC服務(wù)列表。
  1. 啟動(dòng)SeataServer(使用db模式)配置:服務(wù)注冊中心玲献、配置中心殉疼、事務(wù)分組
SeataServer的3種存儲模式(store.mode):
  1. file(文件存儲模式)默認(rèn)
    單機(jī)模式,全局事務(wù)的會話信息在內(nèi)存中讀寫捌年,并持久化本地文件 root.data瓢娜,性能較高。
  2. db(數(shù)據(jù)庫存儲模式)
    高可用模式礼预,全局事務(wù)會話信息通過數(shù)據(jù)庫共享眠砾,性能較低。     
    需要建數(shù)據(jù)庫表托酸。
  3. redis(緩存處處模式)
    Seata Server 1.3 及以上版本支持該模式褒颈,性能較高柒巫,但存在事務(wù)信息丟失風(fēng)險(xiǎn)。
    需要配置 redis 持久化配置谷丸。

1. 建表
在db模式下堡掏,需要針對全局事務(wù)的會話信息創(chuàng)建以下3張數(shù)據(jù)庫表(在seata數(shù)據(jù)庫下)。
  1. 全局事務(wù)表刨疼,對應(yīng)的表為:global_table
  2. 分支事務(wù)表泉唁,對應(yīng)的表為:branch_table
  3. 全局鎖表,對應(yīng)的表為:lock_table
-- 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(256),
    `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_gmt_modified_status` (`gmt_modified`, `status`),
    KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;
-- 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 = utf8;
-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
    `row_key`        VARCHAR(128) NOT NULL,
    `xid`            VARCHAR(96),
    `transaction_id` BIGINT,
    `branch_id`      BIGINT       NOT NULL,
    `resource_id`    VARCHAR(256),
    `table_name`     VARCHAR(32),
    `pk`             VARCHAR(36),
    `gmt_create`     DATETIME,
    `gmt_modified`   DATETIME,
    PRIMARY KEY (`row_key`),
    KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

2. 配置SeataServer
  將SeataServer的conf目錄下的registry.conf文件的registry.type服務(wù)注冊方式和config.type配置方式改為Nacos揩慕。
registry {
  type = "nacos"
  nacos {
    application = "seata-server"
    serverAddr = "127.0.0.1:1111"
    group = "SEATA_GROUP"
    namespace = ""
    cluster = "default"
    username = ""
    password = ""
  }
}
config {
  type = "nacos"
  nacos {
    serverAddr = "127.0.0.1:1111"
    namespace = ""
    group = "SEATA_GROUP"
    username = "nacos"
    password = "nacos"
    dataId = "seataServer.properties"
  }
}

3. 將Seata配置上傳到Nacos
  修改SeataServer源碼/script/config-center目錄下的config.txt亭畜,并上傳到Nacos。
#將 Seata Server 的存儲模式修改為 db
store.mode=db
# 數(shù)據(jù)庫驅(qū)動(dòng)
store.db.driverClassName=com.mysql.cj.jdbc.Driver
# 數(shù)據(jù)庫 url
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useSSL=false&characterEncoding=UTF-8&useUnicode=true&serverTimezone=UTC 
# 數(shù)據(jù)庫的用戶名
store.db.user=root 
# 數(shù)據(jù)庫的密碼
store.db.password=12345678
# 自定義事務(wù)分組
service.vgroupMapping.service-order-group=default 
service.vgroupMapping.service-storage-group=default
service.vgroupMapping.service-account-group=default 

4. 啟動(dòng)Seata Server
  Windows:雙擊SeataServer的bin目錄下的seata-server.bat啟動(dòng)腳本漩绵。
  Linux:sh seata-server.sh
  1. 示例(在業(yè)務(wù)中集成Seata)
電商系統(tǒng)中用戶下單購買一件商品贱案,涉及到3個(gè)微服務(wù)(分別使用各自的數(shù)據(jù)庫)
  1. Order(訂單服務(wù)):創(chuàng)建和修改訂單。
  2. Storage(庫存服務(wù)):對指定的商品扣除倉庫庫存止吐。
  3. Account(賬戶服務(wù)) :從用戶帳戶中扣除商品金額宝踪。
服務(wù)調(diào)用步驟如下:
  1. 調(diào)用Order服務(wù),創(chuàng)建一條訂單數(shù)據(jù)碍扔,訂單狀態(tài)為“未完成”瘩燥;
  2. 調(diào)用Storage服務(wù),扣減商品庫存不同;
  3. 調(diào)用Account服務(wù)厉膀,從用戶賬戶中扣除商品金額;
  4. 調(diào)用Order服務(wù)二拐,將訂單狀態(tài)修改為“已完成”服鹅。
服務(wù)調(diào)用
===》1. 創(chuàng)建訂單(Order)服務(wù)
sql
create database seata_order character set utf8 collate utf8_general_ci;
use seata_order;
DROP TABLE IF EXISTS `t_order`;
CREATE TABLE `t_order` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `user_id` bigint DEFAULT NULL COMMENT '用戶id',
  `product_id` bigint DEFAULT NULL COMMENT '產(chǎn)品id',
  `count` int DEFAULT NULL COMMENT '數(shù)量',
  `money` decimal(11,0) DEFAULT NULL COMMENT '金額',
  `status` int DEFAULT NULL COMMENT '訂單狀態(tài):0:未完成;1:已完結(jié)',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=32 DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
  `branch_id` bigint NOT NULL COMMENT 'branch transaction id',
  `xid` varchar(128) NOT NULL COMMENT 'global transaction id',
  `context` varchar(128) NOT NULL COMMENT 'undo_log context,such as serialization',
  `rollback_info` longblob NOT NULL COMMENT 'rollback info',
  `log_status` int NOT NULL COMMENT '0:normal status,1:defense status',
  `log_created` datetime(6) NOT NULL COMMENT 'create datetime',
  `log_modified` datetime(6) NOT NULL COMMENT 'modify datetime',
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;

創(chuàng)建spring-cloud-alibaba-seata-order-8005子項(xiàng)目
1. 修改pom.xml文件
    <dependencies>
        <!--nacos 服務(wù)注冊中心-->
        <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>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--引入 OpenFeign 的依賴-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-loadbalancer</artifactId>
        </dependency>
        <!--引入 seata 依賴-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.sst.cx</groupId>
            <artifactId>spring-cloud-alibaba-api</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>
        <!--添加 Spring Boot 的監(jiān)控模塊-->
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-actuator -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--SpringCloud ailibaba sentinel -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <!--配置中心 nacos-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <!--mybatis自動(dòng)生成代碼插件-->
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.4.0</version>
                <configuration>
                    <configurationFile>src/main/resources/mybatis-generator/generatorConfig.xml</configurationFile>
                    <verbose>true</verbose>
                    <!-- 是否覆蓋百新,true表示會替換生成的JAVA文件企软,false則不覆蓋 -->
                    <overwrite>true</overwrite>
                </configuration>
                <dependencies>
                    <!--mysql驅(qū)動(dòng)包-->
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>8.0.19</version>
                    </dependency>
                    <dependency>
                        <groupId>org.mybatis.generator</groupId>
                        <artifactId>mybatis-generator-core</artifactId>
                        <version>1.4.0</version>
                    </dependency>
                </dependencies>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
2. 創(chuàng)建bootstrap.yml配置文件(類路徑resources目錄下)
spring:
  cloud:
    ## Nacos認(rèn)證信息
    nacos:
      config:
        username: nacos
        password: nacos
        context-path: /nacos
        server-addr: 127.0.0.1:1111 # 設(shè)置配置中心服務(wù)端地址
        namespace:   # Nacos 配置中心的namespace。需要注意饭望,如果使用 public 的 namcespace 仗哨,請不要填寫這個(gè)值,直接留空即可
3. 創(chuàng)建application.yml配置文件(類路徑resources目錄下)
spring:
  application:
    name: spring-cloud-alibaba-seata-order-8005 #服務(wù)名
  #數(shù)據(jù)源配置
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver #數(shù)據(jù)庫驅(qū)動(dòng)
    name: defaultDataSource
    url: jdbc:mysql://localhost:3306/seata_order?serverTimezone=UTC #數(shù)據(jù)庫連接地址
    username: root  #數(shù)據(jù)庫的用戶名
    password: 12345678  #數(shù)據(jù)庫密碼
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:1111 #nacos 服務(wù)器地址
        namespace: public #nacos 命名空間
        username:
        password:
    sentinel:
      transport:
        dashboard: 127.0.0.1:8080  #Sentinel 控制臺地址
        port: 8719
    alibaba:
      seata:
        #自定義服務(wù)群組铅辞,該值必須與 Nacos 配置中的 service.vgroupMapping.{my-service-group}=default 中的 {my-service-group}相同
        tx-service-group: service-order-group
server:
  port: 8005  #端口
seata:
  application-id: ${spring.application.name}
  #自定義服務(wù)群組厌漂,該值必須與 Nacos 配置中的 service.vgroupMapping.{my-service-group}=default 中的 {my-service-group}相同
  tx-service-group: service-order-group
  service:
    grouplist:
      #Seata 服務(wù)器地址
      seata-server: 127.0.0.1:8091
  # Seata 的注冊方式為 nacos
  registry:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:1111
  # Seata 的配置中心為 nacos
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:1111
feign:
  sentinel:
    enabled: true #開啟 OpenFeign 功能
management:
  endpoints:
    web:
      exposure:
        include: "*"
#### MyBatis 配置 ######
mybatis:
  # 指定 mapper.xml 的位置
  mapper-locations: classpath:mybatis/mapper/*.xml
  #掃描實(shí)體類的位置,在此處指明掃描實(shí)體類的包,在 mapper.xml 中就可以不寫實(shí)體類的全路徑名
  type-aliases-package: com.sst.cx.domain
  configuration:
    #默認(rèn)開啟駝峰命名法斟珊,可以不用設(shè)置該屬性
    map-underscore-to-camel-case: true
4. 創(chuàng)建Order.java
package com.sst.cx.domain;
import java.math.BigDecimal;
public class Order {
    private Long id;
    private Long userId;
    private Long productId;
    private Integer count;
    private BigDecimal money;
    private Integer status;
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public Long getUserId() {
        return userId;
    }
    public void setUserId(Long userId) {
        this.userId = userId;
    }
    public Long getProductId() {
        return productId;
    }
    public void setProductId(Long productId) {
        this.productId = productId;
    }
    public Integer getCount() {
        return count;
    }
    public void setCount(Integer count) {
        this.count = count;
    }
    public BigDecimal getMoney() {
        return money;
    }
    public void setMoney(BigDecimal money) {
        this.money = money;
    }
    public Integer getStatus() {
        return status;
    }
    public void setStatus(Integer status) {
        this.status = status;
    }
}
5. 創(chuàng)建OrderMapper.java
package com.sst.cx.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import com.sst.cx.domain.Order;
@Mapper
public interface OrderMapper {
    int deleteByPrimaryKey(Long id);
    int insert(Order record);
    int create(Order order);
    int insertSelective(Order record);
    // 修改訂單狀態(tài)苇倡,從零改為1
    void update(@Param("userId") Long userId, @Param("status") Integer status);
    Order selectByPrimaryKey(Long id);
    int updateByPrimaryKeySelective(Order record);
    int updateByPrimaryKey(Order record);
}
6. 創(chuàng)建OrderMapper.xml(resouces/mybatis/mapper 目錄下)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sst.cx.mapper.OrderMapper">
    <resultMap id="BaseResultMap" type="Order">
        <id column="id" jdbcType="BIGINT" property="id"/>
        <result column="user_id" jdbcType="BIGINT" property="userId"/>
        <result column="product_id" jdbcType="BIGINT" property="productId"/>
        <result column="count" jdbcType="INTEGER" property="count"/>
        <result column="money" jdbcType="DECIMAL" property="money"/>
        <result column="status" jdbcType="INTEGER" property="status"/>
    </resultMap>
    <sql id="Base_Column_List">
        id
        , user_id, product_id, count, money, status
    </sql>
    <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List"/>
        from t_order
        where id = #{id,jdbcType=BIGINT}
    </select>
    <delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
        delete
        from t_order
        where id = #{id,jdbcType=BIGINT}
    </delete>
    <insert id="insert" parameterType="Order">
        insert into t_order (id, user_id, product_id,
                             count, money, status)
        values (#{id,jdbcType=BIGINT}, #{userId,jdbcType=BIGINT}, #{productId,jdbcType=BIGINT},
                #{count,jdbcType=INTEGER}, #{money,jdbcType=DECIMAL}, #{status,jdbcType=INTEGER})
    </insert>
    <insert id="insertSelective" parameterType="Order">
        insert into t_order
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="id != null">
                id,
            </if>
            <if test="userId != null">
                user_id,
            </if>
            <if test="productId != null">
                product_id,
            </if>
            <if test="count != null">
                count,
            </if>
            <if test="money != null">
                money,
            </if>
            <if test="status != null">
                status,
            </if>
        </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
            <if test="id != null">
                #{id,jdbcType=BIGINT},
            </if>
            <if test="userId != null">
                #{userId,jdbcType=BIGINT},
            </if>
            <if test="productId != null">
                #{productId,jdbcType=BIGINT},
            </if>
            <if test="count != null">
                #{count,jdbcType=INTEGER},
            </if>
            <if test="money != null">
                #{money,jdbcType=DECIMAL},
            </if>
            <if test="status != null">
                #{status,jdbcType=INTEGER},
            </if>
        </trim>
    </insert>
    <insert id="create" parameterType="Order">
        insert into t_order (user_id, product_id,
                             count, money, status)
        values (#{userId,jdbcType=BIGINT}, #{productId,jdbcType=BIGINT},
                #{count,jdbcType=INTEGER}, #{money,jdbcType=DECIMAL}, #{status,jdbcType=INTEGER})
    </insert>
    <update id="updateByPrimaryKeySelective" parameterType="Order">
        update t_order
        <set>
            <if test="userId != null">
                user_id = #{userId,jdbcType=BIGINT},
            </if>
            <if test="productId != null">
                product_id = #{productId,jdbcType=BIGINT},
            </if>
            <if test="count != null">
                count = #{count,jdbcType=INTEGER},
            </if>
            <if test="money != null">
                money = #{money,jdbcType=DECIMAL},
            </if>
            <if test="status != null">
                status = #{status,jdbcType=INTEGER},
            </if>
        </set>
        where id = #{id,jdbcType=BIGINT}
    </update>
    <update id="updateByPrimaryKey" parameterType="Order">
        update t_order
        set user_id    = #{userId,jdbcType=BIGINT},
            product_id = #{productId,jdbcType=BIGINT},
            count      = #{count,jdbcType=INTEGER},
            money      = #{money,jdbcType=DECIMAL},
            status     = #{status,jdbcType=INTEGER}
        where id = #{id,jdbcType=BIGINT}
    </update>
    <update id="update">
        update t_order
        set status = 1
        where user_id = #{userId}
          and status = #{status};
    </update>
</mapper>
7. 創(chuàng)建OrderService.java
package com.sst.cx.service;
import com.sst.cx.domain.*;
public interface OrderService {
    // 創(chuàng)建訂單數(shù)據(jù)
    CommonResult create(Order order);
}
8. 創(chuàng)建StorageService.java
package com.sst.cx.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.sst.cx.domain.CommonResult;
@FeignClient(value = "spring-cloud-alibaba-seata-storage-8006")
public interface StorageService {
    @PostMapping(value = "/storage/decrease")
    CommonResult decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count);
}
9.  創(chuàng)建AccountService.java
package com.sst.cx.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.math.BigDecimal;
import com.sst.cx.domain.CommonResult;
@FeignClient(value = "spring-cloud-alibaba-seata-account-8007")
public interface AccountService {
    @PostMapping(value = "/account/decrease")
    CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
}
10. 創(chuàng)建OrderServiceImpl.java
package com.sst.cx.service.impl;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import com.sst.cx.mapper.OrderMapper;
import com.sst.cx.domain.*;
import com.sst.cx.service.*;
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
    @Resource
    private OrderMapper orderMapper;
    @Resource
    private StorageService storageService;
    @Resource
    private AccountService accountService;
    /**
     * 創(chuàng)建訂單->調(diào)用庫存服務(wù)扣減庫存->調(diào)用賬戶服務(wù)扣減賬戶余額->修改訂單狀態(tài)
     * 簡單說:下訂單->扣庫存->減余額->改訂單狀態(tài)
     */
    @Override
    @GlobalTransactional(name = "fsp-create-order", rollbackFor = Exception.class)
    public CommonResult create(Order order) {
        log.info("----->開始新建訂單");
        // 1 新建訂單
        order.setUserId(new Long(1));
        order.setStatus(0);
        orderMapper.create(order);
        // 2 扣減庫存
        log.info("----->訂單服務(wù)開始調(diào)用庫存服務(wù),開始扣減庫存");
        storageService.decrease(order.getProductId(), order.getCount());
        log.info("----->訂單微服務(wù)開始調(diào)用庫存,扣減庫存結(jié)束");
        // 3 扣減賬戶
        log.info("----->訂單服務(wù)開始調(diào)用賬戶服務(wù)雏节,開始從賬戶扣減商品金額");
        accountService.decrease(order.getUserId(), order.getMoney());
        log.info("----->訂單微服務(wù)開始調(diào)用賬戶胜嗓,賬戶扣減商品金額結(jié)束");
        // 4 修改訂單狀態(tài),從零到1,1代表已經(jīng)完成
        log.info("----->修改訂單狀態(tài)開始");
        orderMapper.update(order.getUserId(), 0);
        log.info("----->修改訂單狀態(tài)結(jié)束");
        log.info("----->下訂單結(jié)束了------->");
        return new CommonResult(200, "訂單創(chuàng)建成功");
    }
}
11. 創(chuàng)建OrderController.java
package com.sst.cx.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import com.sst.cx.service.OrderService;
import com.sst.cx.domain.*;
@RestController
public class OrderController {
    @Autowired
    private OrderService orderService;
    @GetMapping("/order/create/{productId}/{count}/{money}")
    public CommonResult create(@PathVariable("productId") Integer productId, @PathVariable("count") Integer count
            , @PathVariable("money") BigDecimal money) {
        Order order = new Order();
        order.setProductId(Integer.valueOf(productId).longValue());
        order.setCount(count);
        order.setMoney(money);
        return orderService.create(order);
    }
}
12. 給主啟動(dòng)類添加@EnableDiscoveryClient钩乍、@EnableFeignClients注解
package com.sst.cx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication(scanBasePackages = "com.sst.cx")
public class SpringCloudAlibabaSeataOrder8005Application {
    public static void main(String[] args) {
        SpringApplication.run(SpringCloudAlibabaSeataOrder8005Application.class, args);
    }
}
===》2. 搭建庫存(Storage)服務(wù)
sql
create database seata_storage character set utf8 collate utf8_general_ci;
use seata_storage;
DROP TABLE IF EXISTS `t_storage`;
CREATE TABLE `t_storage` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `product_id` bigint DEFAULT NULL COMMENT '產(chǎn)品id',
  `total` int DEFAULT NULL COMMENT '總庫存',
  `used` int DEFAULT NULL COMMENT '已用庫存',
  `residue` int DEFAULT NULL COMMENT '剩余庫存',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO `t_storage` VALUES ('1', '1', '100', '0', '100');
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
  `branch_id` bigint NOT NULL COMMENT 'branch transaction id',
  `xid` varchar(128) NOT NULL COMMENT 'global transaction id',
  `context` varchar(128) NOT NULL COMMENT 'undo_log context,such as serialization',
  `rollback_info` longblob NOT NULL COMMENT 'rollback info',
  `log_status` int NOT NULL COMMENT '0:normal status,1:defense status',
  `log_created` datetime(6) NOT NULL COMMENT 'create datetime',
  `log_modified` datetime(6) NOT NULL COMMENT 'modify datetime',
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='AT transaction mode undo table';

創(chuàng)建spring-cloud-alibaba-seata-storage-8006子項(xiàng)目
1. 修改pom.xml文件
    <dependencies>
        <!--nacos 服務(wù)注冊中心-->
        <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>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--引入 OpenFeign 的依賴-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-loadbalancer</artifactId>
        </dependency>
        <!--seata 依賴-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.sst.cx</groupId>
            <artifactId>spring-cloud-alibaba-api</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>
        <!--添加 Spring Boot 的監(jiān)控模塊-->
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-actuator -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--SpringCloud ailibaba sentinel -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <!--配置中心 nacos-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <!--mybatis自動(dòng)生成代碼插件-->
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.4.0</version>
                <configuration>
                    <configurationFile>src/main/resources/mybatis-generator/generatorConfig.xml</configurationFile>
                    <verbose>true</verbose>
                    <!-- 是否覆蓋辞州,true表示會替換生成的JAVA文件,false則不覆蓋 -->
                    <overwrite>true</overwrite>
                </configuration>
                <dependencies>
                    <!--mysql驅(qū)動(dòng)包-->
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>8.0.19</version>
                    </dependency>
                    <dependency>
                        <groupId>org.mybatis.generator</groupId>
                        <artifactId>mybatis-generator-core</artifactId>
                        <version>1.4.0</version>
                    </dependency>
                </dependencies>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
2. 創(chuàng)建bootstrap.yml配置文件(類路徑resources目錄下)
spring:
  cloud:
    ## Nacos認(rèn)證信息
    nacos:
      config:
        username: nacos
        password: nacos
        context-path: /nacos
        server-addr: 127.0.0.1:1111 # 設(shè)置配置中心服務(wù)端地址
        namespace:   # Nacos 配置中心的namespace寥粹。需要注意变过,如果使用 public 的 namcespace ,請不要填寫這個(gè)值涝涤,直接留空即可
3. 創(chuàng)建application.yml配置文件(類路徑resources目錄下)
spring:
  application:
    name: spring-cloud-alibaba-seata-storage-8006
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    name: defaultDataSource
    url: jdbc:mysql://localhost:3306/seata_storage?serverTimezone=UTC
    username: root
    password: 12345678
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:1111
        namespace: public
        username:
        password:
    sentinel:
      transport:
        dashboard: 127.0.0.1:8080
        port: 8719
    alibaba:
      seata:
        tx-service-group: service-storage-group
server:
  port: 8006
seata:
  application-id: ${spring.application.name}
  tx-service-group: service-storage-group
  service:
    grouplist:
      seata-server: 127.0.0.1:8091
  registry:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:1111
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:1111
feign:
  sentinel:
    enabled: true
management:
  endpoints:
    web:
      exposure:
        include: "*"
##### MyBatis 配置 #####
mybatis:
  # 指定 mapper.xml 的位置
  mapper-locations: classpath:mybatis/mapper/*.xml
  #掃描實(shí)體類的位置,在此處指明掃描實(shí)體類的包媚狰,在 mapper.xml 中就可以不寫實(shí)體類的全路徑名
  type-aliases-package: com.sst.cx.domain
  configuration:
    #默認(rèn)開啟駝峰命名法,可以不用設(shè)置該屬性
    map-underscore-to-camel-case: true
4. 創(chuàng)建Storage.java
package com.sst.cx.domain;
public class Storage {
    private Long id;
    private Long productId;
    private Integer total;
    private Integer used;
    private Integer residue;
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public Long getProductId() {
        return productId;
    }
    public void setProductId(Long productId) {
        this.productId = productId;
    }
    public Integer getTotal() {
        return total;
    }
    public void setTotal(Integer total) {
        this.total = total;
    }
    public Integer getUsed() {
        return used;
    }
    public void setUsed(Integer used) {
        this.used = used;
    }
    public Integer getResidue() {
        return residue;
    }
    public void setResidue(Integer residue) {
        this.residue = residue;
    }
}
5. 創(chuàng)建StorageMapper.java
package com.sst.cx.mapper;
import org.apache.ibatis.annotations.Mapper;
import com.sst.cx.domain.Storage;
@Mapper
public interface StorageMapper {
    Storage selectByProductId(Long productId);
    int decrease(Storage record);
}
6. 創(chuàng)建StorageMapper.xml(resouces/mybatis/mapper 目錄下)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sst.cx.mapper.StorageMapper">
    <resultMap id="BaseResultMap" type="Storage">
        <id column="id" jdbcType="BIGINT" property="id"/>
        <result column="product_id" jdbcType="BIGINT" property="productId"/>
        <result column="total" jdbcType="INTEGER" property="total"/>
        <result column="used" jdbcType="INTEGER" property="used"/>
        <result column="residue" jdbcType="INTEGER" property="residue"/>
    </resultMap>
    <sql id="Base_Column_List">
        id
        , product_id, total, used, residue
    </sql>
    <update id="decrease" parameterType="Storage">
        update t_storage
        <set>
            <if test="total != null">
                total = #{total,jdbcType=INTEGER},
            </if>
            <if test="used != null">
                used = #{used,jdbcType=INTEGER},
            </if>
            <if test="residue != null">
                residue = #{residue,jdbcType=INTEGER},
            </if>
        </set>
        where product_id = #{productId,jdbcType=BIGINT}
    </update>
    <select id="selectByProductId" parameterType="java.lang.Long" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List"/>
        from t_storage
        where product_id = #{productId,jdbcType=BIGINT}
    </select>
</mapper>
7. 創(chuàng)建StorageService.java
package com.sst.cx.service;
public interface StorageService {
    int decrease(Long productId, Integer count);
}
8. 創(chuàng)建StorageServiceImpl.java
package com.sst.cx.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import com.sst.cx.domain.Storage;
import com.sst.cx.mapper.StorageMapper;
import com.sst.cx.service.StorageService;
@Service
@Slf4j
public class StorageServiceImpl implements StorageService {
    @Resource
    StorageMapper storageMapper;
    @Override
    public int decrease(Long productId, Integer count) {
        log.info("------->storage-service中扣減庫存開始");
        log.info("------->storage-service 開始查詢商品是否存在");
        Storage storage = storageMapper.selectByProductId(productId);
        if (storage != null && storage.getResidue().intValue() >= count.intValue()) {
            Storage storage2 = new Storage();
            storage2.setProductId(productId);
            storage.setUsed(storage.getUsed() + count);
            storage.setResidue(storage.getTotal().intValue() - storage.getUsed());
            int decrease = storageMapper.decrease(storage);
            log.info("------->storage-service 扣減庫存成功");
            return decrease;
        } else {
            log.info("------->storage-service 庫存不足阔拳,開始回滾崭孤!");
            throw new RuntimeException("庫存不足,扣減庫存失敗!");
        }
    }
}
9. 創(chuàng)建StorageController.java
package com.sst.cx.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import com.sst.cx.domain.CommonResult;
import com.sst.cx.service.StorageService;
@RestController
@Slf4j
public class StorageController {
    @Resource
    private StorageService storageService;
    @Value("${server.port}")
    private String serverPort;
    @PostMapping(value = "/storage/decrease")
    CommonResult decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count) {
        int decrease = storageService.decrease(productId, count);
        CommonResult result;
        if (decrease > 0) {
            result = new CommonResult(200, "from mysql,serverPort:  " + serverPort, decrease);
        } else {
            result = new CommonResult(505, "from mysql,serverPort:  " + serverPort, "庫存扣減失敗");
        }
        return result;
    }
}
10. 給主啟動(dòng)類添加@EnableDiscoveryClient才避、@EnableFeignClients注解
package com.sst.cx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication(scanBasePackages = "com.sst.cx")
public class SpringCloudAlibabaSeataStorage8006Application {
    public static void main(String[] args) {
        SpringApplication.run(SpringCloudAlibabaSeataStorage8006Application.class, args);
    }
}
===》3. 搭建賬戶(Account)服務(wù)
sql
create database seata_account character set utf8 collate utf8_general_ci;
use seata_account;
DROP TABLE IF EXISTS `t_account`;
CREATE TABLE `t_account` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
  `user_id` bigint DEFAULT NULL COMMENT '用戶id',
  `total` decimal(10,0) DEFAULT NULL COMMENT '總額度',
  `used` decimal(10,0) DEFAULT NULL COMMENT '已用余額',
  `residue` decimal(10,0) DEFAULT '0' COMMENT '剩余可用額度',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO `t_account` VALUES ('1', '1', '1000', '0', '1000');
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
  `branch_id` bigint NOT NULL COMMENT 'branch transaction id',
  `xid` varchar(128) NOT NULL COMMENT 'global transaction id',
  `context` varchar(128) NOT NULL COMMENT 'undo_log context,such as serialization',
  `rollback_info` longblob NOT NULL COMMENT 'rollback info',
  `log_status` int NOT NULL COMMENT '0:normal status,1:defense status',
  `log_created` datetime(6) NOT NULL COMMENT 'create datetime',
  `log_modified` datetime(6) NOT NULL COMMENT 'modify datetime',
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

創(chuàng)建spring-cloud-alibaba-seata-account-8007 子項(xiàng)目
1. 修改pom.xml文件
    <dependencies>
        <!--nacos 服務(wù)注冊中心-->
        <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>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--引入 OpenFeign 的依賴-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-loadbalancer</artifactId>
        </dependency>
        <!--seata-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.sst.cx</groupId>
            <artifactId>spring-cloud-alibaba-api</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>
        <!--添加 Spring Boot 的監(jiān)控模塊-->
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-actuator -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--SpringCloud ailibaba sentinel -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <!--mybatis自動(dòng)生成代碼插件-->
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.4.0</version>
                <configuration>
                    <configurationFile>src/main/resources/mybatis-generator/generatorConfig.xml</configurationFile>
                    <verbose>true</verbose>
                    <!-- 是否覆蓋,true表示會替換生成的JAVA文件嗤形,false則不覆蓋 -->
                    <overwrite>true</overwrite>
                </configuration>
                <dependencies>
                    <!--mysql驅(qū)動(dòng)包-->
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>8.0.19</version>
                    </dependency>
                    <dependency>
                        <groupId>org.mybatis.generator</groupId>
                        <artifactId>mybatis-generator-core</artifactId>
                        <version>1.4.0</version>
                    </dependency>
                </dependencies>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
2. 創(chuàng)建bootstrap.yml配置文件(類路徑resources目錄下)
spring:
  cloud:
    ## Nacos認(rèn)證信息
    nacos:
      config:
        username: nacos
        password: nacos
        context-path: /nacos
        server-addr: 127.0.0.1:1111 # 設(shè)置配置中心服務(wù)端地址
        namespace:   # Nacos 配置中心的namespace。需要注意弧圆,如果使用 public 的 namcespace 赋兵,請不要填寫這個(gè)值,直接留空即可
3. 創(chuàng)建application.yml配置文件(類路徑resources目錄下)
spring:
  application:
    name: spring-cloud-alibaba-seata-account-8007
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    name: defaultDataSource
    url: jdbc:mysql://localhost:3306/seata_account?serverTimezone=UTC
    username: root
    password: 12345678
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:1111
        namespace: public
        username:
        password:
    sentinel:
      transport:
        dashboard: 127.0.0.1:8080
        port: 8719
    alibaba:
      seata:
        tx-service-group: service-account-group
server:
  port: 8007
seata:
  application-id: ${spring.application.name}
  tx-service-group: service-account-group
  service:
    grouplist:
      seata-server: 127.0.0.1:8091
  registry:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:1111
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:1111
feign:
  sentinel:
    enabled: true
management:
  endpoints:
    web:
      exposure:
        include: "*"
##### MyBatis 配置 ###
mybatis:
  # 指定 mapper.xml 的位置
  mapper-locations: classpath:mybatis/mapper/*.xml
  #掃描實(shí)體類的位置,在此處指明掃描實(shí)體類的包搔预,在 mapper.xml 中就可以不寫實(shí)體類的全路徑名
  type-aliases-package: com.sst.cx.domain
  configuration:
    #默認(rèn)開啟駝峰命名法霹期,可以不用設(shè)置該屬性
    map-underscore-to-camel-case: true
4. 創(chuàng)建Account.java
package com.sst.cx.domain;
import java.math.BigDecimal;
public class Account {
    private Long id;
    private Long userId;
    private BigDecimal total;
    private BigDecimal used;
    private BigDecimal residue;
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public Long getUserId() {
        return userId;
    }
    public void setUserId(Long userId) {
        this.userId = userId;
    }
    public BigDecimal getTotal() {
        return total;
    }
    public void setTotal(BigDecimal total) {
        this.total = total;
    }
    public BigDecimal getUsed() {
        return used;
    }
    public void setUsed(BigDecimal used) {
        this.used = used;
    }
    public BigDecimal getResidue() {
        return residue;
    }
    public void setResidue(BigDecimal residue) {
        this.residue = residue;
    }
}
5. 創(chuàng)建AccountMapper.java
package com.sst.cx.mapper;
import org.apache.ibatis.annotations.Mapper;
import java.math.BigDecimal;
import com.sst.cx.domain.Account;
@Mapper
public interface AccountMapper {
    Account selectByUserId(Long userId);
    int decrease(Long userId, BigDecimal money);
}
6. 創(chuàng)建AccountMapper.xml(resouces/mybatis/mapper 目錄下)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sst.cx.mapper.AccountMapper">
    <resultMap id="BaseResultMap" type="Account">
        <id column="id" jdbcType="BIGINT" property="id"/>
        <result column="user_id" jdbcType="BIGINT" property="userId"/>
        <result column="total" jdbcType="DECIMAL" property="total"/>
        <result column="used" jdbcType="DECIMAL" property="used"/>
        <result column="residue" jdbcType="DECIMAL" property="residue"/>
    </resultMap>
    <sql id="Base_Column_List">
        id
        , user_id, total, used, residue
    </sql>
    <select id="selectByUserId" resultType="Account">
        select
        <include refid="Base_Column_List"/>
        from t_account
        where user_id = #{userId,jdbcType=BIGINT}
    </select>
    <update id="decrease">
        UPDATE t_account
        SET residue = residue - #{money},
            used    = used + #{money}
        WHERE user_id = #{userId};
    </update>
</mapper>
7. 創(chuàng)建AccountService.java
package com.sst.cx.service;
import org.springframework.web.bind.annotation.RequestParam;
import java.math.BigDecimal;
public interface AccountService {
    // 扣減賬戶余額
    int decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
}
8. 創(chuàng)建AccountServiceImpl.java
package com.sst.cx.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.math.BigDecimal;
import com.sst.cx.domain.Account;
import com.sst.cx.mapper.AccountMapper;
import com.sst.cx.service.AccountService;
@Service
@Slf4j
public class AccountServiceImpl implements AccountService {
    @Resource
    AccountMapper accountMapper;
    @Override
    public int decrease(Long userId, BigDecimal money) {

        log.info("------->account-service 開始查詢賬戶余額");
        Account account = accountMapper.selectByUserId(userId);
        log.info("------->account-service 賬戶余額查詢完成," + account);
        if (account != null && account.getResidue().intValue() >= money.intValue()) {
            log.info("------->account-service 開始從賬戶余額中扣錢拯田!");
            int decrease = accountMapper.decrease(userId, money);
            log.info("------->account-service 從賬戶余額中扣錢完成");
            return decrease;
        } else {
            log.info("賬戶余額不足历造,開始回滾!");
            throw new RuntimeException("賬戶余額不足勿锅!");
        }
    }
}
9. 創(chuàng)建AccountController.java
package com.sst.cx.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
import javax.annotation.Resource;
import com.sst.cx.domain.CommonResult;
import com.sst.cx.service.AccountService;
@RestController
@Slf4j
public class AccountController {
    @Resource
    private AccountService accountService;
    @Value("${server.port}")
    private String serverPort;
    @PostMapping(value = "/account/decrease")
    CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money) {
        int decrease = accountService.decrease(userId, money);
        CommonResult result;
        if (decrease > 0) {
            result = new CommonResult(200, "from mysql,serverPort:  " + serverPort, decrease);
        } else {
            result = new CommonResult(505, "from mysql,serverPort:  " + serverPort, "賬戶扣減失敗");
        }
        return result;
    }
}
10. 給主啟動(dòng)類添加@EnableDiscoveryClient帕膜、@EnableFeignClients注解
package com.sst.cx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication(scanBasePackages = "com.sst.cx")
public class SpringCloudAlibabaSeataAccount8007Application {
    public static void main(String[] args) {
        SpringApplication.run(SpringCloudAlibabaSeataAccount8007Application.class, args);
    }
}
11. 依次啟動(dòng)NacosServer集群枣氧、SeataServer溢十、spring-cloud-alibaba-seata-order-8005、spring-cloud-alibaba-seata-storage-8006达吞、spring-cloud-alibaba-seata-account-8007张弛,當(dāng)控制臺出現(xiàn)register success日志時(shí),說明該服務(wù)已經(jīng)成功連接上SeataServer(TC)。 
在瀏覽器中訪問http://localhost:8005/order/create/1/2/20吞鸭,結(jié)果如下:
  {"code":200,"message":"訂單創(chuàng)建成功","data":null}
  執(zhí)行以下SQL語句 
    1. 查詢seata_order數(shù)據(jù)庫中的t_order表:SELECT * FROM seata_order.t_order;  
    2. 查詢seata_storage數(shù)據(jù)庫中的t_storage表:SELECT * FROM seata_storage.t_storage;
    3. 查詢seata_account數(shù)據(jù)庫中的t_account表:SELECT * FROM seata_account.t_account;
    可以看到寺董,已經(jīng)創(chuàng)建一條訂單數(shù)據(jù),且訂單狀態(tài)(status)已修改為“已完成”刻剥;商品庫存已經(jīng)扣減 2 件遮咖,倉庫中商品儲量從 100 件減少到了 98 件;賬戶余額已經(jīng)扣減造虏,金額從 1000 元減少到了 980 元御吞。
在瀏覽器中訪問http://localhost:8005/order/create/1/10/1000,頁面會顯示異常信息漓藕,提示賬戶余額不足陶珠。再次查詢t_order表、t_storage表享钞、t_account表揍诽,從控制臺可以看到異常后發(fā)生了回滾。


項(xiàng)目結(jié)構(gòu)

Order訂單服務(wù)回滾

Storage庫存服務(wù)回滾

Account賬戶服務(wù)回滾
給類/方法添加@GlobalTransactional注解來實(shí)現(xiàn)分布式事務(wù)的開啟栗竖、管理和控制(當(dāng)調(diào)用該方法時(shí)TM會先向TC注冊全局事務(wù)暑脆,由TC生成一個(gè)全局唯一的XID并返回給TM)。
  @GlobalTransactional(name = "com-sst-cx-create-order", rollbackFor = Exception.class)

存疑:不添加@GlobalTransactional注解也開啟了全局事務(wù)划滋。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末饵筑,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子处坪,更是在濱河造成了極大的恐慌根资,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件同窘,死亡現(xiàn)場離奇詭異玄帕,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)想邦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進(jìn)店門裤纹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人丧没,你說我怎么就攤上這事鹰椒。” “怎么了呕童?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵漆际,是天一觀的道長。 經(jīng)常有香客問我夺饲,道長奸汇,這世上最難降的妖魔是什么施符? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮擂找,結(jié)果婚禮上戳吝,老公的妹妹穿的比我還像新娘。我一直安慰自己贯涎,他們只是感情好听哭,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著塘雳,像睡著了一般欢唾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上粉捻,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天礁遣,我揣著相機(jī)與錄音,去河邊找鬼肩刃。 笑死祟霍,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的盈包。 我是一名探鬼主播沸呐,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼呢燥!你這毒婦竟也來了崭添?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤叛氨,失蹤者是張志新(化名)和其女友劉穎呼渣,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體寞埠,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡屁置,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了仁连。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蓝角。...
    茶點(diǎn)故事閱讀 40,115評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖饭冬,靈堂內(nèi)的尸體忽然破棺而出使鹅,到底是詐尸還是另有隱情,我是刑警寧澤昌抠,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布患朱,位于F島的核電站,受9級特大地震影響扰魂,放射性物質(zhì)發(fā)生泄漏麦乞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一劝评、第九天 我趴在偏房一處隱蔽的房頂上張望姐直。 院中可真熱鬧,春花似錦蒋畜、人聲如沸声畏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽插龄。三九已至,卻和暖如春科展,著一層夾襖步出監(jiān)牢的瞬間均牢,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工才睹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留徘跪,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓琅攘,卻偏偏與公主長得像垮庐,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子坞琴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容