<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Spring Cloud Alibaba 使用Seata解決分布式事務(wù)

          共 19566字,需瀏覽 40分鐘

           ·

          2021-12-10 12:21

          點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”

          優(yōu)質(zhì)文章,第一時(shí)間送達(dá)



          -? ? ?為什么會(huì)產(chǎn)生分布式事務(wù)?? ? -

          隨著業(yè)務(wù)的快速發(fā)展,網(wǎng)站系統(tǒng)往往由單體架構(gòu)逐漸演變?yōu)榉植际健⑽⒎?wù)架構(gòu),而對(duì)于數(shù)據(jù)庫(kù)則由單機(jī)數(shù)據(jù)庫(kù)架構(gòu)向分布式數(shù)據(jù)庫(kù)架構(gòu)轉(zhuǎn)變。此時(shí),我們會(huì)將一個(gè)大的應(yīng)用系統(tǒng)拆分為多個(gè)可以獨(dú)立部署的應(yīng)用服務(wù),需要各個(gè)服務(wù)之間進(jìn)行遠(yuǎn)程協(xié)作才能完成事務(wù)操作。在微服務(wù)項(xiàng)目中通常一個(gè)大項(xiàng)目會(huì)被拆分為N個(gè)子項(xiàng)目,例如用戶中心服務(wù),會(huì)員中心服務(wù),支付中心服務(wù)等一系列微服務(wù),在面臨各種業(yè)務(wù)需求時(shí)難免會(huì)產(chǎn)生用戶中心服務(wù)中需要調(diào)用會(huì)員中心服務(wù),支付中心服務(wù)而產(chǎn)生調(diào)用鏈路;服務(wù)與服務(wù)之間通訊采用RPC遠(yuǎn)程調(diào)用技術(shù),但是每個(gè)服務(wù)中都有自己獨(dú)立的數(shù)據(jù)源,即自己獨(dú)立的本地事務(wù);兩個(gè)服務(wù)相互進(jìn)行通訊的時(shí)候,兩個(gè)本地事務(wù)互不影響,從而出現(xiàn)分布式事務(wù)產(chǎn)生的原因。



          -? ? ?Seata簡(jiǎn)介? ? -

          Seata 是一款開源的分布式事務(wù)解決方案,致力于提供高性能和簡(jiǎn)單易用的分布式事務(wù)服務(wù)。Seata 將為用戶提供了 AT、TCC、SAGA 和 XA 事務(wù)模式,為用戶打造一站式的分布式解決方案。

          Seata核心術(shù)語(yǔ)

          TC (Transaction Coordinator) - 事務(wù)協(xié)調(diào)者:維護(hù)全局和分支事務(wù)的狀態(tài),驅(qū)動(dòng)全局事務(wù)提交或回滾。

          TM (Transaction Manager) - 事務(wù)管理器:定義全局事務(wù)的范圍:開始全局事務(wù)、提交或回滾全局事務(wù)。

          RM (Resource Manager) - 資源管理器:管理分支事務(wù)處理的資源,與TC交談以注冊(cè)分支事務(wù)和報(bào)告分支事務(wù)的狀態(tài),并驅(qū)動(dòng)分支事務(wù)提交或回滾。

          AT模式工作機(jī)制

          根據(jù)官方說(shuō)明當(dāng)前:通過JDBC訪問支持本地 ACID 事務(wù)的關(guān)系型數(shù)據(jù)庫(kù)的Java應(yīng)用程序才支持AT模式。

          兩階段提交協(xié)議的演變:

          • 一階段:業(yè)務(wù)數(shù)據(jù)和回滾日志記錄在同一個(gè)本地事務(wù)中提交,釋放本地鎖和連接資源。

          • 二階段:

            • 提交異步化,非常快速地完成。

            • 回滾通過一階段的回滾日志進(jìn)行反向補(bǔ)償。

          更詳細(xì)可參考官方文檔:?http://seata.io/zh-cn/docs/dev/mode/at-mode.html



          -? ? ?Seata Server 部署? ? -

          官方Seata配置中心信息:https://github.com/seata/seata/blob/develop/script/config-center/config.txt
          官方Seata Nacos配置部署腳本:https://github.com/seata/seata/blob/develop/script/config-center/config.txt
          版本信息與Seata Server下載地址可參考首頁(yè)介紹文檔:https://gitee.com/SimpleWu/spring-cloud-alibaba-example

          Seata目錄結(jié)構(gòu)說(shuō)明:

          • bin:運(yùn)行腳本

          • conf:配置文件

          • lib:依賴包

          當(dāng)前部署方式采用Nacos作為注冊(cè)中心與配置中心。

          registry.conf

          該配置位于conf目錄,按下以下注釋區(qū)域進(jìn)行修改

          registry?{
          ??#?file?、nacos?、eureka、redis、zk、consul、etcd3、sofa
          ??#?使用nacos作為注冊(cè)中心
          ??type?=?"nacos"

          ??nacos?{
          ????#?注冊(cè)到nacos應(yīng)用名稱
          ????application?=?"seata-server"
          ????#?nacos?ip
          ????serverAddr?=?"127.0.0.1:8848"
          ????#?所在分組
          ????group?=?"EXAMPLE-GROUP"
          ????#?所在命名空間
          ????namespace?=?"7e3699fa-09eb-4d47-8967-60f6c98da94a"
          ????#?所在集群
          ????#cluster?=?"default"
          ????username?=?"nacos"
          ????password?=?"nacos"
          ??}
          ??eureka?{
          ????serviceUrl?=?"http://localhost:8761/eureka"
          ????application?=?"default"
          ????weight?=?"1"
          ??}
          ??redis?{
          ????serverAddr?=?"localhost:6379"
          ????db?=?0
          ????password?=?""
          ????cluster?=?"default"
          ????timeout?=?0
          ??}
          ??zk?{
          ????cluster?=?"default"
          ????serverAddr?=?"127.0.0.1:2181"
          ????sessionTimeout?=?6000
          ????connectTimeout?=?2000
          ????username?=?""
          ????password?=?""
          ??}
          ??consul?{
          ????cluster?=?"default"
          ????serverAddr?=?"127.0.0.1:8500"
          ??}
          ??etcd3?{
          ????cluster?=?"default"
          ????serverAddr?=?"http://localhost:2379"
          ??}
          ??sofa?{
          ????serverAddr?=?"127.0.0.1:9603"
          ????application?=?"default"
          ????region?=?"DEFAULT_ZONE"
          ????datacenter?=?"DefaultDataCenter"
          ????cluster?=?"default"
          ????group?=?"SEATA_GROUP"
          ????addressWaitTime?=?"3000"
          ??}
          ??file?{
          ????name?=?"file.conf"
          ??}
          }

          config?{
          ??#?file、nacos?、apollo、zk、consul、etcd3
          ??#?使用nacos管理配置
          ??type?=?"nacos"

          ??nacos?{
          ????#?nacos?ip
          ????serverAddr?=?"127.0.0.1:8848"
          ????#?所在命名空間
          ????namespace?=?"7e3699fa-09eb-4d47-8967-60f6c98da94a"
          ????#?所在分組
          ????group?=?"EXAMPLE-GROUP"
          ????username?=?"nacos"
          ????password?=?"nacos"
          ??}
          ??consul?{
          ????serverAddr?=?"127.0.0.1:8500"
          ??}
          ??apollo?{
          ????appId?=?"seata-server"
          ????apolloMeta?=?"http://192.168.1.204:8801"
          ????namespace?=?"application"
          ??}
          ??zk?{
          ????serverAddr?=?"127.0.0.1:2181"
          ????sessionTimeout?=?6000
          ????connectTimeout?=?2000
          ????username?=?""
          ????password?=?""
          ??}
          ??etcd3?{
          ????serverAddr?=?"http://localhost:2379"
          ??}
          ??file?{
          ????name?=?"file.conf"
          ??}
          }

          以上內(nèi)容主要修改了注冊(cè)中心與配置中心為Nacos并且修改了Nacos地址與登錄賬號(hào)/登錄密碼,命名空間,分組;

          配置部署到Nacos

          這里簡(jiǎn)化了下Nacos官網(wǎng)下載的config.txt內(nèi)容,從官網(wǎng)下載的配置文本以下內(nèi)容標(biāo)記需要修改的需要關(guān)注

          #事務(wù)組?重點(diǎn)關(guān)注
          service.vgroupMapping.my_test_tx_group=default
          #服務(wù)段分組地址
          service.default.grouplist=127.0.0.1:8091
          #保持默認(rèn)
          service.enableDegrade=false
          #保持默認(rèn)
          service.disableGlobalTransaction=false
          #存儲(chǔ)方式選擇?db模式則數(shù)據(jù)庫(kù)
          store.mode=db
          #需修改
          store.lock.mode=db
          #需修改
          store.session.mode=db
          store.publicKey=
          #需修改
          store.db.datasource=druid
          #需修改
          store.db.dbType=mysql
          #需修改
          store.db.driverClassName=com.mysql.jdbc.Driver
          #需修改
          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
          client.undo.dataValidation=true
          #需修改
          #jackson改為kryo?解決數(shù)據(jù)庫(kù)Datetime類型問題
          client.undo.logSerialization=kryo
          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
          log.exceptionRate=100
          transport.serialization=seata
          transport.compressor=none

          其中該配置需要重點(diǎn)關(guān)注service.vgroupMapping.my_test_tx_group=default這里的配置與微服務(wù)應(yīng)用中的配置必須要一致后面會(huì)描述到。

          由于有時(shí)間類型是Seata回滾反序列化Date類型無(wú)法成功反序列化,需要修改序列化方式解決該問題:?client.undo.logSerialization=kryo

          修改完所有配置運(yùn)行從官網(wǎng)下載的nacos-config.sh文件將文本內(nèi)容上次到nacos配置中心中:

          #?-h?ip?-p?端口?-t?命名空間?-g?分組
          sh?nacos-config.sh?-h?localhost?-p?8848?-t?7e3699fa-09eb-4d47-8967-60f6c98da94a?-g?EXAMPLE-GROUP

          部署好配置文件之后在Nacos命名空間為7e3699fa-09eb-4d47-8967-60f6c98da94a(dev)的配置管理界面可以看到文本中的內(nèi)容。

          Seata數(shù)據(jù)庫(kù)

          按照config.txt中對(duì)應(yīng)的數(shù)據(jù)庫(kù)連接信息創(chuàng)建Seata數(shù)據(jù)庫(kù)并且創(chuàng)建以下幾張表

          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_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;
          部署Seata Server

          以上工作準(zhǔn)備就緒,進(jìn)入bin目錄運(yùn)行seata-server.bat(windows用戶)/seata-server.sh(linux用戶)即可。



          -? ? ?Seata應(yīng)用場(chǎng)景模擬? ? -

          這里做一個(gè)用戶服務(wù)用戶登錄成功后調(diào)用會(huì)員服務(wù)增加會(huì)員積分場(chǎng)景案例。

          父工程改造

          工程名稱:spring-cloud-alibaba-version-parent,增加mybatis,seata序列化等依賴版本管理。



          3.4.2
          2.5.4
          1.3.0



          ????com.baomidou
          ????mybatis-plus-boot-starter
          ????${mybatis.plus.version}


          ????io.seata
          ????seata-serializer-kryo
          ????${seata.serializer.kryo.version}

          會(huì)員服務(wù)工程改造

          工程名稱:spring-cloud-alibaba-service-member,增加數(shù)據(jù)庫(kù)與Seata依賴,增加用戶會(huì)員積分接口。

          pom.xml

          ?

          ?com.alibaba.cloud
          ?spring-cloud-starter-alibaba-seata


          ?io.seata
          ?seata-serializer-kryo


          ?com.baomidou
          ?mybatis-plus-boot-starter


          ?mysql
          ?mysql-connector-java

          bootstrap.yaml

          #注意,此處省略之前配置的信息....
          #注意,此處省略之前配置的信息....
          #注意,此處省略之前配置的信息....
          #注意,此處省略之前配置的信息....
          #數(shù)據(jù)庫(kù)信息配置
          spring:
          ??datasource:
          ????driver-class-name:?com.mysql.cj.jdbc.Driver
          ????url:?jdbc:mysql://localhost:3306/member_db?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true
          ????username:?root
          ????password:?123456
          #Seata配置
          seata:
          ??enabled:?true
          ??application-id:?${spring.application.name}
          ??#對(duì)應(yīng)nacos配置?service.vgroupMapping.my_test_tx_group
          ??tx-service-group:?'my_test_tx_group'
          ??service:
          ????vgroup-mapping:
          ??????#對(duì)應(yīng)nacos配置?service.vgroupMapping.my_test_tx_group?的值?default
          ??????my_test_tx_group:?'default'
          ??registry:
          ????type:?nacos
          ????nacos:
          ??????server-addr:?${spring.cloud.nacos.discovery.server-addr}
          ??????namespace:?${spring.cloud.nacos.discovery.namespace}
          ??????group:?${spring.cloud.nacos.discovery.group}
          ??????#cluster:?${spring.cloud.nacos.discovery.cluster}
          ??config:
          ????type:?nacos
          ????nacos:
          ??????server-addr:?${spring.cloud.nacos.discovery.server-addr}
          ??????namespace:?${spring.cloud.nacos.discovery.namespace}
          ??????group:?${spring.cloud.nacos.discovery.group}

          注意事項(xiàng):

          1. bootstrap.yaml中seata.tx-service-group 配置項(xiàng)一定要配置nacos配置中心中service.vgroupMapping對(duì)應(yīng)的my_test_tx_group。也就是說(shuō)一定要保持一致。

          2. bootstrap.yaml中seata.service.vgroup-mapping.my_test_tx_group配置項(xiàng)一定要配置nacos配置中心對(duì)應(yīng)service.vgroupMapping.my_test_tx_group配置祥的值。

          如果沒有注意上方兩點(diǎn)將會(huì)導(dǎo)致啟動(dòng)時(shí)報(bào):no available service 'default' found, please make sure registry config correct。

          創(chuàng)建member_db數(shù)據(jù)庫(kù)

          其中undo_log表為Seata回滾日志表,需要在每個(gè)使用到Seata的業(yè)務(wù)服務(wù)數(shù)據(jù)庫(kù)中都需要?jiǎng)?chuàng)建。

          SET?NAMES?utf8mb4;
          SET?FOREIGN_KEY_CHECKS?=?0;

          --?----------------------------
          --?Table?structure?for?t_member_integral
          --?----------------------------
          DROP?TABLE?IF?EXISTS?`t_member_integral`;
          CREATE?TABLE?`t_member_integral`??(
          ??`ID`?bigint(20)?NOT?NULL?COMMENT?'主鍵',
          ??`USERNAME`?varchar(55)?CHARACTER?SET?utf8mb4?COLLATE?utf8mb4_general_ci?DEFAULT?NULL?COMMENT?'用戶名稱',
          ??`INTEGRAL`?int(11)?DEFAULT?NULL?COMMENT?'積分',
          ??`CREDATE`?datetime(0)?DEFAULT?NULL?COMMENT?'時(shí)間',
          ??PRIMARY?KEY?(`ID`)?USING?BTREE
          )?ENGINE?=?InnoDB?CHARACTER?SET?=?utf8mb4?COLLATE?=?utf8mb4_general_ci?ROW_FORMAT?=?Dynamic;

          --?----------------------------
          --?Table?structure?for?undo_log
          --?----------------------------
          DROP?TABLE?IF?EXISTS?`undo_log`;
          CREATE?TABLE?`undo_log`??(
          ??`id`?bigint(20)?NOT?NULL?AUTO_INCREMENT,
          ??`branch_id`?bigint(20)?NOT?NULL,
          ??`xid`?varchar(100)?CHARACTER?SET?utf8?COLLATE?utf8_general_ci?NOT?NULL,
          ??`context`?varchar(128)?CHARACTER?SET?utf8?COLLATE?utf8_general_ci?NOT?NULL,
          ??`rollback_info`?longblob?NOT?NULL,
          ??`log_status`?int(11)?NOT?NULL,
          ??`log_created`?datetime(0)?NOT?NULL,
          ??`log_modified`?datetime(0)?NOT?NULL,
          ??`ext`?varchar(100)?CHARACTER?SET?utf8?COLLATE?utf8_general_ci?DEFAULT?NULL,
          ??PRIMARY?KEY?(`id`)?USING?BTREE,
          ??UNIQUE?INDEX?`ux_undo_log`(`xid`,?`branch_id`)?USING?BTREE
          )?ENGINE?=?InnoDB?AUTO_INCREMENT?=?1?CHARACTER?SET?=?utf8?COLLATE?=?utf8_general_ci?ROW_FORMAT?=?Dynamic;

          SET?FOREIGN_KEY_CHECKS?=?1;

          新增會(huì)員積分CRUD

          我這里新增以下類,具體內(nèi)容大家都比較熟悉。

          MemberIntegralController.java
          IMemberIntegralBiz.java
          IMemberIntegralBizImpl.java
          MemberIntegralMapper.java
          MemberIntegral.xml

          在這里所有增加會(huì)員積分的邏輯都寫在同一個(gè)類中 MemberIntegralController.java

          import?com.baomidou.mybatisplus.core.toolkit.IdWorker;
          import?com.gitee.eample.member.service.biz.IMemberIntegralBiz;
          import?com.gitee.eample.member.service.domain.MemberIntegral;
          import?com.gtiee.example.common.exception.Response;
          import?org.springframework.beans.factory.annotation.Autowired;
          import?org.springframework.web.bind.annotation.PathVariable;
          import?org.springframework.web.bind.annotation.PostMapping;
          import?org.springframework.web.bind.annotation.RequestMapping;
          import?org.springframework.web.bind.annotation.RestController;

          import?java.util.Date;

          /**
          ?*?用戶積分
          ?*
          ?*?@author?wentao.wu
          ?*/
          @RestController
          @RequestMapping("/member/integral")
          public?class?MemberIntegralController?{
          ????@Autowired
          ????private?IMemberIntegralBiz?memberIntegralBiz;

          ????@PostMapping("/login/{username}")
          ????public?Response?login(@PathVariable("username")?String?username)?{
          ????????//?每天第一次登錄則增加積分,我這里就不判斷了,每次調(diào)用都新增一條積分記錄了
          ????????MemberIntegral?memberIntegral?=?new?MemberIntegral();
          ????????memberIntegral.setId(IdWorker.getId());
          ????????memberIntegral.setIntegral(10);//固定10積分
          ????????memberIntegral.setUsername(username);
          ????????memberIntegral.setCredate(new?Date());
          ????????memberIntegralBiz.save(memberIntegral);
          ????????return?Response.createOk("登錄新增會(huì)員積分成功!",?true);
          ????}
          }

          運(yùn)行MemberServiceApplication.java啟動(dòng)服務(wù),如果想知道有沒有注冊(cè)成功:

          第一可以看Seata Server端有沒有日志輸出,該日志內(nèi)容主要為注冊(cè)的業(yè)務(wù)服務(wù)的數(shù)據(jù)庫(kù)信息。

          第二可以看業(yè)務(wù)服務(wù)有沒有輸出以下日志,有輸出以下日志則Seata Server端注冊(cè)成功

          2021-11-05?09:56:30.962??INFO?16420?---?[???????????main]?i.s.c.r.netty.NettyClientChannelManager??:?will?connect?to?2.0.4.58:8091
          2021-11-05?09:56:30.962??INFO?16420?---?[???????????main]?i.s.c.rpc.netty.RmNettyRemotingClient????:?RM?will?register?:jdbc:mysql://localhost:3306/member_db
          2021-11-05?09:56:30.967??INFO?16420?---?[???????????main]?i.s.core.rpc.netty.NettyPoolableFactory??:?NettyPool?create?channel?to?transactionRole:RMROLE,address:2.0.4.58:8091,msg:'jdbc:mysql://localhost:3306/member_db',?applicationId='service-member',?transactionServiceGroup='my_test_tx_group'}?>
          用戶服務(wù)工程改造

          工程名稱:spring-cloud-alibaba-service-member,增加數(shù)據(jù)庫(kù)與Seata依賴,增加用戶登錄接口,增加調(diào)用會(huì)員服務(wù)積分接口feign。

          由于內(nèi)容一致此處省略pom.xml,bootstrap.xml(里面注意數(shù)據(jù)庫(kù)要修改為用戶服務(wù)的數(shù)據(jù)庫(kù))。

          創(chuàng)建user_db數(shù)據(jù)庫(kù)

          其中undo_log表為Seata回滾日志表,需要在每個(gè)使用到Seata的業(yè)務(wù)服務(wù)數(shù)據(jù)庫(kù)中都需要?jiǎng)?chuàng)建。


          SET?NAMES?utf8mb4;
          SET?FOREIGN_KEY_CHECKS?=?0;

          --?----------------------------
          --?Table?structure?for?t_user
          --?----------------------------
          DROP?TABLE?IF?EXISTS?`t_user`;
          CREATE?TABLE?`t_user`??(
          ??`ID`?bigint(20)?NOT?NULL?COMMENT?'主鍵',
          ??`USERNAME`?varchar(55)?CHARACTER?SET?utf8mb4?COLLATE?utf8mb4_general_ci?DEFAULT?NULL?COMMENT?'用戶名',
          ??`PWD`?varchar(255)?CHARACTER?SET?utf8mb4?COLLATE?utf8mb4_general_ci?DEFAULT?NULL?COMMENT?'密碼',
          ??`ADDR`?varchar(255)?CHARACTER?SET?utf8mb4?COLLATE?utf8mb4_general_ci?DEFAULT?NULL?COMMENT?'地址',
          ??`LAST_LOGIN_DATE`?datetime(0)?DEFAULT?NULL?COMMENT?'最后登錄時(shí)間',
          ??PRIMARY?KEY?(`ID`)?USING?BTREE
          )?ENGINE?=?InnoDB?CHARACTER?SET?=?utf8mb4?COLLATE?=?utf8mb4_general_ci?ROW_FORMAT?=?Dynamic;

          --?----------------------------
          --?Records?of?t_user
          --?----------------------------
          INSERT?INTO?`t_user`?VALUES?(1,?'test1',?'123456',?'123',?NULL);

          --?----------------------------
          --?Table?structure?for?undo_log
          --?----------------------------
          DROP?TABLE?IF?EXISTS?`undo_log`;
          CREATE?TABLE?`undo_log`??(
          ??`id`?bigint(20)?NOT?NULL?AUTO_INCREMENT,
          ??`branch_id`?bigint(20)?NOT?NULL,
          ??`xid`?varchar(100)?CHARACTER?SET?utf8?COLLATE?utf8_general_ci?NOT?NULL,
          ??`context`?varchar(128)?CHARACTER?SET?utf8?COLLATE?utf8_general_ci?NOT?NULL,
          ??`rollback_info`?longblob?NOT?NULL,
          ??`log_status`?int(11)?NOT?NULL,
          ??`log_created`?datetime(0)?NOT?NULL,
          ??`log_modified`?datetime(0)?NOT?NULL,
          ??`ext`?varchar(100)?CHARACTER?SET?utf8?COLLATE?utf8_general_ci?DEFAULT?NULL,
          ??PRIMARY?KEY?(`id`)?USING?BTREE,
          ??UNIQUE?INDEX?`ux_undo_log`(`xid`,?`branch_id`)?USING?BTREE
          )?ENGINE?=?InnoDB?AUTO_INCREMENT?=?1?CHARACTER?SET?=?utf8?COLLATE?=?utf8_general_ci?ROW_FORMAT?=?Dynamic;

          SET?FOREIGN_KEY_CHECKS?=?1;

          新增用戶登錄CRUD

          我這里新增以下類,具體內(nèi)容大家都比較熟悉。

          UserController.java
          IUserBiz.java
          IUserBizImpl.java
          UserMapper.java
          UserMapper.xml
          MemberInfoControllerClient.java

          MemberInfoControllerClient.java

          /**
          ?*?service-member服務(wù)遠(yuǎn)程調(diào)用接口
          ?*
          ?*?@author?wentao.wu
          ?*/
          @FeignClient(name?=?"service-member")
          public?interface?MemberInfoControllerClient?{
          ????/**
          ?????*?登錄送積分
          ?????*
          ?????*?@param?username
          ?????*?@return
          ?????*/
          ????@PostMapping("/member/integral/login/{username}")
          ????Response?login(@PathVariable("username")String?username);
          }

          IUserBiz.java

          public?interface?IUserBiz?extends?IService?{
          ????/**
          ?????*?用戶登錄并且贈(zèng)送第一次登錄積分
          ?????*
          ?????*?@param?command
          ?????*?@return
          ?????*/
          ????boolean?login(UserLoginCommand?command);

          }

          IUserBizImpl.java

          package?com.gitee.eample.user.service.biz;

          import?com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
          import?com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
          import?com.gitee.eample.user.service.controller.command.UserLoginCommand;
          import?com.gitee.eample.user.service.dao.UserMapper;
          import?com.gitee.eample.user.service.domain.User;
          import?com.gitee.eample.user.service.feign.MemberInfoControllerClient;
          import?com.gtiee.example.common.exception.Response;
          import?io.seata.spring.annotation.GlobalTransactional;
          import?org.springframework.beans.factory.annotation.Autowired;
          import?org.springframework.stereotype.Service;
          import?org.springframework.util.ObjectUtils;

          import?java.util.Date;

          @Service
          public?class?IUserBizImpl?extends?ServiceImpl?implements?IUserBiz?{

          ????@Autowired
          ????private?MemberInfoControllerClient?client;

          ????@GlobalTransactional(name?=?"login_add_member_intergral",rollbackFor?=?Exception.class)//開啟分布式事務(wù)
          ????@Override
          ????public?boolean?login(UserLoginCommand?command)?{
          ????????LambdaQueryWrapper?wrapper?=?new?LambdaQueryWrapper<>();
          ????????wrapper.eq(User::getUsername,?command.getUsername())
          ????????????????.eq(User::getPwd,?command.getPwd());
          ????????User?loginUser?=?getOne(wrapper);
          ????????if?(ObjectUtils.isEmpty(loginUser))?{
          ????????????return?false;
          ????????}
          ????????//調(diào)用會(huì)員登錄接口增加積分
          ????????Response?response?=?client.login(command.getUsername());
          ????????if?(response.isOk())?{//增加積分成功,或已增加積分
          ????????????//調(diào)用積分接口成功,修改當(dāng)前用戶登錄時(shí)間
          ????????????loginUser.setLastLoginDate(new?Date());
          ????????????updateById(loginUser);
          ????????????//假設(shè)此處發(fā)生異常,不但修改當(dāng)前用戶登錄時(shí)間需要回滾并且新增的會(huì)員積分信息也回滾才算正常
          ????????????int?i?=?0?/?0;
          ????????????return?true;
          ????????}?else?{
          ????????????//增加積分失敗
          ????????????return?false;
          ????????}
          ????}
          }

          UserController.java

          import?com.gitee.eample.user.service.biz.IUserBiz;
          import?com.gitee.eample.user.service.controller.command.UserLoginCommand;
          import?com.gtiee.example.common.exception.Response;
          import?org.slf4j.Logger;
          import?org.slf4j.LoggerFactory;
          import?org.springframework.beans.factory.annotation.Autowired;
          import?org.springframework.web.bind.annotation.PostMapping;
          import?org.springframework.web.bind.annotation.RequestMapping;
          import?org.springframework.web.bind.annotation.RestController;

          /**
          ?*?User?Business?Controller
          ?*
          ?*?@author?wentao.wu
          ?*/
          @RestController
          @RequestMapping("/users/")
          public?class?UserController?{
          ????private?Logger?logger?=?LoggerFactory.getLogger(UserController.class);
          ????@Autowired
          ????private?IUserBiz?userBiz;

          ????@PostMapping("/login")
          ????public?Response?login(UserLoginCommand?command)?{
          ????????try?{
          ????????????boolean?result?=?userBiz.login(command);
          ????????????if?(result)?{
          ????????????????return?Response.createOk("登錄并贈(zèng)送積分成功!",?result);
          ????????????}else{
          ????????????????return?Response.createError("賬號(hào)或密碼不存在!",?result);
          ????????????}
          ????????}?catch?(Exception?e)?{
          ????????????logger.error("登錄失敗!",?e);
          ????????????return?Response.createError("服務(wù)器繁忙請(qǐng)稍后再試!",?false);
          ????????}
          ????}

          }

          運(yùn)行啟動(dòng)類UserServiceApplication.java。

          服務(wù)改造成功后主要模擬有三個(gè)場(chǎng)景:

          1. 有分布式事務(wù)處理發(fā)生異常場(chǎng)景:調(diào)用積分接口成功,修改當(dāng)前用戶登錄時(shí)間之后發(fā)生異常,用戶表的修改操作進(jìn)行回滾,同時(shí)會(huì)員服務(wù)新增的用戶對(duì)應(yīng)的積分?jǐn)?shù)據(jù)同樣發(fā)生回滾

          2. 無(wú)分布式事務(wù)處理發(fā)生異常場(chǎng)景: 調(diào)用積分接口成功,修改當(dāng)前用戶登錄時(shí)間之后發(fā)生異常,用戶表的修改操作進(jìn)行回滾,用戶會(huì)員新增的數(shù)據(jù)并沒有發(fā)生回滾,此處造成數(shù)據(jù)異常。

          3. 正常執(zhí)行場(chǎng)景: 調(diào)用積分接口成功,修改當(dāng)前用戶登錄時(shí)間之后未發(fā)生異常,所有操作生效。

          有分布式事務(wù)處理發(fā)生異常場(chǎng)景

          IUserBizImpl.java中l(wèi)ogin方法增加分布式事務(wù)注解 @GlobalTransactional(name = "login_add_member_intergral",rollbackFor = Exception.class)//開啟分布式事務(wù),name為屬性名稱,rollbackFor 為指定回滾異常。

          首先在用戶服務(wù)表中插入一條用戶數(shù)據(jù),作為登錄用戶:

          INSERT?INTO?`user_db`.`t_user`(`ID`,?`USERNAME`,?`PWD`,?`ADDR`,?`LAST_LOGIN_DATE`)?VALUES?(1,?'test1',?'123456',?'123',?NULL);

          并且當(dāng)前會(huì)員服務(wù)t_member_integral表中還沒有數(shù)據(jù)還沒初始化過數(shù)據(jù),當(dāng)前場(chǎng)景操作會(huì)修改t_user.LAST_LOGIN_DATE,并且向t_member_integral表中插入數(shù)據(jù);但是最后發(fā)生異常導(dǎo)致操作失敗,并且存在分布式事務(wù)注解,此時(shí)會(huì)回滾所有服務(wù)DML操作。

          請(qǐng)求用戶登錄接口:

          請(qǐng)求成功后查看t_user與t_member_integral依舊沒有發(fā)生任何改變:


          表示分布式事務(wù)處理成功無(wú)任何異常。

          無(wú)分布式事務(wù)處理發(fā)生異常場(chǎng)景

          IUserBizImpl.java中l(wèi)ogin方法注釋掉全局事務(wù)(分布式事務(wù)),并且修改為本地事務(wù):

          //@GlobalTransactional(name?=?"login_add_member_intergral",rollbackFor?=?Exception.class)//開啟分布式事務(wù)
          @Transactional

          請(qǐng)求用戶登錄接口:

          此時(shí)發(fā)生的異常導(dǎo)致了用戶服務(wù)中修改LAST_LOGIN_DATE操作被回滾成功,但是t_member_integral表中依然插入了積分?jǐn)?shù)據(jù)并未被回滾:

          表示在跨服務(wù)調(diào)用下沒有分布式事務(wù)將會(huì)導(dǎo)致數(shù)據(jù)不一致,事務(wù)異常。

          正常執(zhí)行場(chǎng)景

          IUserBizImpl.java中l(wèi)ogin方法注釋掉本地事務(wù),并且修改為全局事務(wù)(分布式事務(wù)),這里改不改無(wú)所謂,事務(wù)都是成功的,無(wú)論使用本地事務(wù)與全局事務(wù)都不會(huì)有問題,此處改成全局事務(wù)主要是驗(yàn)證全局事務(wù)不會(huì)影響什么:

          @GlobalTransactional(name?=?"login_add_member_intergral",rollbackFor?=?Exception.class)//開啟分布式事務(wù)
          //@Transactional

          同時(shí)將login方法中的異常處理去除掉:

          //假設(shè)此處發(fā)生異常,不但修改當(dāng)前用戶登錄時(shí)間需要回滾并且新增的會(huì)員積分信息也回滾才算正常
          int?i?=?0?/?0;

          請(qǐng)求用戶登錄接口,此時(shí)所有操作全部成功,用戶服務(wù)修改LAST_LOGIN_DATE成功,并且t_member_integral表中數(shù)據(jù)新增成功;這里就不貼圖了,浪費(fèi)大家流量。



          -? ? ?總結(jié)? ? -

          • 每個(gè)業(yè)務(wù)服務(wù)對(duì)應(yīng)的數(shù)據(jù)庫(kù)中都需要包含undo_log表,這個(gè)表主要是記錄全局事務(wù)操作的日志,后續(xù)發(fā)生異常Seata會(huì)通過該日志進(jìn)行事務(wù)回滾補(bǔ)償;

          • Seata回滾反序列化時(shí)Date類型無(wú)法反序列化,所以要修改Seata的序列化為:kryo;(此問題將在1.5版本 Seata發(fā)布后徹底解決)



          -? ? ?源碼代碼存放地址 ? -

          gitee:?https://gitee.com/SimpleWu/spring-cloud-alibaba-example.git

          ? 作者?|??SimpleWu

          來(lái)源 |??cnblogs.com/SimpleWu/p/15529920.html



          瀏覽 59
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产在线成人视频 | 综合玖玖 | 国产一级片免费观看 | 蜜芽av成人 | 国产一级A片在线免费观看 |