赞
踩
目录
Seate是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seate将为用户提供了AT、TCC、SAGA和XA事务模式,为用户打造一站式的分布式解决方案。AT模式是阿里首推的模式。
TC(Transaction Coordinator)-事务协调者:维护全局和分支事务的状态,驱动全局事务提交或回滚
TM(Transaction Manager)-事务管理者:定义全局事务的范围,开始全局事务,提交或回滚全局事务。
RM(Resource Manager)-资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
其中TC是单独部署的Service服务端,TM和RM是嵌入到应用中的Client客户端。
解决分布式事务,也有相应的规范和协议。分布式事务相关的协议有2PC、3PC。目前绝大多数分布式事务解决方案都是以两阶段提交协议2PC为基础的。
2PC两阶段提交协议:分为两个阶段,Prepare和Commit
Prepare:提交事务请求
基本流程如下:
(1)询问:协调者向所有参与者发送事务请求,询问是否可执行事务操作,然后等待各个参与者响应。
(2)执行:各个参与者接收到协调者事务请求后,执行事务操作(例如更新一个关系型数据库表的记录),并将Undo和Redo信息记录事务日志中。
(3)响应 :如果参与者成功执行了事务并写入Undo和Redo信息,则向协调者返回YES响应,否则返回NO响应。当然,参与者也可能宕机,从而不会返回响应
Commit:执行事务提交
基本流程:
(1)commit请求:协调者向所有参与者发送Commit请求。
(2)事务提交:参与者收到Commit请求后,执行事务提交,提交完成后释放事务执行期占用的所有资源。
(3)返回结果:参与者执行事务提交后向协调者发送Ack响应。
(4)完成事务:接收到所有参与者的Ack响应后,完成事务提交。
中断事务:
在执行Prepare步骤过程中,如果某些参与者执行事务失败、宕机或与协调者之间的网络中断,那么协调者就无法接收到所有参与者的YES响应,或者某个参与者返回了NO响应,此时,协调者就会进入回退流程,对事务进行回退。
AT模式是一种无侵入的分布式事务解决方案。阿里Seata框架,实现了该模式。在AT模式下,用户只需关注自己的“业务SQL”,用户的“业务SQL”作为一阶段,Seata框架会自动生成事务的二阶段提交和回滚操作。
AT模式如何做到对业务的无侵入:
一阶段:
Seata会拦截“业务SQL”,首先解析SQL语义,找到“业务SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,然后执行“业务SQL”更新业务数据,在业务数据更新之后,再将其保存成“after image”,最后生成行锁,防止出现脏读数据。以上操作全部在一个数据库事务内完成,这样保证了一阶段的原子性。
二阶段提交:
二阶段如果是提交的话,因为“业务SQL”在一阶段已经提交至数据库,所以Seata框架只需要将一阶段的快照数据和行锁删除,完成数据清除即可。
二阶段回滚:
二阶段是回滚的话,Seata就需要回滚一阶段已经执行的“业务SQL”,还原业务数据。回滚方式便是用“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和“after image”,如果两份数据完全一致则说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写需要转人工处理。
(1)侵入性比较强,并且得自己实现相关事务逻辑控制。
(2)在整个过程基本没有锁
TCC模式需要用户根据自己的业务场景实现Try、Confirm和Cancel三个操作;事务发起方在一阶段执行Try方法,在二阶段提交执行Confirm方法,二阶段回滚执行Cancel方法。
第一阶段:
业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。核心在于对业务sql进行解析,转换成undolog,并同时入库。
第二阶段:
分布式事务操作成功,则TC通知RM异步删除undolog。
分布式事务操作失败,TM向TC发送回滚请求,RM收到协调器TC发来的回滚请求,通过XID和Branch ID找到对应的回滚日志记录,通过回滚日志记录生成反向的更新Sql并执行,以完成分支的回滚。
使用文档参考:Seata部署指南
Server端存储模式(store.mode)支持三种:
.file:单机模式,全局事务回话信息内存中读写并持久化本地文件root.data,性能较高(默认)
.db:(mysql5.7+)高可用模式,全局事务回话信息通过db共享,相应性能差些
.redis:seata-server1.3及以上版本支持,性能较高,存在事务信息丢失风险,请提前配置适合当前场景的redis持久化配置
资源目录:seata/script at 1.3.0 · seata/seata · GitHub
client:存放client端sql脚本,参数配置
config-center:各个配置中心参数导入脚本,config.txt(包含server和client,原名nacos-config。txt)
server:server端数据库脚本及各个容器配置
(1)下载安装包
地址:Releases · seata/seata · GitHub
(2)解析下载的zip文件
打开conf/file.conf文件,修改mode="db",并且修改mysql的连接信息(数据库类型、地址、端口、用户名、密码、数据库)
store { ## store mode: file、db、redis mode = "db" db { ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc. datasource = "druid" ## mysql/oracle/postgresql/h2/oceanbase etc. dbType = "mysql" driverClassName = "com.mysql.jdbc.Driver" url = "jdbc:mysql://127.0.0.1:3306/seata" user = "root" password = "root" minConn = 5 maxConn = 30 globalTable = "global_table" branchTable = "branch_table" lockTable = "lock_table" queryLimit = 100 maxWait = 5000 } }
(3)在第二步配置的数据库下创建数据库
(4) 向创建的护具库中添加表,创建哪些表,需要通过地址:seata/script at 1.3.0 · seata/seata · GitHub获取
直接返回1.3.0的上一级,下载这个目录
解压zip文件
把script文件整个都拷贝到seate目录下
(5)根据我们的数据库类型,选择对应的sql建表文件,在数据库中执行,我们使用的是mysql,所以使用mysql.sql执行,在目录script/server/db中选择mysql.sql执行
创建出需要的表,branck_table:分支系统表,global_table:全局事务化信息表(xid),lock_table:锁住的表(哪张表,主键信息)
(6)配置nacos注册中心
打开conf/registry.conf配置文件,修改registry的type="nacos",配置nacos的连接信息(用户名,密码、地址、端口)
- registry {
- # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
- type = "nacos"
-
- nacos {
- application = "seata-server"
- serverAddr = "127.0.0.1:8848"
- group = "SEATA_GROUP"
- namespace = ""
- cluster = "default"
- username = "nacos"
- password = "nacos"
- }
-
- }
(7)配置配置中心
打开conf/registry.conf配置文件,修改config的type="nacos",配置nacos的连接信息(用户名,密码、地址、端口)
- config {
- # file、nacos 、apollo、zk、consul、etcd3
- type = "nacos"
-
- nacos {
- serverAddr = "127.0.0.1:8848"
- namespace = ""
- group = "SEATA_GROUP"
- username = "nacos"
- password = "nacos"
- }
- }
(8)修改配置中心注册信息
在目录script/config-center下的config.txt文件,修改store.mode="db",数据库的类型store.db.dbType="mysql",数据库的连接信息(用户名、密码、数据库)
service.vgroupMapping.my_test_tx_group=default配置针对事务分组,需要与客户端配置的事务分组一致。防止异地机房停电机制,my_test_tx_group可以自定义(Guangzhou、shanghai),对应的client也需要配置
seata.service.vgroup-mapping.projectA=Guangzhou
service.vgroupMapping.my_test_tx_group=default配置的default必须要等于conf/registry.conf中配置的cluster="default"。
(9)启动nacos服务器
(10)执行注册操作
在目录script/config-center/nacos下有两个执行文件nacos-config.py(python执行)、nacos-config.sh(linux执行,装了git客户端可以直接执行)
双击nacos-config.sh执行
若是nacos的连接信息不是本地,可以编辑nacos-config.sh文件,配置nacos连接信息
执行完成后,这些配置信息已经注册到我们的nacos服务列表
注册的内容一条记录对应我们config.txt的一行记录
(11)启动seate服务
在bin/seata.bat双击启动服务,为了后续方便,我们还是把数据库由seata改为seata_server
修改数据库名为seate_server
在conf/file.conf中修改数据库为seate_server
在已经注册的nacos服务中,对store.db.url修改为seate_server
双击启动文件,启动成功,默认监听8091端口
声明式事务实现(@GlobalTransactional)
使用mybati操作数据库,向数据库中添加订单,同时调用库存服务更新库存数据库。
(1)创建数据库seata_order,创建一张订单表
- create database seata_order;
- use seata_order;
-
- CREATE TABLE `order_tbl` (
- `id` int(11) NOT NULL AUTO_INCREMENT,
- `product_id` varchar(200) DEFAULT NULL,
- `total_amount` decimal(10,3) DEFAULT NULL,
- `statu` int(11) DEFAULT NULL,
- PRIMARY KEY (`id`)
- ) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8
-
(2)创建maven工程,使用mybatis连接数据库
(3)pom.xml依赖jar包添加
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <parent>
- <artifactId>springcloudalibaba</artifactId>
- <groupId>com.qingyun</groupId>
- <version>0.0.1-SNAPSHOT</version>
- </parent>
- <modelVersion>4.0.0</modelVersion>
-
- <artifactId>OrderSeata</artifactId>
-
-
- <dependencies>
-
- <!-- Nacos服务注册发现-->
- <dependency>
- <groupId>com.alibaba.cloud</groupId>
- <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
- </dependency>
-
- <!-- 添加springcloud 的openfeign-->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-openfeign</artifactId>
- <!-- 排除冲突的jar包文件-->
- <exclusions>
- <exclusion>
- <groupId>org.springframework</groupId>
- <artifactId>spring-web</artifactId>
- </exclusion>
- <exclusion>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-commons</artifactId>
- </exclusion>
- </exclusions>
-
- </dependency>
-
- <!--继承了父项目,不需要添加版本号-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
-
- <!-- jdbc -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-jdbc</artifactId>
- <version>2.3.5.RELEASE</version>
- </dependency>
-
- <!-- mybatis -->
- <dependency>
- <groupId>org.mybatis.spring.boot</groupId>
- <artifactId>mybatis-spring-boot-starter</artifactId>
- <version>1.3.2</version>
- <!-- 排除冲突的jar包文件-->
- <exclusions>
- <exclusion>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-jdbc</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
-
- <!--Mysql驱动器-->
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- <version>8.0.18</version>
- <scope>runtime</scope>
- </dependency>
-
- <!-- druid-->
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>druid-spring-boot-starter</artifactId>
- <version>1.1.9</version>
- </dependency>
-
- </dependencies>
-
- </project>

(4)application.properties配置信息
server.port=8084 #应用名称,nacos会将该名称当做服务名称 spring.application.name=order-seata #nacos服务连接地址 spring.cloud.nacos.server-addr=127.0.0.1:8848 #nacos discovery连接用户名 spring.cloud.nacos.discovery.username=nacos #nacos discovery连接密码 spring.cloud.nacos.discovery.password=nacos #nacos discovery工作空间 spring.cloud.nacos.discovery.workspace=public #开始配置mysql连接驱动以及数据库连接池参数 spring.datasource.name=mysql_test spring.datasource.type=com.alibaba.druid.pool.DruidDataSource spring.datasource.druid.filters=stat spring.datasource.druid.driver-class-name=com.mysql.jdbc.Driver spring.datasource.druid.url=jdbc:mysql://172.16.210.29:3307/seata_order?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=GMT spring.datasource.druid.username=root spring.datasource.druid.password=zggk-mysql-3306 #这里可以不用配置,有默认参数,根据自己需求 spring.datasource.druid.initial-size=1 spring.datasource.druid.min-idle=1 spring.datasource.druid.max-active=20 spring.datasource.druid.max-wait=6000 spring.datasource.druid.time-between-eviction-runs-millis=60000 spring.datasource.druid.min-evictable-idle-time-millis=300000 spring.datasource.druid.validation-query=SELECT 'x' spring.datasource.druid.test-while-idle=true spring.datasource.druid.test-on-borrow=false spring.datasource.druid.test-on-return=false spring.datasource.druid.pool-prepared-statements=false spring.datasource.druid.max-pool-prepared-statement-per-connection-size=20 #开始配置mybatis mybatis.mapper-locations=classpath:mapper/*.xml mybatis.type-aliases-package=com.qingyun.entity
(5)程序启动类,开启openfeign远程调用,配置mybatis的扫描路径
- @SpringBootApplication
- @MapperScan("com.qingyun.dao")
- @EnableFeignClients
- public class OrderApplication {
- public static void main(String[] args) {
- SpringApplication.run(OrderApplication.class,args);
- }
- }
(6)controller层,调用本服务添加订单信息,调用远程服务更新库存
-
- @RestController
- @RequestMapping("/order")
- public class OrderController {
-
- @Autowired
- OrderService orderService;
-
- @Autowired
- StockOpenFeign stockOpenFeign;
-
- @RequestMapping("/add")
- public String add(){
-
- OrderTbl orderTbl = new OrderTbl();
- orderTbl.setProduct_id("10");
- orderTbl.setTotal_amount(new BigDecimal(3000));
- orderTbl.setStatu(0);
- orderService.insert(orderTbl);
-
- String reduct = stockOpenFeign.reduct(orderTbl.getProduct_id());
- return "add order "+reduct;
- }
- }

(7)远程更新库存的接口
- @FeignClient(value = "stock-seata",path ="/stock" )
- public interface StockOpenFeign {
-
- @RequestMapping("/reduct")
- String reduct(@RequestParam("product_id")String product_id);
-
-
- }
(8)更新本系统订单的service层
- @Service
- public class OrderService {
-
- @Autowired
- OrderDao orderDao;
-
- public void insert(OrderTbl orderTbl) {
- orderDao.insert(orderTbl);
- }
- }
(9)更新本系统订单的dao接口
- @Repository
- public interface OrderDao {
-
- void insert(OrderTbl orderTbl);
- }
(10)订单表实体OrderTbl
- public class OrderTbl {
-
- private Integer id;
- private String product_id;
- private BigDecimal total_amount;
- private Integer statu;
-
- public OrderTbl() {
- }
-
- public Integer getId() {
- return id;
- }
-
- public void setId(Integer id) {
- this.id = id;
- }
-
- public String getProduct_id() {
- return product_id;
- }
-
- public void setProduct_id(String product_id) {
- this.product_id = product_id;
- }
-
- public BigDecimal getTotal_amount() {
- return total_amount;
- }
-
- public void setTotal_amount(BigDecimal total_amount) {
- this.total_amount = total_amount;
- }
-
- public Integer getStatu() {
- return statu;
- }
-
- public void setStatu(Integer statu) {
- this.statu = statu;
- }
-
- @Override
- public String toString() {
- return "OrderTbl{" +
- "id=" + id +
- ", product_id='" + product_id + '\'' +
- ", total_amount=" + total_amount +
- ", statu=" + statu +
- '}';
- }
- }

(11)添加记录到订单的映射XML文件
- <?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.qingyun.dao.OrderDao">
-
- <!-- 主键自增长的插入 -->
- <insert id="insert" parameterType="com.qingyun.entity.OrderTbl" useGeneratedKeys="true" keyProperty="id">
- insert into seata_order.order_tbl(product_id,total_amount,statu) values(
- #{product_id},
- #{total_amount},
- #{statu}
- );
- </insert>
- </mapper>
使用mybatis操作数据库,等待订单系统调用,根据商品id更新库存。
(1)创建数据库seata_stock,创建一张库存表
- create database seata_stock;
- use seata_stock;
-
- CREATE TABLE `stock_tbl` (
- `id` int(11) NOT NULL AUTO_INCREMENT,
- `product_id` varchar(200) DEFAULT NULL,
- `count` int(11) DEFAULT NULL,
- PRIMARY KEY (`id`)
- ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
-
(2)创建后台maven工程
(3)pom.xml依赖jar包添加
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <parent>
- <artifactId>springcloudalibaba</artifactId>
- <groupId>com.qingyun</groupId>
- <version>0.0.1-SNAPSHOT</version>
- </parent>
- <modelVersion>4.0.0</modelVersion>
-
- <artifactId>StockSeata</artifactId>
-
- <properties>
-
- </properties>
-
- <dependencies>
-
- <!-- Nacos服务注册发现-->
- <dependency>
- <groupId>com.alibaba.cloud</groupId>
- <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
- </dependency>
-
- <!--继承了父项目,不需要添加版本号-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
-
- <!-- jdbc -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-jdbc</artifactId>
- <version>2.3.5.RELEASE</version>
- </dependency>
-
- <!-- mybatis -->
- <dependency>
- <groupId>org.mybatis.spring.boot</groupId>
- <artifactId>mybatis-spring-boot-starter</artifactId>
- <version>1.3.2</version>
- <!-- 排除冲突的jar包文件-->
- <exclusions>
- <exclusion>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-jdbc</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
-
- <!--Mysql驱动器-->
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- <version>8.0.18</version>
- <scope>runtime</scope>
- </dependency>
-
- <!-- druid-->
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>druid-spring-boot-starter</artifactId>
- <version>1.1.9</version>
- </dependency>
-
-
- </dependencies>
-
-
- </project>

(4)application.properties配置信息
server.port=8085 #应用名称,nacos会将该名称当做服务名称 spring.application.name=stock-seata #nacos服务连接地址 spring.cloud.nacos.server-addr=127.0.0.1:8848 #nacos discovery连接用户名 spring.cloud.nacos.discovery.username=nacos #nacos discovery连接密码 spring.cloud.nacos.discovery.password=nacos #nacos discovery工作空间 spring.cloud.nacos.discovery.workspace=public #永久实例,服务宕机后也不会被剔除,默认是true临时实例 #spring.cloud.nacos.discovery.ephemeral=false #开始配置mysql连接驱动以及数据库连接池参数 spring.datasource.name=mysql_test spring.datasource.type=com.alibaba.druid.pool.DruidDataSource spring.datasource.druid.filters=stat spring.datasource.druid.driver-class-name=com.mysql.jdbc.Driver spring.datasource.druid.url=jdbc:mysql://172.16.210.29:3307/seata_stock?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=GMT spring.datasource.druid.username=root spring.datasource.druid.password=zggk-mysql-3306 #这里可以不用配置,有默认参数,根据自己需求 spring.datasource.druid.initial-size=1 spring.datasource.druid.min-idle=1 spring.datasource.druid.max-active=20 spring.datasource.druid.max-wait=6000 spring.datasource.druid.time-between-eviction-runs-millis=60000 spring.datasource.druid.min-evictable-idle-time-millis=300000 spring.datasource.druid.validation-query=SELECT 'x' spring.datasource.druid.test-while-idle=true spring.datasource.druid.test-on-borrow=false spring.datasource.druid.test-on-return=false spring.datasource.druid.pool-prepared-statements=false spring.datasource.druid.max-pool-prepared-statement-per-connection-size=20 #开始配置mybatis mybatis.mapper-locations=classpath:mapper/*.xml mybatis.type-aliases-package=com.qingyun.entity
(5)程序启动类,开启openfeign远程调用,配置mybatis的扫描路径
- @SpringBootApplication
- @MapperScan("com.qingyun.dao")
- public class StockApplication {
- public static void main(String[] args) {
- SpringApplication.run(StockApplication.class,args);
- }
- }
(6)controller层,提供根据订单id扣减库存的接口
- @RestController
- @RequestMapping("/stock")
- public class StockController {
-
- @Autowired
- StockService stockService;
-
- @Value("${server.port}")
- String port;
-
- @RequestMapping("/reduct")
- public String reduct(@RequestParam(value = "product_id") String product_id){
- return stockService.updateStock(product_id);
- }
-
- }

(7)处理扣减库存的service层
- @Service
- public class StockService {
- @Autowired
- StockDao stockDao;
-
- public String updateStock(String product_id) {
- try {
- stockDao.updateStock(product_id);
- return "扣减库存成功";
- }catch (Exception e){
- return "更新库存失败";
- }
- }
- }
(8)扣减库存的dao接口
- @Repository
- public interface StockDao {
-
- void updateStock(@Param("product_id")String product_id);
- }
(9)库存表实体StockTbl
- public class StockTbl {
-
- private Integer id;
- private String product_id;
- private Integer count;
-
- public Integer getId() {
- return id;
- }
-
- public void setId(Integer id) {
- this.id = id;
- }
-
- public String getProduct_id() {
- return product_id;
- }
-
- public void setProduct_id(String product_id) {
- this.product_id = product_id;
- }
-
- public Integer getCount() {
- return count;
- }
-
- public void setCount(Integer count) {
- this.count = count;
- }
- }

(10)更新库存的映射XML文件
- <?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.qingyun.dao.StockDao">
-
- <!-- 主键自增长的插入 -->
- <update id="updateStock" parameterType="java.lang.String" >
- update seata_stock.stock_tbl SET count= count-1 where product_id=#{product_id}
- </update>
- </mapper>
订单表order_tbl一开始是空表
库存表有一个商品id为10,库存为100的记录
调用下单服务接口,下单成功
订单表增加一行记录
库存表减少一个库存
当订单服务已经添加完成,已经使用rpc远程调用库存服务,系统出现异常,@Transactional回滚时,只能回滚当前系统的订单记录,不能回滚远程调用的库存记录。
调用下单接口,在调用完rpc后添加一个异常代码1/0,使程序异常回滚
此时订单表没有记录
而库存表已经扣减库存
这样的程序肯定是不健壮的。
(1)启动Seata server,Seata server使用nacos作为注册中心和配置中心(步骤3.1已经完成)
(2)配置微服务整合Seata
①订单和库存项目的pom.xml中添加seata依赖
- <!--seata-->
- <dependency>
- <groupId>com.alibaba.cloud</groupId>
- <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
- <version>2.2.6.RELEASE</version>
- <exclusions>
- <exclusion>
- <groupId>io.seata</groupId>
- <artifactId>seata-all</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
-
- <dependency>
- <groupId>io.seata</groupId>
- <artifactId>seata-all</artifactId>
- <version>1.3.0</version>
- <exclusions>
- <exclusion>
- <groupId>com.alibaba</groupId>
- <artifactId>druid</artifactId>
- </exclusion>
- <exclusion>
- <groupId>com.google.protobuf</groupId>
- <artifactId>protobuf-java</artifactId>
- </exclusion>
- <exclusion>
- <groupId>com.github.ben-manes.caffeine</groupId>
- <artifactId>caffeine</artifactId>
- </exclusion>
- </exclusions>
- </dependency>

②各微服务数据库中添加数据库回滚表undo_log
- CREATE TABLE `undo_log` (
- `id` bigint(20) NOT NULL AUTO_INCREMENT,
- `branch_id` bigint(20) NOT NULL,
- `xid` varchar(100) NOT NULL,
- `context` varchar(128) NOT NULL,
- `rollback_info` longblob NOT NULL,
- `log_status` int(11) NOT NULL,
- `log_created` datetime NOT NULL,
- `log_modified` datetime NOT NULL,
- `ext` varchar(100) DEFAULT NULL,
- PRIMARY KEY (`id`),
- UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
- ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
③各微服务application.properties配置事务分组,事务分组的值对应于注册到配置中心的service.vgroupMapping的结尾
- #配置seata事务分组
- spring.cloud.alibaba.seata.tx-service-group=my_test_tx_group
④各微服务application.properties中配置连接seata服务器和配置中心信息
- #配置连接seata服务端的注册中心信息
- seata.registry.type=nacos
- #seata server 所在的nacos服务地址
- seata.registry.nacos.server-addr=127.0.0.1:8848
- #seata server 服务名称,在conf/registry.conf中设置的
- seata.registry.nacos.application=seata-server
- #seata server 所在组,在conf/registry.conf中设置的
- seata.registry.nacos.namespace=public
- seata.registry.nacos.group=SEATA_GROUP
- seata.registry.nacos.password=nacos
- seata.registry.nacos.username=nacos
⑤方法体中使用@GlobalTransactional修饰,再调用下单的方法,程序运行异常
订单表没有添加记录(回滚)
库存表也没有扣减记录(回滚)
说明分布式事务回滚成功
在订单模块发生异常前设置断点
当进入add方法时,会在配置的seata服务指定的表global_table中记录信息(方法名、事务分组、服务id、生成事务xid)
branch_table为分支表,记录着属于哪一次事务的xid,是我id,资源id,自己的分值id
记录每个分支操作的行主键(pk),锁住操作行记录
此时订单表中已经有记录产生
undo_log记录着回退信息属于的分值id、事务id,回滚的信息rollback_info
库存信息表已经进行扣减库存操作
回退日志undo_log表记录中分支id、事务id,rollback_info回退信息
undo_log的rollback_info字段值:记录着sql类型、beforeImage和afterImage信息
当出现异常后回退信息,清空数据表,执行完后seate服务的表清空记录
添加的订单记录页删除
回退完undo_log表也清除记录
库存信息被回滚
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。