• 本地事务: 就是传统的单机事务
  • 事务的4大特性ACID:
    • C一致性:修改数据后 前后的数据总和要保持一致
    • A原子性:事务中的所有操作要么全部成功,要么全部失败
    • D隔离性:对同一资源操作的事务不能同时发生
    • I持久性:对数据的修改是永久性的
  • 分布式事务
    • 就是值不是在单个服务或单个数据库的架构下(分布式架构)下产生的事务
  • CAD理论
    • 分布式系统有3个指标:(3个指标不能同时满足)
      • A可用性:用户访问服务中的任意健康节点,必须能得到响应,而不是超时或拒绝
      • C一致性:用户访问分布式的任意节点,得到的数据必须是一致
      • P分区容错性:因为网络故障或其他的原因导致分布式中部分节点是去连接,形成独立分区
      • p是不可避免的;所以C和A不能同时错存在 只能是AP或者CP
  • BASE理论:
    • base理论是对CAP的一种解决思路:包含三个思想
      • basiclly avaliable(基本可用):分布式系统出现故障时,允许损失部分可用性,保证系统的核心功能可用
      • soft state(软状态):在一定的时间内,允许出现一定的数据不一致的中间状态
      • eventually consistent(最终一致):虽然无法保证强一致性:但是在软状态结束后,能保证数据最终一致
    • 解决分布式事务的思路:
      • AP:各个子事务分别提交和执行,允许出现结果不一致,最终采取补救措施,实现数据一致
      • CP:各个子事务执行后,互相等待,同时提交,同时回滚,达到数据的强一致,在等待过程中,处于弱可用状态
      • 不管那种解决方式:都需要一个事务的协调者TC,协调各个子事务之间的通信,状态
  • seata:

    • 阿里开源的一款分布式事务一站式解决方案的框架
    • seata的三个重要的角色
      • TC(Transaction coordinator)事务协调者:维护全局事务和分支事务的状态,协调全局事务提交或者回滚
      • TM(teansaction manager)事务管理器:定义全局事务的范围,开始全局事务的提交回滚
      • RM(Resource manager)资源管理器:管理分支事务处理的资源,与TC交谈以及注册分支事务和报告分支事务的状态,并驱动分支事务提交或者回滚
    • seata基于上述架构提供了4种分布式事务的解决方案
      • XA模式:保证事务的强一致,牺牲了一定的可用性
      • TCC:最终一直的分阶段事务模式,有业务侵入
      • AT:最终一致的分布式事务模式,无侵入,也是seata的默认模式
      • SAGA模式:长事务模式,有业务侵入
    • seata的代码实现

      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>
    • 配置TC的地址(事务的协调者就是seata服务端)

      1. seata:
      2. registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
      3. type: nacos # 注册中心类型 nacos
      4. nacos:
      5. server-addr: 127.0.0.1:8848 # nacos地址
      6. namespace: "" # namespace,默认为空
      7. group: DEFAULT_GROUP # 分组,默认是DEFAULT_GROUP
      8. application: seata-tc-server # seata服务名称
      9. username: nacos
      10. password: nacos
      11. tx-service-group: seata-demo # 事务组名称
      12. service:
      13. vgroup-mapping: # 事务组与cluster的映射关系
      14. seata-demo: SHseata:
      15. data-source-proxy-mode: XA #这个可以选择AT模式
    • 传统的XA接口模式 遵循2pc协议 2个阶段:

      • 一阶段
        • 事务的协调者通知每个事务的参与者执行本地事务
        • 本地事务完成后通知事务的协调者,此时事务不提交,继续持有数据库锁
      • 二阶段
        • 如果第一阶段都成功:则通知所有的事务参与者提交事务
        • 如果第一阶段没有全部成功:则通知所有的事务参与者回滚事务
    • seata的XA模式
      • RM的一阶段
        • 注册分支事务到TC
        • 执行分支事务的sql 但是不提交
        • 报告执行状态到TC
      • TC的二阶段
        • 检查各个分支事务的状态:
          • 如果都成功:则通知所有的RM 提交事务
          • 如果有失败:通知所有的RM 回滚事务
      • RM的二阶段:接收TC 指定,执行任务
  • AT模式

    • 在涉及事务的服务导入依赖,在方法上加上注解@globaltransaction 配置相关信息
    • 在数据库创建3张表 全局事务表 :用于保存全局事务的信息;分支事务表 :用于保存分支事务的信息和全局锁表用户保存操作的数据的锁信息
    • 在每一个修改的数据库都传建一个undo_log日志表 用来保存 修改前的数据(前置镜像)和修改后的数据 (后置镜像)
    • 执行流程:
      • 执行事务方法 :@globaltransaction等于一个拦截器 ,拦截器就会在全局事务表注册写入一条数据 保存XID ,Status ,applicacationnaId,和事务名字;
      • 执行分支事务的时候,然后把 xid 分支id application 状态等信息存数据源会被seata的数据源代理,先解析执行的sql , 获取sql的表名,要操作的字段,以及条件,先去数据库 查询到修改之前的值作为前置镜像, 然后获取本地db锁 执行sql,提交执行必须获取全局锁才可以提交,保证了避免出现并发修改的问题, 提交后释放本地锁再次查询修改后的值作为后置镜像, 把XId 和分支id(branchid)和raollbackinfo(前置镜像和后置镜像)存入undo_log表 :这个表必须和修改的表在同一个数据库,保证是一个本地事务要么一起成功,要么一起失败
      • 如果每一个分支事务都执行成功,则会删除undo_log的数据 和全局事务表,分支事务表,以及全局锁表的数据 这个时候就会释放全局锁 ,其他的线程就可以获取全局锁来操作
      • 如果有一个分支事务没有成功:就会去undo_log表获取数据 先用后置镜像和修改的表 中数据对比是否一直,如果一致,就把把数据恢复到前置镜像的值,然后删除全局事务表的数据…等 释放全局锁资源 这样避免了数据中途有别的写操作(人工或seata以外的事务操作) 如果不一致就会报错,并记录异常,
    • XA 和AT对比
      • xa 保证了数据的强一致性; 但是有阻塞 性能不好
      • at 没有阻塞,性能好, 但是有脏读, 软状态,终止数据一致
      • 2者都必须依赖关系型数据库,以及jdbc才能实现
    • TCC模式
      • TCC模式与AT模式非常相似,每阶段都是独立事务,不同的是TCC通过人工编码来实现数据恢复。需要实现三个方法:
      • Try:资源的检测和预留;
      • Confirm:完成资源操作业务;要求 Try 成功 Confirm 一定要能成功。
      • Cancel:预留资源释放,可以理解为try的反向操作。
    • 流程分析:
    • 举例,一个扣减用户余额的业务。假设账户A原来余额是100,需要余额扣减30元。

      • 阶段一( Try ):检查余额是否充足,如果充足则冻结金额增加30元,可用余额扣除30:总金额 = 冻结金额 + 可用金额,数量依然是100不变。事务直接提交无需等待其它事务。
      • 阶段二(Confirm):假如要提交(Confirm),则冻结金额扣减30: 总金额 = 冻结金额 + 可用金额 = 0 + 70 = 70元
      • 阶段二(Canncel):如果要回滚(Cancel),则冻结金额扣减30,可用余额增加30 需要回滚,那么就要释放冻结金额,恢复可用金额:

        .优缺点TCC模式的每个阶段是做什么的?

    • Try:资源检查和预留

    • Confirm:业务执行和提交
      • Cancel:预留资源的释放

TCC的优点是什么?

  • 一阶段完成直接提交事务,释放数据库资源,性能好
  • 相比AT模型,无需生成快照,无需使用全局锁,性能最强
  • 不依赖数据库事务,而是依赖补偿操作,可以用于非事务型数据库

TCC的缺点是什么?

  • 有代码侵入,需要人为编写try、Confirm和Cancel接口,太麻烦
  • 软状态,事务是最终一致
  • 需要考虑Confirm和Cancel的失败情况,做好幂等处理

    4.4.SAGA模式

    分布式事务内有多个参与者,每一个参与者都是一个冲正补偿服务,需要用户根据业务场景实现其正向操作和逆向回滚操作。
    分布式事务执行过程中,依次执行各参与者的正向操作,如果所有正向操作均执行成功,那么分布式事务提交。如果任何一个正向操作执行失败,那么分布式事务会去退回去执行前面各参与者的逆向回滚操作,回滚已提交的参与者,使分布式事务回到初始状态。

    优点:

  • 事务参与者可以基于事件驱动实现异步调用,吞吐高
  • 一阶段直接提交事务,无锁,性能好
  • 不用编写TCC中的三个阶段,实现简单

缺点:

  • 软状态持续时间不确定,时效性差
  • 没有锁,没有事务隔离,会有脏写

幂等处理;
比如事务失败多次尝试或者说多次调用方法得到的结果是一致的,这个只是一种说法,还有其他的地方可以用到这种思想
image.png

seata添加在nacos上的配置文件:

  1. # 数据存储方式,db代表数据库
  2. store.mode=db
  3. store.db.datasource=druid
  4. store.db.dbType=mysql
  5. store.db.driverClassName=com.mysql.jdbc.Driver
  6. store.db.url=jdbc:mysql://192.168.200.130:3306/seata?useUnicode=true&rewriteBatchedStatements=true
  7. store.db.user=root
  8. store.db.password=root
  9. store.db.minConn=5
  10. store.db.maxConn=30
  11. store.db.globalTable=global_table
  12. store.db.branchTable=branch_table
  13. store.db.queryLimit=100
  14. store.db.lockTable=lock_table
  15. store.db.maxWait=5000
  16. # 事务、日志等配置
  17. server.recovery.committingRetryPeriod=1000
  18. server.recovery.asynCommittingRetryPeriod=1000
  19. server.recovery.rollbackingRetryPeriod=1000
  20. server.recovery.timeoutRetryPeriod=1000
  21. server.maxCommitRetryTimeout=-1
  22. server.maxRollbackRetryTimeout=-1
  23. server.rollbackRetryTimeoutUnlockEnable=false
  24. server.undo.logSaveDays=7
  25. server.undo.logDeletePeriod=86400000
  26. # 客户端与服务端传输方式
  27. transport.serialization=seata
  28. transport.compressor=none
  29. # 关闭metrics功能,提高性能
  30. metrics.enabled=false
  31. metrics.registryType=compact
  32. metrics.exporterList=prometheus
  33. metrics.exporterPrometheusPort=9898
  1. 设置seata的配置文件
  2. registry {
  3. # tc服务的注册中心类,这里选择nacos,也可以是eurekazookeeper
  4. type = "nacos"
  5. nacos {
  6. # seata tc 服务注册到 nacos的服务名称,可以自定义 spring.application.name
  7. application = "seata-tc-server"
  8. serverAddr = "192.168.200.130:8848"
  9. group = "DEFAULT_GROUP"
  10. namespace = ""
  11. cluster = "SH"
  12. username = "nacos"
  13. password = "nacos"
  14. }
  15. }
  16. config {
  17. # 读取tc服务端的配置文件的方式,这里是从nacos配置中心读取,这样如果tc是集群,可以共享配置
  18. type = "nacos"
  19. # 配置nacos地址等信息
  20. nacos {
  21. serverAddr = "192.168.200.130:8848"
  22. namespace = ""
  23. group = "DEFAULT_GROUP"
  24. username = "nacos"
  25. password = "nacos"
  26. dataId = "seataServer.properties"
  27. }
  28. }