请选择 进入手机版 | 继续访问电脑版
JAVEN

分布式事务之TCC策略

所在版块: JavaEE技术 2017-09-27 15:30 [复制链接] 查看: 684|回复: 1
本帖最后由 javen 于 2017-9-27 15:36 编辑

分布式事务之TCC策略
随着业务的增长、用户不断增加,我们往往会将系统不同的业务单元进行拆分,比如订单管理、库存管理和物流管理,原本是一个项目中的一个业务逻辑,当用户量和业务复杂度上升,我们会将订单管理、库存管理等从原始系统中拆分成多个子系统。最后,系统架构就演变成了分布式的面向服务的架构(SOA),订单管理就成了面向服务架构中的一项服务。(演变过程如下图)
与此同时,也会带来一些问题,比如分布式的系统都部署在不同的服务器上,怎么保证事务的一致性就变成了一个非常复杂的问题。分布式事务的实现策略比较多,本文只是介绍TCC事务策略。
1、TCC
TCC是三个字母的缩写:TRY、CONFIRM、CANCEL。也就是事务的处理被划分成了3个过程:
1、Try:尝试执行业务。
  • 完成所有业务检查(一致性)
  • 预留必须业务资源(准隔离性)

2、Confirm:确认执行业务。
  • 真正执行业务
  • 不做任何业务检查
  • 只使用Try阶段预留的业务资源

3、Cancel:取消执行业务
  • 释放Try阶段预留的业务资源

如果要自己实现如上过程,还是比较复杂的。下面介绍一个实现TCC模式的框架
2、ByteTCC的基本使用
Byte TCC是一个基于TCC策略完成的分布式事务管理框架
2.1、前期准备
Byte TCC提供了dubbo和spring cloud两种集成方式,本篇主要介绍dubbo集成使用方式。整个架构初了dubbo,还是用到zookeeper作为dubbo的注册查找器,最后整合到Spring生态圈完成整个代码结构。使用到的技术如下:
a、mysql
b、hibernate
c、spring
d、dubbo
e、zookeeper
首先使用zookeeper+dubbo完成业务分解。
2.2、导入依赖2.2.1、bytetcc的依赖
  1. <dependency>
  2.     <groupId>org.bytesoft</groupId>
  3.     <artifactId>bytetcc-supports-dubbo</artifactId>
  4.     <version>0.4.0-rc1</version>
  5. </dependency>
复制代码


2.2.2、其他依赖(zookeeper等)<dependency>
  1. <groupId>com.alibaba</groupId>
  2.   <artifactId>dubbo</artifactId>
  3.   <version>2.5.5</version>
  4. </dependency>
  5. <!-- https://mvnrepository.com/artifact/com.101tec/zkclient -->
  6. <dependency>
  7.   <groupId>com.101tec</groupId>
  8.   <artifactId>zkclient</artifactId>
  9.   <version>0.10</version>
  10. </dependency>
复制代码

还有spring、hibernate、dbcp等等。
2.3、数据库准备
在mysql数据库中创建如下表信息:
  1. CREATE TABLE tb_account_one (
  2.   acct_id varchar(16),
  3.   amount double(10, 2), //账户资金
  4.   frozen double(10, 2), //冻结资金
  5.   PRIMARY KEY (acct_id)
  6. ) ENGINE=InnoDB;

  7. insert into tb_account_one (acct_id, amount, frozen) values('1001', 10000.00, 0.00);

  8. CREATE TABLE tb_account_two (
  9.   acct_id varchar(16),
  10.   amount double(10, 2), //账户资金
  11.   frozen double(10, 2),  //冻结资金
  12.   PRIMARY KEY (acct_id)
  13. ) ENGINE=InnoDB;

  14. insert into tb_account_two (acct_id, amount, frozen) values('2001', 10000.00, 0.00);
复制代码

分别是账户1和账户2,接下来的逻辑就是使用分布式完成从账户1表中转钱给账户2表。以上两个表可以分别创建在两台数据库服务器上。(为了方便测试,也可以先创建一个服务器上的两个db中)
2.4、Provider配置和代码
在provider方主要提供3个逻辑,分别是try、confirm、cancel。
2.4.1、try代码
  1. @Service("accountService")
  2. @Compensable(
  3.   interfaceClass = IAccountService.class
  4. , confirmableKey = "accountServiceConfirm"
  5. , cancellableKey = "accountServiceCancel"
  6. )
  7. public class AccountServiceImpl implements IAccountService {

  8.     @Resource(name = "jdbcTemplate")
  9.     private JdbcTemplate jdbcTemplate;

  10.     @Transactional
  11.     public void increaseAmount(String accountId, double amount) throws ServiceException {
  12.         this.jdbcTemplate.update("update tb_account set frozen = frozen + ? where acct_id = ?", amount, acctId);
  13.     }

  14. }
复制代码

分布式事务在try结算首先会执行此步骤,代码逻辑就将即将转账的资金先冻结,也就是修改冻结资金字段。这样避免在confirm之前,有人是用掉了需要转账的资金。
@Compensable注解是ByteTCC框架提供的注解,用来配置TCC各方服务的名称。
2.4.2、confirm代码
以下是转入provider方逻辑:

  1. @Service("accountServiceConfirm")
  2. public class AccountServiceConfirm implements IAccountService {

  3.     @Resource(name = "jdbcTemplate")
  4.     private JdbcTemplate jdbcTemplate;

  5.     @Transactional
  6.     public void increaseAmount(String accountId, double amount) throws ServiceException {
  7.         this.jdbcTemplate.update("update tb_account set amount = amount + ?, frozen = frozen - ? where acct_id = ?", amount, amount, acctId);
  8.     }

  9. }
复制代码

此步骤是执行confirm,根据SQL逻辑可以看出,账户资金增加amount值,冻结资金减少amount值,从而完成转账流程。下面是转出provider的代码逻辑:
  1. @Transactional(rollbackFor = ServiceException.class)
  2. public void decreaseAmount(String acctId, double amount) throws ServiceException {
  3.         int value = this.jdbcTemplate.update("update tb_account_one set frozen = frozen - ? where acct_id = ?", amount, acctId);
  4.     }
复制代码

注意:转入和转出是作为两个project分开写的,利用dubbo提供的分布式服务。
执行confirm之后,意味着整个事务完成。  如果在此期间出现异常,则执行cancel阶段(见2.4.3节)。
2.4.3、cancel代码
  1. @Service("accountServiceCancel")
  2. public class AccountServiceCancel implements IAccountService {

  3.     @Resource(name = "jdbcTemplate")
  4.     private JdbcTemplate jdbcTemplate;

  5.     @Transactional
  6.     public void increaseAmount(String accountId, double amount) throws ServiceException {
  7.         this.jdbcTemplate.update("update tb_account set frozen = frozen - ? where acct_id = ?", amount, acctId);
  8.     }

  9. }
复制代码

如果事务期间出现异常,则需要进行回滚。以上代码逻辑就是回滚的代码操作,从代码来看,其实就是将冻结的资金接触冻结。
2.5、consumer配置和代码
消费者的配置和provider的配置类似。只要是需要Byte TCC事务管理,就需要使用@Compensable注解。(具体详见文末提供的源码地址)。
2.6、配置文件说明
  1. <beans>
  2.     ...
  3.    
  4.     <import resource="classpath:bytetcc-supports-dubbo.xml" />
  5.     ...
  6.    
  7.     <dubbo:reference id="remoteAccountService" interface="com.bytesvc.service.IAccountService" group="org.bytesoft.bytetcc"
  8.         filter="compensable" loadbalance="compensable" cluster="failfast" retries="0" />

  9.     <!-- tcc型service可被远程consumer当做普通service调用, 此时: -->
  10.     <!-- 1、全局事务发起方为provider,即consumer不传播事务上下文. -->
  11.     <!-- 2、若consumer参与事务(无论是普通事务还是tcc事务),则其与provider端事务属于两个独立的事务,一致性无法保证. -->
  12.     <!-- 注意:除非consumer不参与事务(无论是普通事务还是tcc事务),否则应该调用tcc型远程service,而不是远程service -->
  13.     <dubbo:reference id="normalAccountService" interface="com.bytesvc.service.IAccountService" cluster="failfast"
  14.         retries="0" />
  15.         
  16.         ...
  17.         
  18. </beans>
复制代码

以上只是配置文件的部分配置(详细见文末源码链接)。
其中<import resource="classpath:bytetcc-supports-dubbo.xml" />此句非常重要,这是封装好的事务配置,我们导入此文件之后,就不需要重复添加自己的事务配置。之后,只需要在用到事务的地方加上@Transactional注解即可。
<dubbo:reference filter="compensable"  loadbalance="compensable"  cluster="failfast"/> 此句中的属性也需要额外关注和配置(写这篇文章时,还没有搞清楚为什么需要这些熟悉,后面研究清楚了,再补上说明)。
2.7、测试
写好了provider和consumer之后,就可以开始测试了。这里附上代码:ByteTCC使用案例下载
3、Byte TCC总结
bytetcc用了一次,发现有些不明白的地方还需要研究,比如:confirm都执行了,但是事务没有提交,目前还没有找到原因。
此篇只是作为一个记录方便后期查阅。




回复

使用道具 举报

smlqf7

该用户从未签到

发表于 2018-8-10 22:10:58 | 显示全部楼层
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

我的博客

QQ|Archiver|手机版|小黑屋|课堂笔记  

GMT+8, 2018-12-12 10:33 , Processed in 0.099451 second(s), 30 queries .

快速回复 返回列表