当前位置:   article > 正文

Spring cloud alibaba--Seata分布式事务_spring-cloud-starter-alibaba-seata

spring-cloud-starter-alibaba-seata

目录

1.Seata是什么

1.1seata的三大角色

1.2分布式事务理论基础

 1.3AT模式(auto transcation)

 1.4TCC模式(Try Confirm Cancel)

2.Seata的AT模式

 3.Seata快速开始

3.1db存储模式+Nacos(注册&配置中心)部署

 4.Seata Client快速开始

4.1创建一个订单系统工程

4.2创建一个库存系统工程

4.3启动系统,访问接口,测试数据

4.4@Transactional事务,系统执行异常

4.5搭建Seata客户端

4.6Seata运行原理


1.Seata是什么

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

1.1seata的三大角色

TC(Transaction Coordinator)-事务协调者:维护全局和分支事务的状态,驱动全局事务提交或回滚

TM(Transaction Manager)-事务管理者:定义全局事务的范围,开始全局事务,提交或回滚全局事务。

RM(Resource Manager)-资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

其中TC是单独部署的Service服务端,TM和RM是嵌入到应用中的Client客户端。

1.2分布式事务理论基础

解决分布式事务,也有相应的规范和协议。分布式事务相关的协议有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响应,此时,协调者就会进入回退流程,对事务进行回退。

 1.3AT模式(auto transcation)

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.4TCC模式(Try Confirm Cancel)

(1)侵入性比较强,并且得自己实现相关事务逻辑控制。

(2)在整个过程基本没有锁

TCC模式需要用户根据自己的业务场景实现Try、Confirm和Cancel三个操作;事务发起方在一阶段执行Try方法,在二阶段提交执行Confirm方法,二阶段回滚执行Cancel方法。

2.Seata的AT模式

第一阶段:

业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。核心在于对业务sql进行解析,转换成undolog,并同时入库。

第二阶段: 

分布式事务操作成功,则TC通知RM异步删除undolog。

 分布式事务操作失败,TM向TC发送回滚请求,RM收到协调器TC发来的回滚请求,通过XID和Branch ID找到对应的回滚日志记录,通过回滚日志记录生成反向的更新Sql并执行,以完成分支的回滚。

 3.Seata快速开始

使用文档参考: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端数据库脚本及各个容器配置

3.1db存储模式+Nacos(注册&配置中心)部署

(1)下载安装包

地址:Releases · seata/seata · GitHub

 (2)解析下载的zip文件

 打开conf/file.conf文件,修改mode="db",并且修改mysql的连接信息(数据库类型、地址、端口、用户名、密码、数据库)

  1. store {
  2. ## store mode: file、db、redis
  3. mode = "db"
  4. db {
  5. ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
  6. datasource = "druid"
  7. ## mysql/oracle/postgresql/h2/oceanbase etc.
  8. dbType = "mysql"
  9. driverClassName = "com.mysql.jdbc.Driver"
  10. url = "jdbc:mysql://127.0.0.1:3306/seata"
  11. user = "root"
  12. password = "root"
  13. minConn = 5
  14. maxConn = 30
  15. globalTable = "global_table"
  16. branchTable = "branch_table"
  17. lockTable = "lock_table"
  18. queryLimit = 100
  19. maxWait = 5000
  20. }
  21. }

(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的连接信息(用户名,密码、地址、端口)

  1. registry {
  2. # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  3. type = "nacos"
  4. nacos {
  5. application = "seata-server"
  6. serverAddr = "127.0.0.1:8848"
  7. group = "SEATA_GROUP"
  8. namespace = ""
  9. cluster = "default"
  10. username = "nacos"
  11. password = "nacos"
  12. }
  13. }

(7)配置配置中心

打开conf/registry.conf配置文件,修改config的type="nacos",配置nacos的连接信息(用户名,密码、地址、端口)

  1. config {
  2. # file、nacos 、apollo、zk、consul、etcd3
  3. type = "nacos"
  4. nacos {
  5. serverAddr = "127.0.0.1:8848"
  6. namespace = ""
  7. group = "SEATA_GROUP"
  8. username = "nacos"
  9. password = "nacos"
  10. }
  11. }

(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端口

 4.Seata Client快速开始

声明式事务实现(@GlobalTransactional)

4.1创建一个订单系统工程

使用mybati操作数据库,向数据库中添加订单,同时调用库存服务更新库存数据库。

(1)创建数据库seata_order,创建一张订单表

  1. create database seata_order;
  2. use seata_order;
  3. CREATE TABLE `order_tbl` (
  4. `id` int(11) NOT NULL AUTO_INCREMENT,
  5. `product_id` varchar(200) DEFAULT NULL,
  6. `total_amount` decimal(10,3) DEFAULT NULL,
  7. `statu` int(11) DEFAULT NULL,
  8. PRIMARY KEY (`id`)
  9. ) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8

(2)创建maven工程,使用mybatis连接数据库

(3)pom.xml依赖jar包添加

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>springcloudalibaba</artifactId>
  7. <groupId>com.qingyun</groupId>
  8. <version>0.0.1-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>OrderSeata</artifactId>
  12. <dependencies>
  13. <!-- Nacos服务注册发现-->
  14. <dependency>
  15. <groupId>com.alibaba.cloud</groupId>
  16. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  17. </dependency>
  18. <!-- 添加springcloud 的openfeign-->
  19. <dependency>
  20. <groupId>org.springframework.cloud</groupId>
  21. <artifactId>spring-cloud-starter-openfeign</artifactId>
  22. <!-- 排除冲突的jar包文件-->
  23. <exclusions>
  24. <exclusion>
  25. <groupId>org.springframework</groupId>
  26. <artifactId>spring-web</artifactId>
  27. </exclusion>
  28. <exclusion>
  29. <groupId>org.springframework.cloud</groupId>
  30. <artifactId>spring-cloud-commons</artifactId>
  31. </exclusion>
  32. </exclusions>
  33. </dependency>
  34. <!--继承了父项目,不需要添加版本号-->
  35. <dependency>
  36. <groupId>org.springframework.boot</groupId>
  37. <artifactId>spring-boot-starter-web</artifactId>
  38. </dependency>
  39. <!-- jdbc -->
  40. <dependency>
  41. <groupId>org.springframework.boot</groupId>
  42. <artifactId>spring-boot-starter-jdbc</artifactId>
  43. <version>2.3.5.RELEASE</version>
  44. </dependency>
  45. <!-- mybatis -->
  46. <dependency>
  47. <groupId>org.mybatis.spring.boot</groupId>
  48. <artifactId>mybatis-spring-boot-starter</artifactId>
  49. <version>1.3.2</version>
  50. <!-- 排除冲突的jar包文件-->
  51. <exclusions>
  52. <exclusion>
  53. <groupId>org.springframework.boot</groupId>
  54. <artifactId>spring-boot-starter-jdbc</artifactId>
  55. </exclusion>
  56. </exclusions>
  57. </dependency>
  58. <!--Mysql驱动器-->
  59. <dependency>
  60. <groupId>mysql</groupId>
  61. <artifactId>mysql-connector-java</artifactId>
  62. <version>8.0.18</version>
  63. <scope>runtime</scope>
  64. </dependency>
  65. <!-- druid-->
  66. <dependency>
  67. <groupId>com.alibaba</groupId>
  68. <artifactId>druid-spring-boot-starter</artifactId>
  69. <version>1.1.9</version>
  70. </dependency>
  71. </dependencies>
  72. </project>

 (4)application.properties配置信息

  1. server.port=8084
  2. #应用名称,nacos会将该名称当做服务名称
  3. spring.application.name=order-seata
  4. #nacos服务连接地址
  5. spring.cloud.nacos.server-addr=127.0.0.1:8848
  6. #nacos discovery连接用户名
  7. spring.cloud.nacos.discovery.username=nacos
  8. #nacos discovery连接密码
  9. spring.cloud.nacos.discovery.password=nacos
  10. #nacos discovery工作空间
  11. spring.cloud.nacos.discovery.workspace=public
  12. #开始配置mysql连接驱动以及数据库连接池参数
  13. spring.datasource.name=mysql_test
  14. spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
  15. spring.datasource.druid.filters=stat
  16. spring.datasource.druid.driver-class-name=com.mysql.jdbc.Driver
  17. spring.datasource.druid.url=jdbc:mysql://172.16.210.29:3307/seata_order?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=GMT
  18. spring.datasource.druid.username=root
  19. spring.datasource.druid.password=zggk-mysql-3306
  20. #这里可以不用配置,有默认参数,根据自己需求
  21. spring.datasource.druid.initial-size=1
  22. spring.datasource.druid.min-idle=1
  23. spring.datasource.druid.max-active=20
  24. spring.datasource.druid.max-wait=6000
  25. spring.datasource.druid.time-between-eviction-runs-millis=60000
  26. spring.datasource.druid.min-evictable-idle-time-millis=300000
  27. spring.datasource.druid.validation-query=SELECT 'x'
  28. spring.datasource.druid.test-while-idle=true
  29. spring.datasource.druid.test-on-borrow=false
  30. spring.datasource.druid.test-on-return=false
  31. spring.datasource.druid.pool-prepared-statements=false
  32. spring.datasource.druid.max-pool-prepared-statement-per-connection-size=20
  33. #开始配置mybatis
  34. mybatis.mapper-locations=classpath:mapper/*.xml
  35. mybatis.type-aliases-package=com.qingyun.entity

(5)程序启动类,开启openfeign远程调用,配置mybatis的扫描路径

  1. @SpringBootApplication
  2. @MapperScan("com.qingyun.dao")
  3. @EnableFeignClients
  4. public class OrderApplication {
  5. public static void main(String[] args) {
  6. SpringApplication.run(OrderApplication.class,args);
  7. }
  8. }

(6)controller层,调用本服务添加订单信息,调用远程服务更新库存

  1. @RestController
  2. @RequestMapping("/order")
  3. public class OrderController {
  4. @Autowired
  5. OrderService orderService;
  6. @Autowired
  7. StockOpenFeign stockOpenFeign;
  8. @RequestMapping("/add")
  9. public String add(){
  10. OrderTbl orderTbl = new OrderTbl();
  11. orderTbl.setProduct_id("10");
  12. orderTbl.setTotal_amount(new BigDecimal(3000));
  13. orderTbl.setStatu(0);
  14. orderService.insert(orderTbl);
  15. String reduct = stockOpenFeign.reduct(orderTbl.getProduct_id());
  16. return "add order "+reduct;
  17. }
  18. }

(7)远程更新库存的接口

  1. @FeignClient(value = "stock-seata",path ="/stock" )
  2. public interface StockOpenFeign {
  3. @RequestMapping("/reduct")
  4. String reduct(@RequestParam("product_id")String product_id);
  5. }

(8)更新本系统订单的service层

  1. @Service
  2. public class OrderService {
  3. @Autowired
  4. OrderDao orderDao;
  5. public void insert(OrderTbl orderTbl) {
  6. orderDao.insert(orderTbl);
  7. }
  8. }

(9)更新本系统订单的dao接口

  1. @Repository
  2. public interface OrderDao {
  3. void insert(OrderTbl orderTbl);
  4. }

(10)订单表实体OrderTbl

  1. public class OrderTbl {
  2. private Integer id;
  3. private String product_id;
  4. private BigDecimal total_amount;
  5. private Integer statu;
  6. public OrderTbl() {
  7. }
  8. public Integer getId() {
  9. return id;
  10. }
  11. public void setId(Integer id) {
  12. this.id = id;
  13. }
  14. public String getProduct_id() {
  15. return product_id;
  16. }
  17. public void setProduct_id(String product_id) {
  18. this.product_id = product_id;
  19. }
  20. public BigDecimal getTotal_amount() {
  21. return total_amount;
  22. }
  23. public void setTotal_amount(BigDecimal total_amount) {
  24. this.total_amount = total_amount;
  25. }
  26. public Integer getStatu() {
  27. return statu;
  28. }
  29. public void setStatu(Integer statu) {
  30. this.statu = statu;
  31. }
  32. @Override
  33. public String toString() {
  34. return "OrderTbl{" +
  35. "id=" + id +
  36. ", product_id='" + product_id + '\'' +
  37. ", total_amount=" + total_amount +
  38. ", statu=" + statu +
  39. '}';
  40. }
  41. }

(11)添加记录到订单的映射XML文件

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  3. <mapper namespace="com.qingyun.dao.OrderDao">
  4. <!-- 主键自增长的插入 -->
  5. <insert id="insert" parameterType="com.qingyun.entity.OrderTbl" useGeneratedKeys="true" keyProperty="id">
  6. insert into seata_order.order_tbl(product_id,total_amount,statu) values(
  7. #{product_id},
  8. #{total_amount},
  9. #{statu}
  10. );
  11. </insert>
  12. </mapper>

4.2创建一个库存系统工程

使用mybatis操作数据库,等待订单系统调用,根据商品id更新库存。

(1)创建数据库seata_stock,创建一张库存表

  1. create database seata_stock;
  2. use seata_stock;
  3. CREATE TABLE `stock_tbl` (
  4. `id` int(11) NOT NULL AUTO_INCREMENT,
  5. `product_id` varchar(200) DEFAULT NULL,
  6. `count` int(11) DEFAULT NULL,
  7. PRIMARY KEY (`id`)
  8. ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8

(2)创建后台maven工程

 (3)pom.xml依赖jar包添加

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>springcloudalibaba</artifactId>
  7. <groupId>com.qingyun</groupId>
  8. <version>0.0.1-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>StockSeata</artifactId>
  12. <properties>
  13. </properties>
  14. <dependencies>
  15. <!-- Nacos服务注册发现-->
  16. <dependency>
  17. <groupId>com.alibaba.cloud</groupId>
  18. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  19. </dependency>
  20. <!--继承了父项目,不需要添加版本号-->
  21. <dependency>
  22. <groupId>org.springframework.boot</groupId>
  23. <artifactId>spring-boot-starter-web</artifactId>
  24. </dependency>
  25. <!-- jdbc -->
  26. <dependency>
  27. <groupId>org.springframework.boot</groupId>
  28. <artifactId>spring-boot-starter-jdbc</artifactId>
  29. <version>2.3.5.RELEASE</version>
  30. </dependency>
  31. <!-- mybatis -->
  32. <dependency>
  33. <groupId>org.mybatis.spring.boot</groupId>
  34. <artifactId>mybatis-spring-boot-starter</artifactId>
  35. <version>1.3.2</version>
  36. <!-- 排除冲突的jar包文件-->
  37. <exclusions>
  38. <exclusion>
  39. <groupId>org.springframework.boot</groupId>
  40. <artifactId>spring-boot-starter-jdbc</artifactId>
  41. </exclusion>
  42. </exclusions>
  43. </dependency>
  44. <!--Mysql驱动器-->
  45. <dependency>
  46. <groupId>mysql</groupId>
  47. <artifactId>mysql-connector-java</artifactId>
  48. <version>8.0.18</version>
  49. <scope>runtime</scope>
  50. </dependency>
  51. <!-- druid-->
  52. <dependency>
  53. <groupId>com.alibaba</groupId>
  54. <artifactId>druid-spring-boot-starter</artifactId>
  55. <version>1.1.9</version>
  56. </dependency>
  57. </dependencies>
  58. </project>

 (4)application.properties配置信息

  1. server.port=8085
  2. #应用名称,nacos会将该名称当做服务名称
  3. spring.application.name=stock-seata
  4. #nacos服务连接地址
  5. spring.cloud.nacos.server-addr=127.0.0.1:8848
  6. #nacos discovery连接用户名
  7. spring.cloud.nacos.discovery.username=nacos
  8. #nacos discovery连接密码
  9. spring.cloud.nacos.discovery.password=nacos
  10. #nacos discovery工作空间
  11. spring.cloud.nacos.discovery.workspace=public
  12. #永久实例,服务宕机后也不会被剔除,默认是true临时实例
  13. #spring.cloud.nacos.discovery.ephemeral=false
  14. #开始配置mysql连接驱动以及数据库连接池参数
  15. spring.datasource.name=mysql_test
  16. spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
  17. spring.datasource.druid.filters=stat
  18. spring.datasource.druid.driver-class-name=com.mysql.jdbc.Driver
  19. spring.datasource.druid.url=jdbc:mysql://172.16.210.29:3307/seata_stock?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=GMT
  20. spring.datasource.druid.username=root
  21. spring.datasource.druid.password=zggk-mysql-3306
  22. #这里可以不用配置,有默认参数,根据自己需求
  23. spring.datasource.druid.initial-size=1
  24. spring.datasource.druid.min-idle=1
  25. spring.datasource.druid.max-active=20
  26. spring.datasource.druid.max-wait=6000
  27. spring.datasource.druid.time-between-eviction-runs-millis=60000
  28. spring.datasource.druid.min-evictable-idle-time-millis=300000
  29. spring.datasource.druid.validation-query=SELECT 'x'
  30. spring.datasource.druid.test-while-idle=true
  31. spring.datasource.druid.test-on-borrow=false
  32. spring.datasource.druid.test-on-return=false
  33. spring.datasource.druid.pool-prepared-statements=false
  34. spring.datasource.druid.max-pool-prepared-statement-per-connection-size=20
  35. #开始配置mybatis
  36. mybatis.mapper-locations=classpath:mapper/*.xml
  37. mybatis.type-aliases-package=com.qingyun.entity

(5)程序启动类,开启openfeign远程调用,配置mybatis的扫描路径

  1. @SpringBootApplication
  2. @MapperScan("com.qingyun.dao")
  3. public class StockApplication {
  4. public static void main(String[] args) {
  5. SpringApplication.run(StockApplication.class,args);
  6. }
  7. }

(6)controller层,提供根据订单id扣减库存的接口

  1. @RestController
  2. @RequestMapping("/stock")
  3. public class StockController {
  4. @Autowired
  5. StockService stockService;
  6. @Value("${server.port}")
  7. String port;
  8. @RequestMapping("/reduct")
  9. public String reduct(@RequestParam(value = "product_id") String product_id){
  10. return stockService.updateStock(product_id);
  11. }
  12. }

(7)处理扣减库存的service层

  1. @Service
  2. public class StockService {
  3. @Autowired
  4. StockDao stockDao;
  5. public String updateStock(String product_id) {
  6. try {
  7. stockDao.updateStock(product_id);
  8. return "扣减库存成功";
  9. }catch (Exception e){
  10. return "更新库存失败";
  11. }
  12. }
  13. }

(8)扣减库存的dao接口

  1. @Repository
  2. public interface StockDao {
  3. void updateStock(@Param("product_id")String product_id);
  4. }

(9)库存表实体StockTbl 

  1. public class StockTbl {
  2. private Integer id;
  3. private String product_id;
  4. private Integer count;
  5. public Integer getId() {
  6. return id;
  7. }
  8. public void setId(Integer id) {
  9. this.id = id;
  10. }
  11. public String getProduct_id() {
  12. return product_id;
  13. }
  14. public void setProduct_id(String product_id) {
  15. this.product_id = product_id;
  16. }
  17. public Integer getCount() {
  18. return count;
  19. }
  20. public void setCount(Integer count) {
  21. this.count = count;
  22. }
  23. }

(10)更新库存的映射XML文件

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  3. <mapper namespace="com.qingyun.dao.StockDao">
  4. <!-- 主键自增长的插入 -->
  5. <update id="updateStock" parameterType="java.lang.String" >
  6. update seata_stock.stock_tbl SET count= count-1 where product_id=#{product_id}
  7. </update>
  8. </mapper>

4.3启动系统,访问接口,测试数据

订单表order_tbl一开始是空表

 库存表有一个商品id为10,库存为100的记录

 调用下单服务接口,下单成功

 订单表增加一行记录

 库存表减少一个库存

4.4@Transactional事务,系统执行异常

当订单服务已经添加完成,已经使用rpc远程调用库存服务,系统出现异常,@Transactional回滚时,只能回滚当前系统的订单记录,不能回滚远程调用的库存记录。

 调用下单接口,在调用完rpc后添加一个异常代码1/0,使程序异常回滚

 此时订单表没有记录

而库存表已经扣减库存

 这样的程序肯定是不健壮的。

4.5搭建Seata客户端

(1)启动Seata server,Seata server使用nacos作为注册中心和配置中心(步骤3.1已经完成)

(2)配置微服务整合Seata

①订单和库存项目的pom.xml中添加seata依赖

  1. <!--seata-->
  2. <dependency>
  3. <groupId>com.alibaba.cloud</groupId>
  4. <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
  5. <version>2.2.6.RELEASE</version>
  6. <exclusions>
  7. <exclusion>
  8. <groupId>io.seata</groupId>
  9. <artifactId>seata-all</artifactId>
  10. </exclusion>
  11. </exclusions>
  12. </dependency>
  13. <dependency>
  14. <groupId>io.seata</groupId>
  15. <artifactId>seata-all</artifactId>
  16. <version>1.3.0</version>
  17. <exclusions>
  18. <exclusion>
  19. <groupId>com.alibaba</groupId>
  20. <artifactId>druid</artifactId>
  21. </exclusion>
  22. <exclusion>
  23. <groupId>com.google.protobuf</groupId>
  24. <artifactId>protobuf-java</artifactId>
  25. </exclusion>
  26. <exclusion>
  27. <groupId>com.github.ben-manes.caffeine</groupId>
  28. <artifactId>caffeine</artifactId>
  29. </exclusion>
  30. </exclusions>
  31. </dependency>

②各微服务数据库中添加数据库回滚表undo_log

  1. CREATE TABLE `undo_log` (
  2. `id` bigint(20) NOT NULL AUTO_INCREMENT,
  3. `branch_id` bigint(20) NOT NULL,
  4. `xid` varchar(100) NOT NULL,
  5. `context` varchar(128) NOT NULL,
  6. `rollback_info` longblob NOT NULL,
  7. `log_status` int(11) NOT NULL,
  8. `log_created` datetime NOT NULL,
  9. `log_modified` datetime NOT NULL,
  10. `ext` varchar(100) DEFAULT NULL,
  11. PRIMARY KEY (`id`),
  12. UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
  13. ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

 ③各微服务application.properties配置事务分组,事务分组的值对应于注册到配置中心的service.vgroupMapping的结尾

  1. #配置seata事务分组
  2. spring.cloud.alibaba.seata.tx-service-group=my_test_tx_group

 ④各微服务application.properties中配置连接seata服务器和配置中心信息

  1. #配置连接seata服务端的注册中心信息
  2. seata.registry.type=nacos
  3. #seata server 所在的nacos服务地址
  4. seata.registry.nacos.server-addr=127.0.0.1:8848
  5. #seata server 服务名称,在conf/registry.conf中设置的
  6. seata.registry.nacos.application=seata-server
  7. #seata server 所在组,在conf/registry.conf中设置的
  8. seata.registry.nacos.namespace=public
  9. seata.registry.nacos.group=SEATA_GROUP
  10. seata.registry.nacos.password=nacos
  11. seata.registry.nacos.username=nacos

⑤方法体中使用@GlobalTransactional修饰,再调用下单的方法,程序运行异常

 订单表没有添加记录(回滚)

 库存表也没有扣减记录(回滚)

 说明分布式事务回滚成功

4.6Seata运行原理

在订单模块发生异常前设置断点

 当进入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表也清除记录

 库存信息被回滚

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/68831?site
推荐阅读
相关标签
  

闽ICP备14008679号