什么是分布式事务

  • 本地事务,也就是传统的单机事务。在传统数据库事务中,必须要满足四个原则
    • A(Atomic):原子性,构成事务的所有操作,要么都执行完成,要么全部不执行,不可能出现部分成功部分失败的情况
    • C(Consistency):一致性,在事务执行前后,数据库的一致性约束没有被破坏
    • I(Isolation):隔离性,数据库中的事务一般都是并发的,隔离性是指并发的两个事务的执行互不干扰,一个事务不能看到其他事务的运行过程的中间状态。通过配置事务隔离级别可以比避免脏读、重复读问题
    • D(Durability):持久性,事务完成之后,该事务对数据的更改会持久到数据库,且不会被回滚
  • 分布式事务,就是指不是在单个服务或单个数据库架构下,产生的事务

    什么是CAP理论?

  • CAP 是 Consistency、Availability、Partition tolerance 三个单词的缩写,分别表示一致性、可用性、分区容忍性

    • 一致性
      • 用户访问分布式系统中的任意节点,得到的数据必须一致
    • 可用性
      • 用户访问集群中的任意健康节点,必须能得到响应,而不是超时或拒绝
    • 分区容错
      • 分区
        • 因为网络故障或其它原因导致分布式系统中的部分节点与其它节点失去连接,形成独立分区
      • 容错
        • 在集群出现分区时,整个系统也要持续对外提供服务
  • 在分布式系统中分区容错不可避免,在出现的情况下,A和C之间只能实现一个

    什么是BASE理论?

  • BASE理论是对CAP的一种解决思路,包含三个思想

    • Basically Available(基本可用):分布式系统在出现故障时,允许损失部分可用性,即保证核心可用
    • Soft State(软状态):在一定时间内,允许出现中间状态,比如临时的不一致状态
    • Eventually Consistent(最终一致性):虽然无法保证强一致性,但是在软状态结束后,最终达到数据一致

      常见的分布式事务解决方案?

  • AP模式:各子事务分别执行和提交,允许出现结果不一致,然后采用弥补措施恢复数据即可,实现最终一致

  • CP模式:各个子事务执行后互相等待,同时提交,同时回滚,达成强一致。但事务等待过程中,处于弱可用状态

    简单介绍一下seata框架?

  • 致力于提供高性能和简单易用的分布式事务服务,为用户打造一站式的分布式解决方案

  • seata使用前提
    • 必须使用支持本地 ACID 事务特性的关系型数据库,例如 MySQL、Oracle 等
    • 应用程序必须是使用 JDBC 对数据库进行访问的 JAVA 应用
  • Seata事务管理中有三个重要的角色
    • TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚。
    • TM (Transaction Manager) - 事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务。
    • RM (Resource Manager) - 资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚

image.png

  • 提供了四种不同的分布式事务解决方案
    • A模式:强一致性分阶段事务模式,牺牲了一定的可用性,无业务侵入
    • TCC模式:最终一致的分阶段事务模式,有业务侵入
    • AT模式:最终一致的分阶段事务模式,无业务侵入,也是Seata的默认模式
    • SAGA模式:长事务模式,有业务侵入

无论哪种方案,都离不开TC,也就是事务的协调者

如何使用seata解决分布式事务?

  1. 在order-service中引入依赖

    1. <!--seata-->
    2. <dependency>
    3. <groupId>com.alibaba.cloud</groupId>
    4. <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    5. <exclusions>
    6. <!--版本较低,1.3.0,因此排除-->
    7. <exclusion>
    8. <artifactId>seata-spring-boot-starter</artifactId>
    9. <groupId>io.seata</groupId>
    10. </exclusion>
    11. </exclusions>
    12. </dependency>
    13. <dependency>
    14. <groupId>io.seata</groupId>
    15. <artifactId>seata-spring-boot-starter</artifactId>
    16. <!--seata starter 采用1.4.2版本-->
    17. <version>${seata.version}</version>
    18. </dependency>
  2. 配置TC地址

  • 在order-service中的application.yml中,配置TC服务信息,通过注册中心nacos,结合服务名称获取TC地址
    seata:
    registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
      type: nacos # 注册中心类型 nacos
      nacos:
        server-addr: 127.0.0.1:8848 # nacos地址
        namespace: "" # namespace,默认为空
        group: DEFAULT_GROUP # 分组,默认是DEFAULT_GROUP
        application: seata-tc-server # seata服务名称
        username: nacos
        password: nacos
    tx-service-group: seata-demo # 事务组名称
    service:
      vgroup-mapping: # 事务组与cluster的映射关系
        seata-demo: SH
    
  1. 其它微服务也都参考order-service的步骤来做,完全一样

    介绍一下seata AT模式的工作原理?

  • 获取 SQL 的基本信息:Seata 拦截并解析业务 SQL,得到 SQL 的操作类型
  • 查询前镜像:根据得到的业务 SQL 信息,生成前镜像查询语句
  • 执行前镜像查询语句,得到即将执行操作的数据,并将其保存为前镜像数据(beforeImage)
  • 执行业务 SQL,将这条记录的 url 修改为 c.biancheng.net
  • 查询后镜像:根据前镜像数据的主键(id : 1),生成后镜像查询语句
  • 执行后镜像查询语句,得到执行业务操作后的数据,并将其保存为后镜像数据(afterImage)
  • 插入回滚日志:将前后镜像数据和业务 SQL 的信息组成一条回滚日志记录,插入到 UNDO_LOG 表中
  • 注册分支事务,生成行锁:在这次业务操作的本地事务提交前,RM 会向 TC 注册分支事务,并针对主键 id 为 1 的记录生成行锁
  • 本地事务提交:将业务数据的更新和前面生成的 UNDO_LOG 一并提交
  • 上报执行结果:将本地事务提交的结果上报给 TC
  • 当所有的 RM 都将自己分支事务的提交结果上报给 TC 后,TM 根据 TC 收集的各个分支事务的执行结果,来决定向 TC 发起全局事务的提交或回滚
  • 若所有分支事务都执行成功,TM 向 TC 发起全局事务的提交,并批量删除各个 RM 保存的 UNDO_LOG 记录和行锁;否则全局事务回滚

    请详细说明一下AT模式具体是如何实现事务回滚的?

  • 查找 UNDO_LOG 记录:通过 XID 和分支事务 ID(Branch ID) 查找所有的 UNDO_LOG 记录

  • 数据校验:将 UNDO_LOG 中的后镜像数据(afterImage)与当前数据进行比较,如果有不同,则说明数据被当前全局事务之外的动作所修改,需要人工对这些数据进行处理
  • 生成回滚语句:根据 UNDO_LOG 中的前镜像(beforeImage)和业务 SQL 的相关信息生成回滚语句
  • 还原数据:执行回滚语句,并将前镜像数据、后镜像数据以及行锁删除
  • 提交事务:提交本地事务,并把本地事务的执行结果(即分支事务回滚的结果)上报给 TC

    seataAT模式是否有脏读问题,如何解决?

  • seataAT模式的默认全局隔离级别是读未提交(Read Uncommitted)

  • 应用在特定场景下,必需要求全局的 读已提交 ,目前 Seata 的方式是通过 SELECT FOR UPDATE 语句的代理
  • SELECT FOR UPDATE 语句的执行会申请全局锁 ,如果全局锁被其他事务持有,则释放本地锁(回滚 SELECT FOR UPDATE 语句的本地执行)并重试,这个过程中,查询是被 block 住的,直到全局锁拿到,即读取的相关数据是已提交的,才返回

    seataAT模式是否有脏写问题,如何解决?

  • 在多线程并发访问AT模式的分布式事务时,有可能出现脏写问题

  • 写隔离
    • 一阶段本地事务提交前,需要确保先拿到全局锁
    • 拿不到全局锁 ,不能提交本地事务;
    • 拿全局 的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁

      项目中的实际业务场景?