日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

SpringCloud与Seata分布式事务初体验

發(fā)布時(shí)間:2023/11/30 javascript 78 豆豆
生活随笔 收集整理的這篇文章主要介紹了 SpringCloud与Seata分布式事务初体验 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

在本篇文章中我們?cè)赟pringCloud環(huán)境下通過使用Seata來模擬用戶購(gòu)買商品時(shí)由于用戶余額不足導(dǎo)致本次訂單提交失敗,來驗(yàn)證下在MySQL數(shù)據(jù)庫內(nèi)事務(wù)是否會(huì)回滾。

本章文章只涉及所需要測(cè)試的服務(wù)列表以及Seata配置部分。

用戶提交訂單購(gòu)買商品大致分為以下幾個(gè)步驟:

  • 減少庫存
  • 扣除金額
  • 提交訂單
  • 1. 準(zhǔn)備環(huán)境

    • Seata Server

      如果對(duì)Seata Server部署方式還不了解,請(qǐng)?jiān)L問:http://blog.yuqiyu.com/seata-init-env.html

    • Eureka Server

      服務(wù)注冊(cè)中心,如果對(duì)Eureka Server部署方式還不了解,請(qǐng)?jiān)L問http://blog.yuqiyu.com/eureka-server.html

    2. 準(zhǔn)備測(cè)試服務(wù)

    為了方便學(xué)習(xí)的同學(xué)查看源碼,我們本章節(jié)源碼采用Maven Module(多模塊)的方式進(jìn)行構(gòu)建。

    我們用于測(cè)試的服務(wù)所使用的第三方依賴都一致,各個(gè)服務(wù)的pom.xml文件內(nèi)容如下所示:

    ?

    <dependencies><!--Web--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--openfeign接口定義--><dependency><groupId>org.minbox.chapter</groupId><artifactId>openfeign-service</artifactId><version>0.0.1-SNAPSHOT</version></dependency><!--公共依賴--><dependency><groupId>org.minbox.chapter</groupId><artifactId>common-service</artifactId><version>0.0.1-SNAPSHOT</version></dependency><!--seata--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><!--Eureka Client--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency><dependency><groupId>com.zaxxer</groupId><artifactId>HikariCP</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>org.minbox.framework</groupId><artifactId>api-boot-starter-mybatis-enhance</artifactId></dependency> </dependencies>

    2.1 Openfeign接口定義模塊

    由于我們服務(wù)之間采用的Openfeign方式進(jìn)行相互調(diào)用,所以創(chuàng)建了一個(gè)模塊openfeign-service來提供服務(wù)接口的定義。

    • 賬戶服務(wù)提供的接口定義

    賬戶服務(wù)對(duì)外所提供的Openfeign接口定義如下所示:

    ?

    /*** 賬戶服務(wù)接口** @author 恒宇少年*/ @FeignClient(name = "account-service") @RequestMapping(value = "/account") public interface AccountClient {/*** 扣除指定賬戶金額** @param accountId 賬戶編號(hào)* @param money 金額*/@PostMappingvoid deduction(@RequestParam("accountId") Integer accountId, @RequestParam("money") Double money); }
    • 商品服務(wù)提供的接口定義

      商品服務(wù)對(duì)外所提供的Openfeign接口定義如下所示:

    ?

    /*** 商品服務(wù)接口定義** @author 恒宇少年*/@FeignClient(name = "good-service")@RequestMapping(value = "/good")public interface GoodClient {/*** 查詢商品基本信息** @param goodId {@link Good#getId()}* @return {@link Good}*/@GetMappingGood findById(@RequestParam("goodId") Integer goodId);/*** 減少商品的庫存** @param goodId {@link Good#getId()}* @param stock 減少庫存的數(shù)量*/@PostMappingvoid reduceStock(@RequestParam("goodId") Integer goodId, @RequestParam("stock") int stock);}

    2.2 公共模塊

    公共模塊common-service內(nèi)所提供的類是共用的,各個(gè)服務(wù)都可以調(diào)用,其中最為重要的是將Seata所提供的數(shù)據(jù)源代理(DataSourceProxy)實(shí)例化配置放到了這個(gè)模塊中,數(shù)據(jù)庫代理相關(guān)配置代碼如下所示:

    ?

    /*** Seata所需數(shù)據(jù)庫代理配置類** @author 恒宇少年*/ @Configuration public class DataSourceProxyAutoConfiguration {/*** 數(shù)據(jù)源屬性配置* {@link DataSourceProperties}*/private DataSourceProperties dataSourceProperties;public DataSourceProxyAutoConfiguration(DataSourceProperties dataSourceProperties) {this.dataSourceProperties = dataSourceProperties;}/*** 配置數(shù)據(jù)源代理,用于事務(wù)回滾** @return The default datasource* @see DataSourceProxy*/@Primary@Bean("dataSource")public DataSource dataSource() {HikariDataSource dataSource = new HikariDataSource();dataSource.setJdbcUrl(dataSourceProperties.getUrl());dataSource.setUsername(dataSourceProperties.getUsername());dataSource.setPassword(dataSourceProperties.getPassword());dataSource.setDriverClassName(dataSourceProperties.getDriverClassName());return new DataSourceProxy(dataSource);} }

    該配置類在所需要的服務(wù)中使用@Import注解進(jìn)行導(dǎo)入使用。

    2.3 賬戶服務(wù)

    • 服務(wù)接口實(shí)現(xiàn)

      賬戶服務(wù)用于提供接口的服務(wù)實(shí)現(xiàn),通過實(shí)現(xiàn)openfeign-service內(nèi)提供的AccountClient服務(wù)定義接口來對(duì)應(yīng)提供服務(wù)實(shí)現(xiàn),實(shí)現(xiàn)接口如下所示:

    ?

    /*** 賬戶接口實(shí)現(xiàn)** @author 恒宇少年*/@RestControllerpublic class AccountController implements AccountClient {/*** 賬戶業(yè)務(wù)邏輯*/@Autowiredprivate AccountService accountService;@Overridepublic void deduction(Integer accountId, Double money) {accountService.deduction(accountId, money);}}
    • 服務(wù)配置(application.yml)

    ?

    # 服務(wù)名spring:application:name: account-service# seata分組cloud:alibaba:seata:tx-service-group: minbox-seata# 數(shù)據(jù)源datasource:url: jdbc:mysql://localhost:3306/testusername: rootpassword: 123456type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.Driver# eurekaeureka:client:service-url:defaultZone: http://service:nodev2@10.180.98.83:10001/eureka/

    通過spring.cloud.alibaba.seata.tx-service-group我們可以指定服務(wù)所屬事務(wù)的分組,該配置非必填,默認(rèn)為spring.application.name配置的內(nèi)容加上字符串-fescar-service-group,如:account-service-fescar-service-group,詳見com.alibaba.cloud.seata.GlobalTransactionAutoConfiguration配置類源碼。

    在我本地測(cè)試環(huán)境的Eureka Server在10.180.98.83服務(wù)器上,這里需要修改成你們自己的地址,數(shù)據(jù)庫連接信息也需要修改成你們自己的配置。

    • 導(dǎo)入Seata數(shù)據(jù)源代理配置

    ?

    /*** @author 恒宇少年*/@SpringBootApplication@Import(DataSourceProxyAutoConfiguration.class)public class AccountServiceApplication {/*** logger instance*/static Logger logger = LoggerFactory.getLogger(AccountServiceApplication.class);public static void main(String[] args) {SpringApplication.run(AccountServiceApplication.class, args);logger.info("賬戶服務(wù)啟動(dòng)成功.");}}

    通過@Import導(dǎo)入我們common-service內(nèi)提供的Seata數(shù)據(jù)源代理配置類DataSourceProxyAutoConfiguration。

    2.4 商品服務(wù)

    • 服務(wù)接口實(shí)現(xiàn)

      商品服務(wù)提供商品的查詢以及庫存扣減接口服務(wù),實(shí)現(xiàn)openfeign-service提供的GoodClient服務(wù)接口定義如下所示:

    ?

    /*** 商品接口定義實(shí)現(xiàn)** @author 恒宇少年*/@RestControllerpublic class GoodController implements GoodClient {/*** 商品業(yè)務(wù)邏輯*/@Autowiredprivate GoodService goodService;/*** 查詢商品信息** @param goodId {@link Good#getId()}* @return*/@Overridepublic Good findById(Integer goodId) {return goodService.findById(goodId);}/*** 扣減商品庫存** @param goodId {@link Good#getId()}* @param stock 減少庫存的數(shù)量*/@Overridepublic void reduceStock(Integer goodId, int stock) {goodService.reduceStock(goodId, stock);}}
    • 服務(wù)配置(application.yml)

    ?

    spring:application:name: good-servicecloud:alibaba:seata:tx-service-group: minbox-seatadatasource:url: jdbc:mysql://localhost:3306/testusername: rootpassword: 123456type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.Drivereureka:client:service-url:defaultZone: http://service:nodev2@10.180.98.83:10001/eureka/server:port: 8081
    • 導(dǎo)入Seata數(shù)據(jù)源代理配置

    ?

    /*** @author 恒宇少年*/@SpringBootApplication@Import(DataSourceProxyAutoConfiguration.class)public class GoodServiceApplication {/*** logger instance*/static Logger logger = LoggerFactory.getLogger(GoodServiceApplication.class);public static void main(String[] args) {SpringApplication.run(GoodServiceApplication.class, args);logger.info("商品服務(wù)啟動(dòng)成功.");}}

    2.5 訂單服務(wù)

    • 服務(wù)接口

      訂單服務(wù)提供了下單的接口,通過調(diào)用該接口完成下單功能,下單接口會(huì)通過Openfeign調(diào)用account-service、good-service所提供的服務(wù)接口來完成數(shù)據(jù)驗(yàn)證,如下所示:

    ?

    /*** @author 恒宇少年*/@RestController@RequestMapping(value = "/order")public class OrderController {/*** 賬戶服務(wù)接口*/@Autowiredprivate AccountClient accountClient;/*** 商品服務(wù)接口*/@Autowiredprivate GoodClient goodClient;/*** 訂單業(yè)務(wù)邏輯*/@Autowiredprivate OrderService orderService;/*** 通過{@link GoodClient#reduceStock(Integer, int)}方法減少商品的庫存,判斷庫存剩余數(shù)量* 通過{@link AccountClient#deduction(Integer, Double)}方法扣除商品所需要的金額,金額不足由account-service拋出異常** @param goodId {@link Good#getId()}* @param accountId {@link Account#getId()}* @param buyCount 購(gòu)買數(shù)量* @return*/@PostMapping@GlobalTransactionalpublic String submitOrder(@RequestParam("goodId") Integer goodId,@RequestParam("accountId") Integer accountId,@RequestParam("buyCount") int buyCount) {Good good = goodClient.findById(goodId);Double orderPrice = buyCount * good.getPrice();goodClient.reduceStock(goodId, buyCount);accountClient.deduction(accountId, orderPrice);Order order = toOrder(goodId, accountId, orderPrice);orderService.addOrder(order);return "下單成功.";}private Order toOrder(Integer goodId, Integer accountId, Double orderPrice) {Order order = new Order();order.setGoodId(goodId);order.setAccountId(accountId);order.setPrice(orderPrice);return order;}}
    • 服務(wù)配置(application.yml)

    ?

    spring:application:name: order-servicecloud:alibaba:seata:tx-service-group: minbox-seatadatasource:url: jdbc:mysql://localhost:3306/testusername: rootpassword: 123456type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.Drivereureka:client:service-url:defaultZone: http://service:nodev2@10.180.98.83:10001/eureka/server:port: 8082
    • 啟用Openfeign & 導(dǎo)入Seata數(shù)據(jù)源代理配置

    ?

    /*** @author 恒宇少年*/@SpringBootApplication@EnableFeignClients(basePackages = "org.minbox.chapter.seata.openfeign")@Import(DataSourceProxyAutoConfiguration.class)public class OrderServiceApplication {/*** logger instance*/static Logger logger = LoggerFactory.getLogger(OrderServiceApplication.class);public static void main(String[] args) {SpringApplication.run(OrderServiceApplication.class, args);logger.info("訂單服務(wù)啟動(dòng)成功.");}}

    我們僅在order-service調(diào)用了其他服務(wù)的Openfeign接口,所以我們只需要在order-service內(nèi)通過@EnableFeignClients注解啟用Openfeign接口實(shí)現(xiàn)代理。

    3. 服務(wù)連接Seata Server

    服務(wù)想要連接到Seata Server需要添加兩個(gè)配置文件,分別是registry.conf、file.conf。

    • registry.conf

      注冊(cè)到Seata Server的配置文件,里面包含了注冊(cè)方式、配置文件讀取方式,內(nèi)容如下所示:

    ?

    registry {# file、nacos、eureka、redis、zk、consultype = "file"file {name = "file.conf"}}config {type = "file"file {name = "file.conf"}}
    • file.conf

      該配置文件內(nèi)包含了使用file方式連接到Eureka Server的配置信息以及存儲(chǔ)分布式事務(wù)信息的方式,如下所示:

    ?

    transport {# tcp udt unix-domain-sockettype = "TCP"#NIO NATIVEserver = "NIO"#enable heartbeatheartbeat = true#thread factory for nettythread-factory {boss-thread-prefix = "NettyBoss"worker-thread-prefix = "NettyServerNIOWorker"server-executor-thread-prefix = "NettyServerBizHandler"share-boss-worker = falseclient-selector-thread-prefix = "NettyClientSelector"client-selector-thread-size = 1client-worker-thread-prefix = "NettyClientWorkerThread"# netty boss thread size,will not be used for UDTboss-thread-size = 1#auto default pin or 8worker-thread-size = 8}}## transaction log storestore {## store mode: file、dbmode = "file"## file storefile {dir = "sessionStore"# branch session size , if exceeded first try compress lockkey, still exceeded throws exceptionsmax-branch-session-size = 16384# globe session size , if exceeded throws exceptionsmax-global-session-size = 512# file buffer size , if exceeded allocate new bufferfile-write-buffer-cache-size = 16384# when recover batch read sizesession.reload.read_size = 100# async, syncflush-disk-mode = async}## database storedb {datasource = "druid"db-type = "mysql"driver-class-name = "com.mysql.jdbc.Driver"url = "jdbc:mysql://10.180.98.83:3306/iot-transactional"user = "dev"password = "dev2019."}}service {vgroup_mapping.minbox-seata = "default"default.grouplist = "10.180.98.83:8091"enableDegrade = falsedisable = false}client {async.commit.buffer.limit = 10000lock {retry.internal = 10retry.times = 30}}

    配置文件內(nèi)service部分需要注意,我們?cè)赼pplication.yml配置文件內(nèi)配置了事務(wù)分組為minbox-seata,在這里需要進(jìn)行對(duì)應(yīng)配置vgroup_mapping.minbox-seata = "default",通過default.grouplist = "10.180.98.83:8091"配置Seata Server的服務(wù)列表。

    將上面兩個(gè)配置文件在各個(gè)服務(wù)resources目錄下創(chuàng)建。

    4. 編寫下單邏輯

    在前面說了那么多,只是做了準(zhǔn)備工作,我們要為每個(gè)參與下單的服務(wù)添加對(duì)應(yīng)的業(yè)務(wù)邏輯。

    • 賬戶服務(wù)

      在account-service內(nèi)添加賬戶余額扣除業(yè)務(wù)邏輯類,AccountService如下所示:

    ?

    /*** 賬戶業(yè)務(wù)邏輯處理** @author 恒宇少年*/@Service@Transactional(rollbackFor = Exception.class)public class AccountService {@Autowiredprivate EnhanceMapper<Account, Integer> mapper;/*** {@link EnhanceMapper} 具體使用查看ApiBoot官網(wǎng)文檔http://apiboot.minbox.io/zh-cn/docs/api-boot-mybatis-enhance.html** @param accountId {@link Account#getId()}* @param money 扣除的金額*/public void deduction(Integer accountId, Double money) {Account account = mapper.selectOne(accountId);if (ObjectUtils.isEmpty(account)) {throw new RuntimeException("賬戶:" + accountId + ",不存在.");}if (account.getMoney() - money < 0) {throw new RuntimeException("賬戶:" + accountId + ",余額不足.");}account.setMoney(account.getMoney().doubleValue() - money);mapper.update(account);}}
    • 商品服務(wù)

      在good-service內(nèi)添加查詢商品、扣減商品庫存的邏輯類,GoodService如下所示:

    ?

    /*** 商品業(yè)務(wù)邏輯實(shí)現(xiàn)** @author 恒宇少年*/@Service@Transactional(rollbackFor = Exception.class)public class GoodService {@Autowiredprivate EnhanceMapper<Good, Integer> mapper;/*** 查詢商品詳情** @param goodId {@link Good#getId()}* @return {@link Good}*/public Good findById(Integer goodId) {return mapper.selectOne(goodId);}/*** {@link EnhanceMapper} 具體使用查看ApiBoot官網(wǎng)文檔http://apiboot.minbox.io/zh-cn/docs/api-boot-mybatis-enhance.html* 扣除商品庫存** @param goodId {@link Good#getId()}* @param stock 扣除的庫存數(shù)量*/public void reduceStock(Integer goodId, int stock) {Good good = mapper.selectOne(goodId);if (ObjectUtils.isEmpty(good)) {throw new RuntimeException("商品:" + goodId + ",不存在.");}if (good.getStock() - stock < 0) {throw new RuntimeException("商品:" + goodId + "庫存不足.");}good.setStock(good.getStock() - stock);mapper.update(good);}}

    5. 提交訂單測(cè)試

    我們?cè)趫?zhí)行測(cè)試之前在數(shù)據(jù)庫內(nèi)的seata_account、seata_good表內(nèi)對(duì)應(yīng)添加兩條測(cè)試數(shù)據(jù),如下所示:

    ?

    -- seata_good INSERT INTO `seata_good` VALUES (1,'華為Meta 30',10,5000.00); -- seata_account INSERT INTO `seata_account` VALUES (1,10000.00,'2019-10-11 02:37:35',NULL);

    增加SEATA恢復(fù)表

    DROP SCHEMA IF EXISTS zeroa;CREATE SCHEMA zeroa;USE zeroa;CREATE TABLE `undo_log` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`branch_id` bigint(20) NOT NULL,`xid` varchar(100) NOT NULL,`context` varchar(128) NOT NULL,`rollback_info` longblob NOT NULL,`log_status` int(11) NOT NULL,`log_created` datetime NOT NULL,`log_modified` datetime NOT NULL,`ext` varchar(100) DEFAULT NULL,PRIMARY KEY (`id`),UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;DROP SCHEMA IF EXISTS zerob;CREATE SCHEMA zerob;USE zerob;CREATE TABLE `undo_log` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`branch_id` bigint(20) NOT NULL,`xid` varchar(100) NOT NULL,`context` varchar(128) NOT NULL,`rollback_info` longblob NOT NULL,`log_status` int(11) NOT NULL,`log_created` datetime NOT NULL,`log_modified` datetime NOT NULL,`ext` varchar(100) DEFAULT NULL,PRIMARY KEY (`id`),UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

    5.1 啟動(dòng)服務(wù)

    將我們本章所使用good-server、order-service、account-service三個(gè)服務(wù)啟動(dòng)。

    5.2 測(cè)試點(diǎn):正常購(gòu)買

    我們添加的賬戶余額測(cè)試數(shù)據(jù)夠我們購(gòu)買兩件商品,我們先來購(gòu)買一件商品驗(yàn)證下接口訪問是否成功,通過如下命令訪問下單接口:

    ?

    ~ curl -X POST http://localhost:8082/order\?goodId\=1\&accountId\=1\&buyCount\=1 下單成功.

    通過我們?cè)L問/order下單接口,根據(jù)響應(yīng)的內(nèi)容我們確定商品已經(jīng)購(gòu)買成功。

    通過查看order-service控制臺(tái)內(nèi)容:

    ?

    2019-10-11 16:52:15.477 INFO 13142 --- [nio-8082-exec-4] i.seata.tm.api.DefaultGlobalTransaction : [10.180.98.83:8091:2024417333] commit status:Committed 2019-10-11 16:52:16.412 INFO 13142 --- [atch_RMROLE_2_8] i.s.core.rpc.netty.RmMessageListener : onMessage:xid=10.180.98.83:8091:2024417333,branchId=2024417341,branchType=AT,resourceId=jdbc:mysql://localhost:3306/test,applicationData=null 2019-10-11 16:52:16.412 INFO 13142 --- [atch_RMROLE_2_8] io.seata.rm.AbstractRMHandler : Branch committing: 10.180.98.83:8091:2024417333 2024417341 jdbc:mysql://localhost:3306/test null 2019-10-11 16:52:16.412 INFO 13142 --- [atch_RMROLE_2_8] io.seata.rm.AbstractRMHandler : Branch commit result: PhaseTwo_Committed

    我們可以看到本次事務(wù)已經(jīng)成功Committed。

    再去驗(yàn)證下數(shù)據(jù)庫內(nèi)的賬戶余額、商品庫存是否有所扣減。

    5.3 測(cè)試點(diǎn):庫存不足

    測(cè)試商品添加了10個(gè)庫存,在之前測(cè)試已經(jīng)銷售掉了一件商品,我們測(cè)試購(gòu)買數(shù)量超過庫存數(shù)量時(shí),是否有回滾日志,執(zhí)行如下命令:

    ?

    ~ curl -X POST http://localhost:8082/order\?goodId\=1\&accountId\=1\&buyCount\=10 {"timestamp":"2019-10-11T08:57:13.775+0000","status":500,"error":"Internal Server Error","message":"status 500 reading GoodClient#reduceStock(Integer,int)","path":"/order"}

    在我們good-service服務(wù)控制臺(tái)已經(jīng)打印了商品庫存不足的異常信息:

    ?

    java.lang.RuntimeException: 商品:1庫存不足.at org.minbox.chapter.seata.service.GoodService.reduceStock(GoodService.java:42) ~[classes/:na]....

    我們?cè)倏磑rder-service的控制臺(tái)打印日志:

    ?

    Begin new global transaction [10.180.98.83:8091:2024417350] 2019-10-11 16:57:13.771 INFO 13142 --- [nio-8082-exec-5] i.seata.tm.api.DefaultGlobalTransaction : [10.180.98.83:8091:2024417350] rollback status:Rollbacked

    通過日志可以查看本次事務(wù)進(jìn)行了回滾。

    由于庫存的驗(yàn)證在賬戶余額扣減之前,所以我們本次并不能從數(shù)據(jù)庫的數(shù)據(jù)來判斷事務(wù)是真的回滾。

    5.4 測(cè)試點(diǎn):余額不足

    既然商品庫存不足我們不能直接驗(yàn)證數(shù)據(jù)庫事務(wù)回滾,我們從賬戶余額不足來下手,在之前成功購(gòu)買了一件商品,賬戶的余額還夠購(gòu)買一件商品,商品庫存目前是9件,我們本次測(cè)試購(gòu)買5件商品,這樣就會(huì)出現(xiàn)購(gòu)買商品庫存充足而余額不足的應(yīng)用場(chǎng)景,執(zhí)行如下命令發(fā)起請(qǐng)求:

    ?

    ~ curl -X POST http://localhost:8082/order\?goodId\=1\&accountId\=1\&buyCount\=5 {"timestamp":"2019-10-11T09:03:00.794+0000","status":500,"error":"Internal Server Error","message":"status 500 reading AccountClient#deduction(Integer,Double)","path":"/order"}

    我們通過查看account-service控制臺(tái)日志可以看到:

    ?

    java.lang.RuntimeException: 賬戶:1,余額不足.at org.minbox.chapter.seata.service.AccountService.deduction(AccountService.java:33) ~[classes/:na]

    已經(jīng)拋出了余額不足的異常。

    通過查看good-service、order-serivce控制臺(tái)日志,可以看到事務(wù)進(jìn)行了回滾操作。

    接下來查看seata_account表數(shù)據(jù),我們發(fā)現(xiàn)賬戶余額沒有改變,賬戶服務(wù)的事務(wù)回滾驗(yàn)證成功。

    查看seata_good表數(shù)據(jù),我們發(fā)現(xiàn)商品的庫存也沒有改變,商品服務(wù)的事務(wù)回滾驗(yàn)證成功

    6. 總結(jié)

    本章主要來驗(yàn)證分布式事務(wù)框架Seata在MySQL下提交與回滾有效性,是否能夠完成我們預(yù)期的效果,Seata作為SpringCloud Alibaba的核心框架,更新頻率比較高,快速的解決使用過程中遇到的問題,是一個(gè)潛力股,不錯(cuò)的選擇。

    由于本章設(shè)計(jì)的代碼比較多,請(qǐng)結(jié)合源碼進(jìn)行學(xué)習(xí)。

    7. 本章源碼

    請(qǐng)?jiān)L問<a href="https://gitee.com/hengboy/spring-cloud-chapter" target="_blank">https://gitee.com/hengboy/spring-cloud-chapter</a>查看本章源碼,建議使用git clone https://gitee.com/hengboy/spring-cloud-chapter.git將源碼下載到本地。

    • Gitee:https://gitee.com/hengboy/spring-boot-chapter



    ?

    坑點(diǎn)一
    如果你的項(xiàng)目采用是spring-cloud-alibaba-seata 0.9.0版本或以下的話,它集成了fescar-spring的0.4.2版本,如果你的seata-server服務(wù)端是采用0.5.0以上建議還是降低版本,采用0.4.2版本。因?yàn)?.4.2版本解壓是fescar-server名字,意不意外。這就是坑。而且項(xiàng)目引入seata依賴會(huì)與舊版本的fescar依賴沖突。

    ? ? ? ? ? ? <dependency>
    ? ? ? ? ? ? ? ? <groupId>org.springframework.cloud</groupId>
    ? ? ? ? ? ? ? ? <artifactId>spring-cloud-alibaba-seata</artifactId>
    ? ? ? ? ? ? ? ? <version>0.9.0.RELEASE</version>
    ? ? ? ? ? ? </dependency>
    ?


    如果你的項(xiàng)目采用是spring-cloud-alibaba-seata 0.9.1(這個(gè)的seata為0.5.2)版本以上的話,那恭喜你。你可以使用seata-server的0.5.2以上的版本了。只需要在依賴這樣引入

    ? ? ? ?<dependency>
    ? ? ? ? ? ? <groupId>org.springframework.cloud</groupId>
    ? ? ? ? ? ? <artifactId>spring-cloud-alibaba-seata</artifactId>
    ? ? ? ? ? ? <version>0.9.1.BUILD-SNAPSHOT</version>
    ? ? ? ? ? ? <exclusions>
    ? ? ? ? ? ? ? ? <exclusion>
    ? ? ? ? ? ? ? ? ? ? <groupId>io.seata</groupId>
    ? ? ? ? ? ? ? ? ? ? <artifactId>seata-spring</artifactId>
    ? ? ? ? ? ? ? ? </exclusion>
    ? ? ? ? ? ? </exclusions>
    ? ? ? ? </dependency>
    ? ? ? ? <dependency>
    ? ? ? ? ? ? <groupId>io.seata</groupId>
    ? ? ? ? ? ? <artifactId>seata-all</artifactId>
    ? ? ? ? ? ? <version>seata-server對(duì)應(yīng)的版本</version>
    ? ? ? ? </dependency>
    ?


    坑點(diǎn)二
    需要在每個(gè)服務(wù)中的resources文件中添加兩個(gè)文件file.conf和registry.conf


    具體以seata-server中的file.conf和registry.conf為準(zhǔn)。

    坑點(diǎn)三
    如果你的項(xiàng)目采用是spring-cloud-alibaba-seata 0.9.0版本或以下的話,因?yàn)樗闪薴escar-spring的0.4.2版本,如果你是使用nacos來配置參數(shù)的話,建議使用seata-server 0.4.2,不然引入seata0.5.0以上的版本依賴會(huì)混淆,容易報(bào)以下錯(cuò)誤

    org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'globalTransactionScanner' defined in class path resource [org/springframework/cloud/alibaba/seata/GlobalTransactionAutoConfiguration.class]: Invocation of init method failed; nested exception is java.lang.ExceptionInInitializerError
    Caused by: java.lang.ExceptionInInitializerError: null
    Caused by: java.lang.NullPointerException: Name is null
    ?? ?at java.lang.Enum.valueOf(Enum.java:236) ~[na:1.8.0_201]
    ?? ?at com.alibaba.fescar.core.rpc.netty.TransportProtocolType.valueOf(TransportProtocolType.java:25) ~[fescar-core-0.4.2.jar:na]
    ?? ?at com.alibaba.fescar.core.rpc.netty.NettyBaseConfig.<clinit>(NettyBaseConfig.java:114) ~[fescar-core-0.4.2.jar:na]
    ?? ?... 23 common frames omitted
    ?


    坑點(diǎn)四
    數(shù)據(jù)源需要引入druid依賴

    /**
    ?* @author lgt
    ?*/
    @Configuration
    public class DatabaseConfiguration {

    ?? ?@Bean
    ?? ?@ConfigurationProperties(prefix = "spring.datasource")
    ?? ?public DruidDataSource druidDataSource() {
    ?? ??? ?return new DruidDataSource();
    ?? ?}

    ?? ?/**
    ?? ? * 需要將 DataSourceProxy 設(shè)置為主數(shù)據(jù)源,否則事務(wù)無法回滾
    ?? ? *
    ?? ? * @param druidDataSource The DruidDataSource
    ?? ? * @return The default datasource
    ?? ? */
    ?? ?@Primary
    ?? ?@Bean("dataSource")
    ?? ?public DataSource dataSource(DruidDataSource druidDataSource) {
    ?? ??? ?return new DataSourceProxy(druidDataSource);
    ?? ?}

    }

    ps:上面是 seata 數(shù)據(jù)源的配置,數(shù)據(jù)源采用 druid 的DruidDataSource,但實(shí)際 jdbcTemplate 執(zhí)行時(shí)并不是用該數(shù)據(jù)源,而用的是 seata 對(duì)DruidDataSource的代理DataSourceProxy,所以,與 RM 相關(guān)的代碼邏輯基本上都是從DataSourceProxy這個(gè)代理數(shù)據(jù)源開始的。

    坑點(diǎn)五
    0.6.1及之前版本的啟動(dòng)命令是:sh seata-server.sh 8091 file 127.0.0.1
    0.7.0 及之后版本的啟動(dòng)命令是:sh seata-server.sh -p 8091 -h 127.0.0.1 -m file
    0.5.2及之前的版本的數(shù)據(jù)庫和之后版本數(shù)據(jù)庫是不一樣,詳細(xì)以github的文件一致
    ————————————————
    版權(quán)聲明:本文為CSDN博主「sbit_」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接及本聲明。
    原文鏈接:https://blog.csdn.net/sbit_/article/details/96112393

    作者:恒宇少年
    鏈接:https://www.jianshu.com/p/0a92b7c97c65
    來源:簡(jiǎn)書
    著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。

    ?

    總結(jié)

    以上是生活随笔為你收集整理的SpringCloud与Seata分布式事务初体验的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。