分布式事务:解决方案之2PC实战

说完《分布式事务:解决方案之2PC理论》,我们现在就要在理论的基础上实践一把。

1. 业务说明

下面我们通过Seata中间件实现分布式事务来模拟两个账户的转账交易过程。交易过程是:张三给李四转账指定金额。

2. 开发环境

  • 数据库:MySQL-5.6
  • 微服务框架:spring-boot-2.2.2.RELEASEspring-cloud-Hoxton.SR1spring-cloud-alibaba-2.1.0.RELEASE
  • Seata服务端(TC):1.1.0。(下载地址:https://github.com/seata/seata/releases)

2.1 启动程序

源码地址:https://gitee.com/anbang713/distributed-transaction-study

启动程序之前,我们需要使用源码工程中对应的sql脚本创建相对应的数据库和表,以及插入测试数据。

(1)首先我们要启动registry-server服务注册中心;

(2)然后进入Seata服务端的bin文件夹,双击seata-server.bat文件启动;

(3)分别启动seata-demo-bank1seata-demo-bank2微服务。

3. 技术架构

分布式事务:解决方案之2PC实战_第1张图片

交互流程如下:

(1)请求Bank1进行转账,传入转账金额。

http://localhost:9011/bank1/transfer?amount=xxx

(2)Bank1减少转账金额,通过feign调用Bank2,传入转账金额。

4. Seata执行流程

4.1 正常提交流程

分布式事务:解决方案之2PC实战_第2张图片

4.2 回滚流程

回滚流程省略前的RM注册过程。

分布式事务:解决方案之2PC实战_第3张图片

要点说明:

(1)每个RM使用DataSourceProxy连接数据库,其目的是使用ConnectionProxy,使用数据源和数据连接代理的目的就是在第一阶段将undo_log和业务数据放在一个本地事务提交,这样就保存了只要有业务操作就一定有
undo_log

(2)在第一阶段undo_log中存放了数据修改前和修改后的值,为事务回滚作好准备,所以第一阶段完成就已经将分支事务提交,也就释放了锁资源。

(3)TM开启全局事务开始,将XID全局事务id放在事务上下文中,通过feign调用也将XID传入下游分支事务,每个分支事务将自己的Branch ID分支事务ID与XID关联。

(4)第二阶段全局事务提交,TC会通知各个分支参与者提交分支事务,在第一阶段就已经提交了分支事务,这里各个参与者只需要删除undo_log即可,并且可以异步执行,第二阶段很快可以完成。

(5)第二阶段全局事务回滚,TC会通知各个分支参与者回滚分支事务,通过 XID 和 Branch ID 找到相应的回滚日
志,通过回滚日志生成反向的 SQL 并执行,以完成分支事务回滚到之前的状态,如果回滚失败则会重试回滚操作。

5. 核心代码

5.1 bank1的业务层实现

@GlobalTransactional注解标注在全局事务发起的Service实现方法上,开启全局事务:GlobalTransactionalInterceptor会拦截@GlobalTransactional注解的方法,生成全局事务ID(XID),XID会在整个分布式事务中传递。在远程调用时,spring-cloud-alibaba-seata会拦截Feign调用将XID传递到下游服务。

@Transactional
@GlobalTransactional//开启全局事务
@Override
public void updateAccountBalance(String accountNo, Double amount) {
    log.info("bank1 service begin,XID:{}", RootContext.getXID());
    //扣减张三的金额
    accountInfoDao.updateAccountBalance(accountNo, amount * -1);
    //调用李四微服务,转账
    String transfer = bank2Client.transfer(amount);
    if ("fallback".equals(transfer)) {
        throw new RuntimeException("调用李四微服务异常");
    }
    if (amount == 2) {
        throw new RuntimeException("bank1 make exception..");
    }
}

5.2 bank2的业务层实现

@Transactional
@Override
public void updateAccountBalance(String accountNo, Double amount) {
    //李四增加金额
    accountInfoDao.updateAccountBalance(accountNo,amount);
    if(amount == 3) {
        //人为制造异常
        throw new RuntimeException("bank2 make exception..");
    }
}

6. 测试场景

(1)张三向李四转账成功

http://localhost:9011/bank1/transfer?amount=1

(2)李四事务失败,张三事务回滚成功

http://localhost:9011/bank1/transfer?amount=3

(3)张三事务失败,李四事务回滚成功

http://localhost:9011/bank1/transfer?amount=2
——End——
更多精彩分享,可扫码关注微信公众号哦。

在这里插入图片描述

你可能感兴趣的:(分布式事务,分布式事务,2PC,Seata)