分布式事务解决方案一:TCC(补偿事务)

1、TCC事务

TCC的核心思想是:针对每一个操作都需要注册一个和其相对应的确认和补偿的操作,他分为三个阶段Try、Confirm和Cancel

  • Try 阶段主要是对业务系统做检测及资源预留
  • Confirm 阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行Confirm阶段时,默认Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。若Confirm阶段真的出错了,需引入重试机制或人工处理。
  • Cancel阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。
    在这里插入图片描述
    我们利用事务里最常见的案例来向大家解释:

A转账30元给B,A账户和B账户在不同银行(服务),当前余额都为100元

在这里插入图片描述
使用TTC事务,我们需要把之前实现的转账的代码拆分成三块,套到try-confirm-cancel中,由事务管理器(协调管理)推进AB两个try分别执行,在这个过程中,事务管理器会对AB进行监控,一旦任何一方出现了问题,就推进对方执行cancel;如果双方都没有异常,就推进AB执行confirm。如果在执行confirm或cancel过程中出现问题,就引入重试机制或由人工处理。

由此可见TCC解决分布式事务的缺点非常的明显:1、代码侵入性很强,改造成本很高;2、实现难度也不小,回滚策略实现并不简单。

2、Hmily

Hmily是一个高性能分布式事务tcc开源框架。基于java语言来开发(JDK1.8),支持dubbo,springcloud,motan等rpc框架进行分布式事务。
https://dromara.org/website/zh-cn/docs/hmily/index.html
它目前支持以下特性:

  • 支持嵌套事务(Nested transaction support)。
  • 采用disruptor框架进行事务日志的异步读写,与RPC框架的性能毫无差别。
  • 支持SpringBoot-starter 项目启动,使用简单。
  • RPC框架支持 : dubbo,motan,springcloud。
  • 本地事务存储支持 : redis,mongodb,zookeeper,file,mysql。
  • 事务日志序列化支持 :java,hessian,kryo,protostuff。
  • 采用Aspect AOP 切面思想与Spring无缝集成,天然支持集群。
  • RPC事务恢复,超时异常恢复等。

3、牛刀小试

环境要求

​ 数据库:MySQL 5.7.25+
​ JDK: jdk1.8+
​ 微服务:spring-boot-2.1.3、spring-cloud-Greenwich.RELEASE
​ hmily:hmily-springcloud.2.0.4-RELEASE

创建数据库
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 (1, '张三', '1', '', 10000);


CREATE DATABASE `bank2` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';
USE bank2;
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, '李四', '2', NULL, 0);
功能实现

(1)application.yml配置(只显示hmily部分)

org:
  dromara:
    hmily :
      serializer : kryo #序列化工具
      retryMax : 30 #最大重试次数
      repositorySupport : db #持久化方式
      started: true #事务发起方
      hmilyDbConfig :
        driverClassName  : com.mysql.jdbc.Driver
        url :  jdbc:mysql://localhost:3306/bank1?useUnicode=true
        username : root
        password : root

(2)Hmily配置类

@Configuration
@EnableAspectJAutoProxy(proxyTargetClass=true)
public class DatabaseConfiguration {

    @Autowired
    private Environment env;

    @Bean
    public HmilyTransactionBootstrap hmilyTransactionBootstrap(HmilyInitService hmilyInitService){
        HmilyTransactionBootstrap hmilyTransactionBootstrap = new HmilyTransactionBootstrap(hmilyInitService);
        hmilyTransactionBootstrap.setSerializer(env.getProperty("org.dromara.hmily.serializer"));
        hmilyTransactionBootstrap.setRetryMax(Integer.parseInt(env.getProperty("org.dromara.hmily.retryMax")));
        hmilyTransactionBootstrap.setRepositorySupport(env.getProperty("org.dromara.hmily.repositorySupport"));
        hmilyTransactionBootstrap.setStarted(Boolean.parseBoolean(env.getProperty("org.dromara.hmily.started")));
        HmilyDbConfig hmilyDbConfig = new HmilyDbConfig();
        hmilyDbConfig.setDriverClassName(env.getProperty("org.dromara.hmily.hmilyDbConfig.driverClassName"));
        hmilyDbConfig.setUrl(env.getProperty("org.dromara.hmily.hmilyDbConfig.url"));
        hmilyDbConfig.setUsername(env.getProperty("org.dromara.hmily.hmilyDbConfig.username"));
        hmilyDbConfig.setPassword(env.getProperty("org.dromara.hmily.hmilyDbConfig.password"));
        hmilyTransactionBootstrap.setHmilyDbConfig(hmilyDbConfig);
        return hmilyTransactionBootstrap;
    }
}

(3)feign代理

@FeignClient(value = "hmily-demo-bank2")
public interface Bank2Client {
    @GetMapping("/bank2/transfer")
    @Hmily
    Boolean transfer(@RequestParam("amount") Double amount);
}

(4)转账业务

@Service
public class AccountInfoTccImpl implements AccountInfoTcc {

    @Autowired
    private AccountInfoDao accountInfoDao;

    @Autowired
    private Bank2Client bank2Client;

    @Override
    @Hmily(confirmMethod = "commit", cancelMethod = "rollback")
    public void prepare( String accountNo, double amount) {
        System.out.println("...Bank1 Service  prepare..." );
        if(!bank2Client.transfer(amount)){
            throw new RuntimeException("bank2 exception");
        }
    }

    @Override
    public void commit( String accountNo, double amount) {
        System.out.println("...Bank1 Service commit..." );
    }

    @Override
    public void rollback( String accountNo, double amount) {
        accountInfoDao.updateAccountBalance(accountNo ,amount );
        System.out.println("...Bank1 Service rollback..." );
    }
}

注意:Try、Confirm、cancel的方法参数必须保持一致。

(5)启动类

@SpringBootApplication(exclude = MongoAutoConfiguration.class)
@EnableDiscoveryClient
@EnableFeignClients(basePackages = {"cn.itxw.hmilydemo.bank1.feignClient"})
@ComponentScan({"cn.itxw.hmilydemo.bank1","org.dromara.hmily"})
public class Bank1HmilyServer {
   public static void main(String[] args) {
      SpringApplication.run(Bank1HmilyServer.class, args);
   }
}
工程2

(1)application.yml配置(只显示hmily部分)

org:
  dromara:
    hmily :
      serializer : kryo #序列化工具
      retryMax : 30 #最大重试次数
      repositorySupport : db #持久化方式
      started: false #事务参与方
      hmilyDbConfig :
        driverClassName  : com.mysql.jdbc.Driver
        url :  jdbc:mysql://localhost:3306/bank2?useUnicode=true
        username : root
        password : root

(2)Hmily配置类,和hmily-demo-bank1一样

(3)转账业务

@Service
public class AccountInfoServiceImpl implements AccountInfoService {
   @Autowired
   private AccountInfoDao accountInfoDao;

   @Override
   @Hmily(confirmMethod = "confirmMethod", cancelMethod = "cancelMethod")
   public Boolean updateAccountBalance(String accountNo, Double amount) {
      System.out.println("...Bank2 Service Begin ...");
      try{
         accountInfoDao.updateAccountBalance(accountNo ,amount);
      }catch(Exception e){
         e.printStackTrace();
         throw new RuntimeException( e.getMessage() );
      }
      return true;
   }
   public Boolean confirmMethod(String accountNo, Double amount) {
      System.out.println("...Bank2 Service commit..." );
      return true;
   }

   public Boolean cancelMethod(String accountNo, Double amount) {
      accountInfoDao.updateAccountBalance(accountNo ,amount * -1);
      System.out.println("...Bank2 Service rollback..." );
      return true;
   }
}

注意:Try、Confirm、cancel的方法参数必须保持一致。

(4)启动类

@SpringBootApplication(exclude = MongoAutoConfiguration.class)
@EnableDiscoveryClient
@ComponentScan({"cn.itxw.hmilydemo.bank2","org.dromara.hmily"})
public class Bank2HmilyServer {
   public static void main(String[] args) {
      SpringApplication.run(Bank2HmilyServer.class, args);
   }
}
  • 4
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zyyn_未来可期

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值