001-分布式事务详细分析

分布式事务

本地事务

单库处理的时候利用数据库的事务实现

Connection conn = ... //获取数据库连接
conn.setAutoCommit(false); //开启事务
try{
   //...执行增删改查sql
   conn.commit(); //提交事务
}catch (Exception e) {
  conn.rollback();//事务回滚
}finally{
   conn.close();//关闭链接
}

也就是说事务是基于链接实现的,想要保证多库(多链接)事务就需要引入分布式事务

分布式事务

两阶段提交协议(2PC)

角色

  1. 服务
  2. 多个数据库
  3. 事务管理器

阶段1:
数据库 给 事务管理器 预提交SQL 提交后把自身的本地事务改为 可以提交
阶段2:
根据阶段1 所有事务预提交的结果 判断是否可以确认提交,如果确认提交则返回 提交完成

Java实现

Java JDK 其实实现了一套对应的接口 XAConnection
mqsql 有对其进行实现

示例:

// 获得资源管理器操作接口实例 RM1
Connection conn1 = DriverManager.getConnection
         ("jdbc:mysql://localhost:3306/db_1", "root", "root");
XAConnection xaConn1 = new MysqlXAConnection(
        (com.mysql.jdbc.Connection) conn1, logXaCommands);
XAResource rm1 = xaConn1.getXAResource();

// 获得资源管理器操作接口实例 RM2
Connection conn2 = DriverManager.getConnection
        ("jdbc:mysql://localhost:3306/db_2", "root", "root");
XAConnection xaConn2 = new MysqlXAConnection(
        (com.mysql.jdbc.Connection) conn2, logXaCommands);
XAResource rm2 = xaConn2.getXAResource();

// AP请求TM执行一个分布式事务,TM生成全局事务id
byte[] gtrid = "g12345".getBytes();
int formatId = 1;
try {
    // ==============分别执行RM1和RM2上的事务分支====================
    // TM生成rm1上的事务分支id
    byte[] bqual1 = "b00001".getBytes();
    Xid xid1 = new MysqlXid(gtrid, bqual1, formatId);
    // 执行rm1上的事务分支
    rm1.start(xid1, XAResource.TMNOFLAGS);//One of TMNOFLAGS, TMJOIN, or TMRESUME.
    PreparedStatement ps1 = conn1.prepareStatement(
            "INSERT into order_tbl(user_id,commodity_code,count,money,status) VALUES (1001,2001,2,10,1)");
    ps1.execute();
    rm1.end(xid1, XAResource.TMSUCCESS);

    // TM生成rm2上的事务分支id
    byte[] bqual2 = "b00002".getBytes();
    Xid xid2 = new MysqlXid(gtrid, bqual2, formatId);
    // 执行rm2上的事务分支
    rm2.start(xid2, XAResource.TMNOFLAGS);
    PreparedStatement ps2 = conn2.prepareStatement(
            "update storage_tbl set count=count-2 where commodity_code=2001");
    ps2.execute();
    rm2.end(xid2, XAResource.TMSUCCESS);
    
    // ===================两阶段提交================================
    // phase1:询问所有的RM 准备提交事务分支
    int rm1_prepare = rm1.prepare(xid1);
    int rm2_prepare = rm2.prepare(xid2);
    // phase2:提交所有事务分支
    boolean onePhase = false;
    //TM判断有2个事务分支,所以不能优化为一阶段提交
    if (rm1_prepare == XAResource.XA_OK
            && rm2_prepare == XAResource.XA_OK) {
        //所有事务分支都prepare成功,提交所有事务分支
        rm1.commit(xid1, onePhase);
        rm2.commit(xid2, onePhase);
    } else {
        //如果有事务分支没有成功,则回滚
        rm1.rollback(xid1);
        rm2.rollback(xid2);
    }
} catch (XAException e) {
    // 如果出现异常,也要进行回滚
    e.printStackTrace();
}

这个就是 2PC 的简单实现
但是为什么没有见人这个用过呢?
其实就是这个简单实现有一些问题:

  1. 链接提交时同步的,性能有瓶颈
  2. 在 确认2个 预提交可以取人提交了
    rm1.commit(xid1, onePhase); 执行ok
    rm2.commit(xid2, onePhase); 执行异常
    此时 rm1 无法回滚了
  3. 事务管理器一旦出问题,整个服务就不可用了

因为存在很多问题,且代码比较繁琐,所以就出现了很多开源框架对 2PC 协议实现

seata-AT

简要概述

既然每一个本地事务只能管理一个链接
那么就提供一个 第三方的事务管理器 用来管理整个链路上的所有事务
做到一起成功一起失败
在这里插入图片描述
在这里插入图片描述
其中每一个本地事务对 Seata 来说都是一个 Branch Transaction
在这里插入图片描述

术语

Transaction Coordinator 事务协调器
Transaction Manager 事务管理器
Resource Manager 资源管理器

分析

业务起始的服务就作为 TM 事务管理器
只有 TM 操作都成功了,事务才算成功
只要 TM 出现异常,所有 RM 提交的事务都需要回滚

这就要求了 RM 出现异常需要向上抛出,TM 一定需要感知到

过程分析

分支事务提交

TM 向 TC 申请全局事务ID

RM 在执行 SQL 时

  1. 记录变更的字段修改前的值
  2. 执行SQL
  3. 记录变更的字段修改后的值
  4. 记录入库(RM自己的库,并关联全局事务ID : undolog表)
  5. 通知 TC 我要提交了
  6. 提交事务
  7. 提交状态上报 (可选 : 因为 TM 知道最终事务是否执行成功)
    在这里插入图片描述
全局事务提交

RM 定时检查 TC 是否执行成功
成功则清除本次 undolog 记录
在这里插入图片描述

全局事务回滚

TM 告知 TC 全局事务回滚
TC 通知 RM 全局事务ID 回滚
读取 undolog 记录的后值 和 数据库数据比较一致则回滚 字段值为 记录的前值
不一致则进入异常逻辑(默认无限重试 这里 seata 处理不了了 必须人工介入 且 此TM 不再接受新的请求)
在这里插入图片描述

TCC

AT 模式虽然释放了本地事务的链接提高了性能
但是为了避免在事务执行过程中对修改行数据的修改
在 TC 还维护了一个锁 导致性能还是有影响的
另外就是 AT 模式只适用于关系型数据库方案

所以就出现了一套能兼容非关系型数据方案且性能较好的分布式事务方案 TCC

术语

TCC 也是基于 2PC
try - confirm - cancel

简要分析

在这里插入图片描述
TCC 怎么做到 支持非关系型数据库呢?
因为他根本就不是自动的 如果使用 TCC 模式
事务提交的三个阶段都是 开发者自己实现的

Prepare

一般这里做的是校验能否执行业务
并且把资源保存起来
如果库存扣减,在这一步就可以把库存移动到冻结库存中并建立流水数据
也就是:

  1. 库存 - 扣减数量
  2. 冻结库存 + 扣减数量

Commit

这里就是可以提交事务了
处理各种状态
冻结库存 - 扣减数量

Cancel

这个是 TC 发起的,直接调用配置的自定义回滚方法
回滚

  1. 库存 + 扣减数量
  2. 冻结库存 - 扣减数量

可能遇到的问题

因为网络问题可能会重复请求 Commit : 需要幂等处理
如果 try 还没有执行那么 cancel 就也不需要执行
如果 cancel 先执行那么 try 就不需要执行

解决:
seata 新增了一记录表记录的状态

  1. tried:1
  2. committed:2
  3. rollbacked:3
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值