IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> 分布式事务之TCC -> 正文阅读

[大数据]分布式事务之TCC

什么是TCC事务

TCC是Try、Confirm、Cancel三个词语的缩写,TCC要求每个分支事务实现三个操作:
预处理Try、确认Confirm、撤销Cancel。
Try操作做业务检查及资源预留,Confirm做业务确认操作,Cancel实现一个与Try相反的操作即回滚操作。TM首先发起所有的分支事务的try操作,任何一个分支事务的try操作执行失败,

TM将会发起所有分支事务的Cancel操作,若try操作全部成功,TM将会发起所有分支事务的Confirm操作,其中Confirm/Cancel操作若执行失败,TM会进行重试

数据库

# 创建hmily数据库,用于存储hmily框架记录的数据。
CREATE DATABASE `hmily` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';

CREATE DATABASE `bank1` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';
use `bank1`;
DROP TABLE IF EXISTS `account_info`;
CREATE TABLE `account_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`account_name` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '户
主姓名',
`account_no` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '银行
卡号',
`account_password` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT
'帐户密码',
`account_balance` double NULL DEFAULT NULL COMMENT '帐户余额',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT =
Dynamic;
INSERT INTO `account_info` VALUES (2, '张三的账户', '1', '', 10000);

CREATE DATABASE `bank2` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';
use `bank2`;

CREATE TABLE `account_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`account_name` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '户
主姓名',
`account_no` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '银行
卡号',
`account_password` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT
'帐户密码',
`account_balance` double NULL DEFAULT NULL COMMENT '帐户余额',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT =
Dynamic;
INSERT INTO `account_info` VALUES (3, '李四的账户', '2', NULL, 0);

每个数据库都创建try、confirm、cancel三张日志表:

CREATE TABLE `local_try_log` (
`tx_no` varchar(64) NOT NULL COMMENT '事务id',
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`tx_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
CREATE TABLE `local_confirm_log` (
`tx_no` varchar(64) NOT NULL COMMENT '事务id',
`create_time` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
CREATE TABLE `local_cancel_log` (
`tx_no` varchar(64) NOT NULL COMMENT '事务id',
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`tx_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

示例代码

   @Autowired

    AccountInfoDao accountInfoDao;

    @Autowired
    Bank2Client bank2Client;

    // 账户扣款,就是tcc的try方法

    /**
     * try幂等校验
     * try悬挂处理
     * 检查余额是够扣减金额
     * 扣减金额
     *
     * @param accountNo
     * @param amount
     */
    @Transactional(rollbackFor = Exception.class)
    //只要标记@HmilyTCC就是try方法,在注解中指定confirm、cancel两个方法的名字
    @HmilyTCC(confirmMethod = "confirm", cancelMethod = "rollback")
    public void updateAccountBalance(AccountChangeEvent accountChangeEvent) {
        //获取全局事务id
        String transId = accountChangeEvent.getTxNo();

        log.info("bank1 try begin 开始执行...xid:{}", transId);

        //幂等判断 判断local_try_log表中是否有try日志记录,如果有则不再执行

        if (accountInfoDao.isExistTry(transId) > 0) {
            log.info("bank1 try 已经执行,无需重复执行,xid:{}", transId);
            return;
        }

        //try悬挂处理,如果cancel、confirm有一个已经执行了,try不再执行
        if (accountInfoDao.isExistConfirm(transId) > 0 || accountInfoDao.isExistCancel(transId) > 0) {
            log.info("bank1 try悬挂处理  cancel或confirm已经执行,不允许执行try,xid:{}", transId);
            return;
        }

        //扣减金额
        if (accountInfoDao.subtractAccountBalance(accountChangeEvent) <= 0) {
            //扣减失败
            log.info("bank1 try 扣减金额失败,xid:{}", transId);
            return;
        }
        //插入try执行记录,用于幂等判断
        accountInfoDao.addTry(transId);
        try {
            //业务耗时
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //人为异常
        if (accountChangeEvent.getAmount() == -1) throw new RuntimeException();
        //远程调用李四,转账
        bank2Client.transfer(accountChangeEvent);

        //人为异常
        if (accountChangeEvent.getAmount() == -2) throw new RuntimeException();
        log.info("bank1 try end 结束执行...xid:{}", transId);
    }

    //confirm方法
    @Transactional
    public void confirm(AccountChangeEvent accountChangeEvent) {
        //获取全局事务id
        String transId = accountChangeEvent.getTxNo();
        log.info("bank1 confirm begin 开始执行...xid:{},accountNo:{},amount:{}", transId, accountChangeEvent.getAccountNo(), accountChangeEvent.getAmount());
        if (accountInfoDao.isExistConfirm(transId) > 0) {
            log.info("bank1 confirm 已经执行,无需重复执行...xid:{}", transId);
            return;
        }
        //添加 confirm记录
        accountInfoDao.addConfirm(transId);
        log.info("bank1 confirm end 结束执行...xid:{}", transId);
    }


    /**
     * cancel方法
     * cancel幂等校验
     * cancel空回滚处理
     * 增加可用余额
     *
     * @param accountNo
     * @param amount
     */
    @Transactional
    public void rollback(AccountChangeEvent accountChangeEvent) {
        //获取全局事务id
        String transId = accountChangeEvent.getTxNo();
        log.info("bank1 cancel begin 开始执行...xid:{}", transId);
        //	cancel幂等校验
        if (accountInfoDao.isExistCancel(transId) > 0) {
            log.info("bank1 cancel 已经执行,无需重复执行,xid:{}", transId);
            return;
        }
        //cancel空回滚处理,如果try没有执行,cancel不允许执行
        if (accountInfoDao.isExistTry(transId) <= 0) {
            log.info("bank1 空回滚处理,try没有执行,不允许cancel执行,xid:{}", transId);
            return;
        }
        //	增加可用余额
        accountInfoDao.addAccountBalance(accountChangeEvent);
        //插入一条cancel的执行记录
        accountInfoDao.addCancel(transId);
        log.info("bank1 cancel end 结束执行...xid:{}", transId);

    }

解决方案分析

生成还款计划是一个执行时长较长的业务,不建议阻塞主业务流程,此业务对一致性要求较低。
根据上述需求进行解决方案分析:
1、采用Seata实现2PC
Seata在事务执行过程会进行数据库资源锁定,由于事务执行时长较长会将资源锁定较长时间,所以不适用。
2、采用Hmily实现TCC
本需求对业务一致性要求较低,因为生成还款计划的时长较长,所以不要求交易中心修改标的状态为“还款中”就立
即生成还款计划 ,所以本方案不适用。
3、基于MQ的可靠消息一致性
满标审批通过后由交易中心修改标的状态为“还款中”并且向还款服务发送消息,还款服务接收到消息开始生成还款
计划,基本于MQ的可靠消息一致性方案适用此场景 。
4、最大努力通知方案
满标审批通过后由交易中心向还款服务发送通知要求生成还款计划,还款服务并且对外提供还款计划生成结果校对
接口供其它服务查询,最大努力 通知方案也适用本场景 。

  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2022-11-05 00:34:29  更:2022-11-05 00:36:49 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年4日历 -2025/4/23 10:25:16-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码