不要再说你不懂分布式事务了 seata

事务概述

数据库事务

  • A(Atomic):原子性,构成事务的所有操作,要么都执行完成,要么全部不执行,不可能出现部
    分成功部分失败的情况。

  • C(Consistency):一致性,在事务执行前后,数据库的一致性约束没有被破坏。
    比如:张三向李四转 100 元,转账前和转账后的数据是正确状态这叫一致性,如果出现张三
    转出 100 元,李四账户没有增加 100 元这就出现了数据错误,就没有达到一致性。

  • I(Isolation):隔离性,数据库中的事务一般都是并发的,隔离性是指并发的两个事务的执行互
    不干扰,一个事务不能看到其他事务的运行过程的中间状态。通过配置事务隔离级别可以比避免脏
    读、重复读问题。

  • D(Durability):持久性,事务完成之后,该事务对数据的更改会持久到数据库,且不会被回
    滚。

分布式事务

分布式事务:分布式+事务

一句话理解:订单生成业务:
1.订单生成
2.扣减库存

这是两个系统 并且是两个业务数据库 这就涉及到了分布式事务 如何控制?

seata

Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式决方案。一句话理解 加一个注解就能解决分布式事务

seata术语

  • TC:事务协调者 维护全局和分支事务的状态。独立的中间件 需要单独部署。

  • TM:事务管理器 开启全局事务、提交或回滚 发起者

  • RM:资源管理器 管理分支事务负责注册汇报等各分支的情况,驱动分支事务的提交和回滚

分布式事物的几种模式

XA模式

典型的2pc模式 执行原理

1.TM向TC注册一个全局事务
2.根据业务 各个RM会逐个向TC注册分支事务
3.各个RM在接到指令后会在本地进行事务预执行
4.然后各个RM将执行结果报告的TC 这个结果可能成功也可能失败
5.TC在收到各个RM的报告后会获取汇总结果并将总结果汇报给TM,根据汇总结果TM会向TC发起最后确认指令
6.TC收到最后执行向各个RM发送最终执行指令— 提交还是回滚

两阶段提交,将整个事务流程分为两个阶段,准备阶段、提交阶段,2是两个阶段,P是准备阶段,C是提交阶段
举个例子:我和你相约饭店吃饭,咱们使用AA制付款,只有我们都付了钱,老板才会上菜。如果有一个人没付款或不够钱付款,那么将不上菜并且会确保收了部分的钱退还。
准备阶段:老板要求咱两付钱,我付钱,你也付钱
提交阶段:老板确认完毕,开始做菜,坐等吃饭
异常:其中有人没付钱或者没菜了,不会上菜或者会退款给咱们。

AT模式

seata默认使用AT协议
和XA协议非常相似 区别点在于 在所有的RM执行完毕第二阶段的Global commit 后 AT模式能够自动异步方式批量清理掉回滚日志。而XA模式不会自动清洗,需要手动清理
在这里插入图片描述

2PC

优点:原理和实现都比较简单
缺陷:同步阻塞、存在中心化问题 实现太过于保守。

Saga模式

Saga模式是1987年由普林斯顿大学的Hector Garcia-Molina和Kenneth Salem共同提出的。该模式不同于前面的模式,它不是2PC的,其是一种长事务解决方案。

在这里插入图片描述

其应用场景是:

在无法提供TC、TM、RM接口的情况下,对于一个流程很长的复杂业务,其会包含很多的子流程(事
务)。每个子流程都让它们真实提交它们真正的执行结果。只有当前子流程执行成功后才能执行下一个子流程。若整个流程中所有子流程全部执行成功,则整个业务流程成功;只要有一个子流程执行失败,

则可采用两种补偿方式:

向后恢复:对于其前面所有成功的子流程,其执行结果全部撤销
向前恢复:重试失败的子流程直到其成功

seata-server配置和启动

官网:https://seata.io/zh-cn/blog/download.html 源码包和运行包都要下载 因为包含了很多脚本
下载源码包和运行包 注意:需要注意使用seata和springcloud兼容的版本 https://github.com/alibaba/spring-cloud-alibaba/wiki/

1.下载源码包和运行包
在这里插入图片描述
在这里插入图片描述
2.运行mysql.sql脚本 建立一个seata数据库 导入该sql 源码包:seata-1.3.0\script\server\db\mysql.sql 该脚本会创建了三 张表。这三张表都是用于保存整个系统中分布式事务相关日志数据的。

在这里插入图片描述
3.修改seata运行包seata-server-1.3.0\seata\conf目录下的file.conf文件 配置stata服务存放日志的位置 这里选择用mysql
在这里插入图片描述
4.修改seata运行包seata-server-1.3.0\seata\conf目录下的registry.conf文件 配置stata服务要连接的配置中心和注册中心
在这里插入图片描述
5.修改seata源码包下的seata-1.3.0\script\config-center目录下的config.txt文件复制一份到seata运行包下的根目录下
在这里插入图片描述
6.对其config.txt进行修改:修改其数据库的配置
在这里插入图片描述
7.运行seata源码包下的seata-1.3.0\script\config-center\nacos目录下有一份nacos-config.sh配置文件 将其复制到seata运行包下seata-server-1.3.0\seata\config.txt这个文件的下一级目录 也就是seata-server-1.3.0\seata\bin目录下 在该目录下执行 sh nacos-config.sh -h 127.0.0.1
在这里插入图片描述
8.启动seata服务
在seata运行包目录下seata-server-1.3.0\seata\bin有个seata-server.bat脚本 执行命令:seata-server.bat -m db -p 8091 -h 127.0.0.1 启动 为止 seata服务的配置和启动就搞定了 接下来定义两个工程来验证分布式事务

在这里插入图片描述

验证

需求:定义两个服务 1.purchase(采购服务) 2.stock(库存服务)
入口:采购服务定义一个接口 1.添加一条采购记录 2.调用库存服务 添加一条库存记录对于系统来说是两个事务 但对于我们的业务来说 就是一个完整的事务

环境构建purchase采购服务:

1.pom.xml

<?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>springcloud-alibaba-sxw-parent</artifactId>
        <groupId>com.sxw</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>

    <artifactId>07-seata-purchase</artifactId>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR8</spring-cloud.version>
        <spring-cloud-alibaba.version>2.2.3.RELEASE</spring-cloud-alibaba.version>
    </properties>

    <dependencies>
        <!-- seata依赖 -->
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>1.4.2</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <version>2.2.1.RELEASE</version>
            <exclusions>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-spring-boot-starter</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--nacos config 依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!--nacos discovery依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--actuator依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.10</version>
        </dependency>

        <!--修改MySQL驱动版本-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>


    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2.配置文件boostrap.yml

spring:
  application:
    name: sxw-purchase-seata
  cloud:
    nacos:
      config:
        server-addr: localhost:8848
        file-extension: yml
        group: SEATA_GROUP
seata:
  tx-service-group: my_test_tx_group

logging.level.io.seata: debug

配置中心的配置:

server:
  port: 8081
spring:
  cloud:
    nacos:
      disvovery:
        server-addr: 127.0.0.1:8848
  jpa:
    generate-ddl: true
    show-sql: true
    hibernate:
      ddl-auto: none
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/kaikeba?
    username: root
    password: root
  logging:
    level:
      root: info
      org.hibernate: info
      org.hibernate.type.descriptor.sql.BasicBinder: trace
      org.hibernate.type.descriptor.sql.BasicExtractor: trace
      com.abc.provider: debug

3.配置代理数据源 注意:使用seata需要接入seata的代理数据源 这里以jpa为例子 常用的还有 mybatis 需要注意区分开

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

    /**
     * 需要将 DataSourceProxy 设置为主数据源,否则事务无法回滚
     * @param druidDataSource The DruidDataSource
     * @return The default datasource
     */
    @Primary
    @Bean("dataSource")
    public DataSource dataSource(DruidDataSource druidDataSource) {
        return new DataSourceProxy(druidDataSource);
    }
}

4.采购实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@JsonIgnoreProperties({"hibernateLazyInitializer","handler","fieldHandler"})
public class Purchase {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String name;//采购资源名称
    private Integer count;//采购量
}

5.定义采购接口

@RestController
@RequestMapping("vm")
public class PurchaseController {

    @Autowired
    private PurchaseService purchaseService;
    @Autowired
    private RestTemplate restTemplate;

    @GlobalTransactional//TM 事务的管理器
    @RequestMapping("/purchase/add/{deno}")
    //采购服务控制器 入口
    public boolean addPurchaseHandle(@RequestBody Purchase purchase
            ,@PathVariable("deno") int deno) {
        //添加一条采购记录
        purchaseService.addPurchase(purchase);

        //添加一条库存记录  另外一个服务。。。
        Stock stock = new Stock();
        stock.setName(purchase.getName());
        stock.setTotal(purchase.getCount());

        String url = "http://sxw-stock-seata/stock/add";
        Boolean result = restTemplate.postForObject(url, stock, Boolean.class);

        if (result != null) {

            return result;
        }
        return false;
    }

}

6.添加undo.log表 在源码包seata-1.3.0\script\client\at\db的mysql.sql文件 启动服务

环境构建库存服务

1.pom.xml 是一样的

2.boostrap.yml

# 配置配置中心的服务地址
spring:
  application:
    # 配置应用名称
    name: sxw-stock-seata
  cloud:
    nacos:
      config:
        server-addr: localhost:8848
        file-extension: yml
        group: SEATA_GROUP
seata:
  tx-service-group: my_test_tx_group

配置中心:

erver:
  port: 8082
spring:
  cloud:
    nacos:
      disvovery:
        server-addr: 127.0.0.1:8848
  jpa:
    generate-ddl: true
    show-sql: true
    hibernate:
      ddl-auto: none
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/kaikeba?
    username: root
    password: root
  logging:
    level:
      root: info
      org.hibernate: info
      org.hibernate.type.descriptor.sql.BasicBinder: trace
      org.hibernate.type.descriptor.sql.BasicExtractor: trace
      com.abc.provider: debug

3.代理数据源 和采购服务是一样的 为什么?因为既然要使用分布式事务 需要各个事务参与者都要被seata服务检测到 作为一个seata-client的角色

4.库存实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@JsonIgnoreProperties({"hibernateLazyInitializer","handler","fieldHandler"})
public class Stock {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String name;//库存名称
    private Integer total;//资源数量
}

5.定义库存接口

@RestController
public class StockController {
    @Autowired
    private StockService stockService;

    @RequestMapping("/stock/add")
    public boolean addStockHandle(@RequestBody Stock stock) {
        //手动异常
        throw new RuntimeException("bug is coming~");
//        return stockService.addStock(stock);
    }
}

6.添加undo.log表 在源码包seata-1.3.0\script\client\at\db的mysql.sql文件 启动服务

测试:采购服务执行正常插入一条采购数据,调用库存服务,库存服务异常,此时我们看下数据库的结果是几条数据?
当然是不变 这样就确保了分布式事务的一致性。

注意:seata使用的数据库的三个表会发现数据一直都是空的。为什么?因为seata默认使用的是AT模式,AT模式事务处理完毕会自动清楚日志,因此我们是看不到数据的。我们可以在一个分支事务提交后打一个断点,此时就可以看到存了一些数据,具体内容是一些sql拼装以及整个分布式事务的一些信息,如果所有分支事务都完成了,那么提交,否则根据拼装的sql进行回滚数据。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值