当前位置: 首页 > news >正文

分布式事务解决方案——TCC

TCC是Try、Confirm、Cancel三个词语的缩写,TCC要求每个分支事务实现三个操作:预处理Try、确认Confirm、撤销Cancel。

1、Try 阶段是做业务检查(一致性)及资源预留(隔离),此阶段仅是一个初步操作,它和后续的Confirm一起才能真正构成一个完整的业务逻辑。

2、Confirm 阶段是做确认提交,Try阶段所有分支事务执行成功后开始执行Confirm。通常情况下,TCC认为Confirm阶段是不会出错的,若Confirm阶段真的出错了,则重试或人工处理。

3、Cancel 阶段是在业务执行错误需要回滚的状态下执行分支事务的业务取消,预留资源释放。通常情况下,TCC认为Cancel阶段也是一定成功的,若Cancel阶段真的出错了,则重试或人工处理。

TM(事务管理器)首先发起所有的分支事务的try操作,任何一个分支事务的try操作执行失败,TM将会发起所有分支事务的Cancel操作;若try操作全部成功,TM将会发起所有分支事务的Confirm操作,其中Confirm/Cancel操作若执行失败,TM会进行重试。

所有try都成功

有一个try失败

特别提醒

  1. 所有分支事务的try阶段执行成功,则会执行confirm阶段。TCC认为confirm阶段一定会执行成功,如果confirm执行失败,则会重试或者人工处理错误。

  1. 任何一个分支事务的try阶段执行失败,则会执行cancel阶段。TCC认为cancel阶段一定会执行成功,如果cancel执行失败,则会重试或者人工处理错误。

TCC需要注意三种异常处理分别是:空回滚、幂等、悬挂。

空回滚

事务管理器调用服务的try操作,可能会出现因为丢包而导致的网络超时,导致应用的try阶段没执行,事务管理器认为执行try超时,会触发cancel操作。这就导致cancel比try先执行。

悬挂

1、事务协调器在调用 TCC 服务的一阶段 Try 操作时,可能会出现因网络拥堵而导致的超时。

2、此时事务管理器会触发二阶段回滚,调用 TCC 服务的 Cancel 操作,Cancel 执行正常。

3、在此之后,拥堵在网络上的一阶段 Try 数据包被 TCC 服务收到,出现了二阶段 Cancel 请求比一阶段 Try 请求先执行的情况。

4、此 TCC 服务在执行晚到的 Try 之后,将永远不会再收到二阶段的 Confirm 或者 Cancel ,造成 TCC 服务悬挂。

幂等

try、confirm、cancel都会被重复调用,需要做幂等处理。

处理空回滚、悬挂、幂等

可以使用日志表,来解决 空回滚、悬挂、幂等。

新建3张表

try阶段日志表local_try_log

字段

注释

tx_no

全局事务id

create_time

创建时间

confirm阶段日志表local_confirm_log

字段

注释

tx_no

全局事务id

create_time

创建时间

cancel阶段日志表local_cancel_log

字段

注释

tx_no

全局事务id

create_time

创建时间

例子

从银行1转账10元给银行2,使用TCC方案

方案1

银行1

try:幂等校验,查找try日志(全局事务id是主键)悬挂处理,查找confirm、cancel日志(全局事务id是主键)检查余额是否够10元锁定10元插入try日志(全局事务id是主键)
confirm:幂等校验,查找confirm日志(全局事务id是主键)扣减10元删除锁定10元插入confirm日志(全局事务id是主键)
cancel:cancel幂等校验,查找cancel日志(全局事务id是主键)空回滚处理,查找try日志(全局事务id是主键)增加余额10元(回滚)删除锁定10元插入cancel日志(全局事务id是主键)

银行2

try:幂等校验,查找try日志(全局事务id是主键)悬挂处理,查找confirm、cancel日志(全局事务id是主键)插入待激活10元插入try日志(全局事务id是主键)
confirm:幂等校验,查找confirm日志(全局事务id是主键)正式增加30元删除待激活10元插入confirm日志(全局事务id是主键)
cancel:空

由于业务很简单,上面的流程还可以取消锁定,解锁的操作,直接在银行1的try中扣减10元,流程如下。

方案2

银行1

try:幂等校验,查找try日志(全局事务id是主键)悬挂处理,查找confirm、cancel日志(全局事务id是主键)检查余额是否够10元扣减10元插入try日志(全局事务id是主键)
confirm:空
cancel:cancel幂等校验,查找cancel日志(全局事务id是主键)空回滚处理,查找try日志(全局事务id是主键)增加余额10元(回滚)插入cancel日志(全局事务id是主键)

银行2

try:空
confirm:幂等校验,查找confirm日志(全局事务id是主键)正式增加30元插入confirm日志(全局事务id是主键)
cancel:空

Hmily实现方案2的代码

服务1

@Service
@Slf4j
public class Bank1ServiceImpl implements Bank1Service {@AutowiredAccountInfoDao accountInfoDao;@AutowiredHmilyLogDao hmilyLogDao;@AutowiredBank2Client bank2Client;@Override@Transactional(rollbackFor = Exception.class)@Hmily(confirmMethod = "confirm", cancelMethod = "cancel")public void updateAccountBalance(String msg, Double amount) {// 全局事务idString transId = HmilyTransactionContextLocal.getInstance().get().getTransId();log.info("bank1 try 开始,transId={}", transId);// 幂等判断int existTry = hmilyLogDao.isExistTry(transId);// 通故全局事务id查找到try日志,表明已经只执行过tryif (existTry > 0) {log.info("已经执行过try,无需重复执行try,transId={}", transId);return;}// 悬挂处理int existConfirm = hmilyLogDao.isExistConfirm(transId);int existCancel = hmilyLogDao.isExistCancel(transId);// 通故全局事务id查找到confirm、cancel日志,表明已经只执行过confirm、cancelif (existConfirm > 0 || existCancel > 0) {log.info("confirm,cancel有一个已经执行过,try不能再次执行,transId={}", transId);return;}// 制造空回滚if (StringUtils.equals("制造空回滚", msg)) {throw new RuntimeException("try方法没修改数据库就抛出异常,cancel方法会执行,形成空回滚,transId=" + transId);}// blank1减金额accountInfoDao.subtractAccountBalance("1", amount);// 添加try日志记录,try日志和扣减余额在同一个本地事务中,要么都成功,要么都失败// 日志的组件id必须是全局事务id,如果同一个事物重复调用try,到这一步会报主键重复hmilyLogDao.addTry(transId);// 远程调用Boolean result = bank2Client.transfer(msg, amount);if (!result) {throw new RuntimeException("调用bank2失败");}// bank1调用bank2成功后,发生异常,模拟回滚if (StringUtils.equals("bank1调用bank2成功后,发生异常,模拟回滚", msg)) {throw new RuntimeException("bank1调用bank2成功后,发生异常,模拟回滚,transId=" + transId);}}public void confirm(String accountNo, Double amount) {String transId = HmilyTransactionContextLocal.getInstance().get().getTransId();log.info("bank1 confirm 开始执行,transId={}", transId);}@Transactional(rollbackFor = Exception.class)public void cancel(String msg, Double amount) {// 全局事务idString transId = HmilyTransactionContextLocal.getInstance().get().getTransId();log.info("bank1 cancel 开始执行,transId={}", transId);// 幂等判断int existCancel = hmilyLogDao.isExistCancel(transId);if (existCancel > 0) {log.info("cancel已经执行过,无需重复执行,transId={}", transId);return;}// 处理空回滚int existTry = hmilyLogDao.isExistTry(transId);if (existTry == 0) {log.info("try未执行过,不能执行cancel,transId={}", transId);return;}// bank1回滚,加钱accountInfoDao.addAccountBalance(msg, amount);// 添加日志hmilyLogDao.addCancel(transId);}}
@Service
@Slf4j
public class Bank2ServiceImpl implements Bank2Service {@AutowiredAccountInfoDao accountInfoDao;@AutowiredHmilyLogDao hmilyLogDao;@Override@Hmily(confirmMethod = "confirm", cancelMethod = "cancel")public void updateAccountBalance(String msg, Double amount) {String transId = HmilyTransactionContextLocal.getInstance().get().getTransId();log.info("bank2 try 开始执行,transId:{}",transId);}@Transactional(rollbackFor = Exception.class)public void confirm(String msg, Double amount) {// 全局事务idString transId = HmilyTransactionContextLocal.getInstance().get().getTransId();log.info("bank2 confirm 开始执行,transId:{}",transId);int existConfirm = hmilyLogDao.isExistConfirm(transId);if (existConfirm > 0) {log.info("bank2 confirm 已经执行过,无需再次执行,transId", transId);return;}// bank2加钱accountInfoDao.addAccountBalance("2", amount);// 添加confirm日志hmilyLogDao.addConfirm(transId);// bank2 confirm,抛出异常,会重试if (StringUtils.equals("confirm抛出异常会重试", msg)) {throw new RuntimeException("confirm抛出异常会重试,transId=" + transId);}}public void cancel(String msg, Double amount) {String transId = HmilyTransactionContextLocal.getInstance().get().getTransId();log.info("bank2 cancel 开始执行,transId:{}",transId);}}

http://www.lryc.cn/news/6863.html

相关文章:

  • ITSS认证分为几个级别,哪个级别最高
  • ZigBee案例笔记 - USART
  • java | 基于Redis的分布式锁实现①
  • 十六、基于FPGA的CRC校验设计实现
  • 2022爱分析 · DataOps厂商全景报告 | 爱分析报告
  • 京东前端react面试题及答案
  • TongWeb8数据源相关问题
  • 关于最近大热的AI,你怎么看?
  • 25.架构和软件产品线
  • Seata-server 源码学习(一)
  • 2023新华为OD机试题 - 斗地主(JavaScript)
  • 素数相关(结合回文数,合数)线性筛素数(欧拉筛法)Euler【算法模板笔记】
  • 1.7配置OSPF手动汇总
  • 多线程下载工具axel的安装和使用
  • 大数据专业职业前景如何
  • 拉格朗日乘数法在原材料选择问题上的具体应用
  • 零信任-腾讯零信任iOA介绍(4)
  • 标准的maven依赖包应该包含哪些东西?
  • 网络安全-Nmap
  • 【物联网】mqtt初体验
  • 2023年阿里云活动有哪些实例规格的云服务器?如何选择这些实例规格
  • 深入理解 Handler(java 层 + native 层)
  • 初步认识操作系统(Operator System)
  • Android—HTTPS部署自签名证书
  • java基于springboot+vue微信小程序的学生健康管理
  • 金三银四丨黑蛋老师带你剖析-漏洞岗
  • pinia实战 购物车(自定义插件实现pinia持久化)
  • idea使用本地代码远程调试线上运行代码---linux环境
  • Java 基础面试题——集合
  • 编程思想、方法论和架构模式的应用