irpas技术客

Spring Cloud Eureka整合 Seata 实现分布式事务_前尘忆梦Memory_eureka集成seata

网络 7052

一、Seata 介绍

????????Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

????????Seata相关名词

????????TC (Transaction Coordinator) - 事务协调者

????????维护全局和分支事务的状态,驱动全局事务提交或回滚。

????????TM (Transaction Manager) - 事务管理器

????????定义全局事务的范围:开始全局事务、提交或回滚全局事务。

????????RM (Resource Manager) - 资源管理器

????????管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

二、软件版本

软件

版本

地址

JDK

1.8.0_271

Java Downloads | Oracle

Spring Boot

2.5.6

Spring Boot

Spring Cloud

2020.0.4

Spring Cloud

Seata

1.4.2

https://github.com/seata/seata/releases

????????代码地址(Seata 和 SQL?脚本在resource目录下):? ? ? ? spring-cloud-seata

三、环境搭建 3.1、安装Seata 3.1.1、下载seata-server

????????下载seata-server-1.4.2.zip,地址:https://github.com/seata/seata/releases

3.1.2、安装seata-server

????????解压seata-server-1.4.2.zip

?3.1.3、 seata数据库配置

????????创建数据库spring-cloud-seata-eureka ,并创建seata服务需要的表:

????????global_table、branch_table、lock_table

????????seata-server 需要的数据库脚本:

? ? ? ??https://github.com/seata/seata/tree/v1.4.2/script/server/db

????????seata-client 需要的数据库脚本:

????????https://github.com/seata/seata/tree/v1.4.2/script/client/at/db

? ? ? ? Seata AT 模式,客户端只需要 undo_log表,下面要新建业务表t_account、t_order、t_storage 三张业务表?

????????如果三张表放到一个数据库里面,只需要新建一个 undo_log 表

????????如果将三张表拆分到三个数据库里面,则每个数据库都需要创建 undo_log 表

3.1.4、配置seata-server

? ? ? ? 我们需要修改 seata-server-1.4.2/conf 目录里面file.conf和registry.conf,这里我们使用的是eureka注册中心,存储使用的MySQL数据库,配置如下

????????file.conf 配置如下:

## transaction log store, only used in seata-server store { ? ## store mode: file、db、redis ? mode = "db" ? ## rsa decryption public key ? publicKey = "" ? ## database store property ? db { ? ? ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc. ? ? datasource = "druid" ? ? ## mysql/oracle/postgresql/h2/oceanbase etc. ? ? dbType = "mysql" ? ? driverClassName = "com.mysql.cj.jdbc.Driver" ? ? ## if using mysql to store the data, recommend add rewriteBatchedStatements=true in jdbc connection param ? ? url = "jdbc:mysql://127.0.0.1:3306/spring-cloud-seata-eureka?rewriteBatchedStatements=true&characterEncoding=utf8&useSSL=false" ? ? user = "root" ? ? password = "root" ? ? minConn = 5 ? ? maxConn = 30 ? ? globalTable = "global_table" ? ? branchTable = "branch_table" ? ? lockTable = "lock_table" ? ? queryLimit = 100 ? } }

????????registry.conf 配置如下

registry { ? # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa ? type = "eureka" ? eureka { ? ? serviceUrl = "http://127.0.0.1:8761/eureka" ? ? application = "default" ? ? weight = "1" ? } } config { ? # file、nacos 、apollo、zk、consul、etcd3 ? type = "file" ?? ? file { ? ? name = "file.conf" ? } } 3.2、启动服务 3.2.1、启动Eureka注册中心

? ? ? ? eureka配置如下:

server: port: 8761 eureka: instance: hostname: localhost client: registerWithEureka: false fetchRegistry: false serviceUrl: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

????????启动eureka

3.2.2、启动Seata服务

????????执行 seata-server-1.4.2\bin 目录下的 seata-server.bat (windows)/ seata-server.sh (linux或mac)启动seata服务 。

????????打开?http://localhost:8761/,我们可以看出,seata 服务已经成功注册到eureka注册中心。

四、项目架构 4.1、项目说明

????????用户购买商品的业务逻辑。整个业务逻辑由3个微服务提供支持:

采购服务(business-service):购买商品的业务逻辑,也是事务的发起者。仓储服务(storage-service):对给定的商品扣除仓储数量。订单服务(order-service):根据采购需求创建订单。帐户服务(account-service):从用户帐户中扣除余额。 4.2、项目架构

五、项目搭建 5.1、项目结构

5.2、表结构初始化

????????初始化订单、库存、账户三张表

-- ---------------------------- -- Table structure for t_account -- ---------------------------- DROP TABLE IF EXISTS `t_account`; CREATE TABLE `t_account` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL DEFAULT 0, `money` bigint(20) NOT NULL DEFAULT 0, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4; -- ---------------------------- -- Records of t_account -- ---------------------------- INSERT INTO `t_account` VALUES (1, 10001, 10000); -- ---------------------------- -- Table structure for t_order -- ---------------------------- DROP TABLE IF EXISTS `t_order`; CREATE TABLE `t_order` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL DEFAULT 0, `commodity_code` varchar(20) CHARACTER SET utf8mb4 NOT NULL DEFAULT '', `count` int(10) NOT NULL DEFAULT 0, `money` bigint(20) NOT NULL DEFAULT 0, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4; -- ---------------------------- -- Records of t_order -- ---------------------------- -- ---------------------------- -- Table structure for t_storage -- ---------------------------- DROP TABLE IF EXISTS `t_storage`; CREATE TABLE `t_storage` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `commodity_code` varchar(50) CHARACTER SET utf8mb4 NOT NULL DEFAULT '', `commodity_name` varchar(255) CHARACTER SET utf8mb4 NOT NULL DEFAULT '', `count` int(11) NOT NULL DEFAULT 0, `price` bigint(20) NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `commodity_code`(`commodity_code`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4; -- ---------------------------- -- Records of t_storage -- ---------------------------- INSERT INTO `t_storage` VALUES (1, '10001', '苹果手机', 100, 1000); 5.3、服务搭建 5.3.1、pom依赖

????????order-service、storage-service、account-service三个服务的pom.xml的依赖一样,business-service服务不需要连接数据库,所以不需要引用mybatis-plus-boot-starter和mysql-connector-java

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>

5.3.2、application.yml

????????order-service、storage-service、account-service三个服务的application.yml基本一致,其中需要修改的有以下几个地方:

server.port spring.application.name mybatis-plus.type-aliases-package seata.application-id

????????order-service 服务yml配置

server: port: 9001 spring: application: name: order-service datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/spring-cloud-seata-eureka?characterEncoding=utf-8 username: root password: root eureka: instance: instance-id: order-service prefer-ip-address: true client: fetch-registry: true register-with-eureka: true service-url: defaultZone: http://127.0.0.1:8761/eureka mybatis-plus: global-config: banner: false configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启sql日志 map-underscore-to-camel-case: true # 开启驼峰 type-aliases-package: com.seata.order.entity #定义所有操作类的别名所在包 mapper-locations: classpath:mapper/*Mapper.xml # Seata Config seata: application-id: order-service tx-service-group: my_test_tx_group service: vgroup-mapping: # 此处配置对应Server端配置registry.eureka.application的值 my_test_tx_group: default registry: type: eureka eureka: service-url: http://localhost:8761/eureka weight: 1

????????business-service服务不需要连接数据库,所以不用配置datasource和mybatis-plus节点

server: port: 9000 spring: application: name: business-service eureka: instance: instance-id: business-service prefer-ip-address: true client: fetch-registry: true register-with-eureka: true service-url: defaultZone: http://127.0.0.1:8761/eureka # Seata Config seata: application-id: business-service tx-service-group: my_test_tx_group service: vgroup-mapping: # 此处配置对应Server端配置registry.eureka.application的值 my_test_tx_group: default registry: type: eureka eureka: service-url: http://localhost:8761/eureka weight: 1 5.3.3、seata配置说明(这一步不用配置,只是说明一下上述配置)

????????registry.conf中内容已经配置到 application.yml 中,因此不需要引用registry.conf文件

????????如果不想正application.yml中配置seata相关内容,只需要将seata相关的脚本file.conf、registry.conf拷贝到项目resource目录下

????????文件地址:https://github.com/seata/seata/tree/v1.4.2/script/client/conf

????????其中?registry.conf 修改后如下:

registry { # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa、custom type = "eureka" eureka { serviceUrl = "http://localhost:8761/eureka" weight = "1" } } config { # file、nacos 、apollo、zk、consul、etcd3、springCloudConfig、custom type = "file" file { name = "file.conf" } } 5.3.4、仓储服务 /** * 扣减库存 */ int deductStorage(String commodityCode, int count); /** * 扣减库存 * * @param commodityCode 商品编码 * @param count 数量 * @return */ @Override @Transactional(rollbackFor = Exception.class) public int deductStorage(String commodityCode, int count) { log.info("[库存服务]>------>扣减库存开始"); storageMapper.deductStorage(commodityCode, count); log.info("[库存服务]>------>扣减库存结束"); return count; } 5.3.5、订单服务 /** * 创建订单 */ Long createOrder(Long userId, String commodityCode, int count); /** * 创建订单 * * @param userId * @param commodityCode * @param count * @return */ @Override public Long createOrder(Long userId, String commodityCode, int count) { log.info("[订单服务]>------>创建订单开始"); //扣减账户余额 Long price = storageService.selectPrice(commodityCode); Long money = price * count; // 创建订单 Order order = new Order(); order.setUserId(userId); order.setCommodityCode(commodityCode); order.setMoney(money); order.setCount(count); orderMapper.insert(order); // 扣减账户 log.info("[订单服务]>------>扣减账户开始"); accountService.deductAccount(order.getUserId(), money); log.info("[订单服务]>------>扣减账户结束"); log.info("[订单服务]>------>创建订单结束"); return order.getId(); } 5.3.6、帐户服务 /** * 扣减账户 */ Long deductAccount(Long userId, Long money); /** * 扣减账户 * @param userId * @param money * @return */ @Override public Long deductAccount(Long userId, Long money) { log.info("[账户服务]>------>扣减账户开始"); if (10000 == userId) { throw new RuntimeException("[库存服务]>------>扣减库存异常"); } accountMapper.deductAccount(userId, money); log.info("[账户服务]>------>扣减账户结束"); return money; } 5.3.7、采购业务逻辑 /** * 扣减库存-》创建订单 * * @param userId 用户Id * @param commodityCode 商品编码 * @param count 数量 */ @Override @GlobalTransactional(timeoutMills = 10000, name = "spring-cloud-seata", rollbackFor = Exception.class) public Long purchase(Long userId, String commodityCode, int count) { log.info("开始全局事务,XID = " + RootContext.getXID()); log.info("[采购服务]>------>扣减库存开始"); storageService.deductStorage(commodityCode, count); log.info("[采购服务]>------>扣减库存结束"); log.info("[采购服务]>------>创建订单开始"); Long orderId = orderService.createOrder(userId, commodityCode, count); log.info("[采购服务]>------>创建订单结束"); return orderId; } 5.3.8、查看服务启动情况

????????启动business服务,“register RM success.?”,表示RM已经注册到TC。

????????查看order服务的启动情况:

????????打开eureka注册中心,我们会清楚的看到各个服务的健康状态:

? ? ? ? 我们也可以从Seata服务端查看各个RM是否注册成功,如下图所示:?

5.3 分布式事务测试 5.3.1、正常业务逻辑测试

????????postman测试:

????????请求地址:? ? ? ? http://localhost:9000/business/purchase?userId=10001&commodityCode=10001&count=1

????????数据库中:订单表已经有一条数据,库存表商品库存数量减1

????????发起创建订单的请求后,日志里面可以清楚的看到本次全局事务的情况,

????????xid=172.20.97.98:8091:5395550782355771430,branchId=5395550782355771432,branchType=AT

????????business 服务的事务执行情况?

????????order 服务执行情况?

????????在Seata服务端,我们也能清楚的看到xid=172.20.97.98:8091:5395550782355771430这个全局事务的提交情况

5.3.1、异常业务逻辑测试

????????账户服务中,如果userId = 1000,就抛出异常,进行异常测试

@Override public Long deductAccount(Long userId, Long money) { log.info("[账户服务]>------>扣减账户开始"); if (10000L == userId) { throw new RuntimeException("[账户服务]>------>扣减账户异常"); } accountMapper.deductAccount(userId, money); log.info("[账户服务]>------>扣减账户结束"); return money; }

? ? ? ? http://localhost:9000/business/purchase?userId=10000&commodityCode=10001&count=1

????????这次全局事务ID 172.20.97.98:8091:5395550782355771442,开启全局事务

????????创建订单的时候,在order服务调用account服务的时候发生了异常?,下面日志可以看出,数据已经开始回滚。

????????Branch Rollbacking: 172.20.97.98:8091:5395550782355771442 5395550782355771447 jdbc:mysql://127.0.0.1:3306/spring-cloud-seata-eureka

????????xid 172.20.97.98:8091:5395550782355771442 branch 5395550782355771447, undo_log deleted with GlobalFinished

????????Branch Rollbacked result: PhaseTwo_Rollbacked

????????回滚的时候,是将之前保存的undo_log 取出来,进行数据恢复,在发生异常的地方,我们打上断点,再次请求,会发现undo_log表会将前面执行的日志保存下来

????????undo_log保存之前的日志

源码地址:

GitHub - jeespring/spring-cloud-seata


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #eureka集成seata #一Seata #介绍Seata #Seata #将为用户提供了 #ATTCCSAGA #