Skip to content

User Guide 0.5.x

liuyangming edited this page Sep 17, 2019 · 8 revisions

一、使用约束

1.1、共同约束

  1. 针对一个特定的可补偿型服务接口,业务系统提供的Try、Confirm、Cancel三个实现类,其Try实现类必须定义@Compensable注解,而Confirm、Cancel实现类则不能定义Compensable注解;
  2. 可补偿型服务的Try/Confirm/Cancel实现类/实现方法必须定义Transactional注解,且propagation必须是Required, RequiresNew, Mandatory中的一种(即业务代码必须参与事务,从0.3.0开始强制要求);
  3. 业务系统尽量不要使用随机端口;
  4. 在每个参与tcc事务的数据库中创建bytejta表(ddl见bytetcc-supports.jar/bytetcc.sql);
  5. JDK版本:8.0及以上版本;

1.2、使用Spring Cloud的约束

  1. 服务提供方Controller必须添加@Compensable注解;
  2. 不允许对Feign/Ribbon/RestTemplate等HTTP请求自行进行封装,但允许拦截;
  3. 如果需要定制instanceId, 格式必须为${ip}:${自行指定}:${port};
  4. 0.5.x版本仅支持Spring Boot 2.x、Spring Cloud 2.x版本;

1.3、使用Dubbo的约束

  1. 必须且仅可指定一个<dubbo:application name="..." />元素,其name不能为空,且必须唯一;
  2. 必须且仅可指定一个<dubbo:protocol port="..." />元素,其port不能为空,也不能为-1;
  3. 定义dubbo服务提供者时(<dubbo:service />):a、filter必须为bytetcc;b、cluster必须为failfast;c、retries必须-1;d、group必须为x-bytetcc;
  4. 定义dubbo服务消费者时(<dubbo:reference />):a、filter必须为bytetcc;b、cluster必须为failfast;c、retries必须为-1;d、group必须为x-bytetcc;
  5. 通过url来配置<dubbo:service />时(或自行实现loadbalance机制选择服务实例时),url的ip、port必须是dubbo发现机制发现的可选地址中的一个,即不允许指定dubbo发现机制可发现的服务实例之外的服务提供者;

二、配置ByteTCC

2.1、引入ByteTCC配置

2.1.1、Spring Cloud引入ByteTCC配置
@Import(SpringCloudConfiguration.class)
2.1.2、Dubbo引入ByteTCC配置
@Import(DubboSupportConfiguration.class)
2.1.2、Spring Boot引入ByteTCC配置
@Import(SpringBootConfiguration.class)

2.2、配置扫描的应用包名

2.2.1、Spring Cloud配置扫描的应用包名
@SpringBootApplication(scanBasePackages = "com.yourcompany.service...")
2.2.2、Dubbo配置扫描的应用包名
@DubboComponentScan(basePackages = { "com.bytesvc.service.impl", "com.bytesvc.service.confirm", "com.bytesvc.service.cancel" })
@EnableDubbo(scanBasePackages = { "com.bytesvc.service", "com.bytesvc.config" })

三、TCC型业务服务开发

TCC型服务可以有两种配置方式:简化配置、一般配置,业务开发者可视情况选择任意一种。

3.1、TCC型服务Try业务定义

通过@Compensable注解定义的service为可补偿型service。@Compensable注解需要定义三个参数:
1)interfaceClass,必需。该值用于指定confirm/cancel针对的业务接口,该接口同时被用于校验confirm/cancel实现类。confirm/cancel实现类如果没有实现该业务接口则会被认为无效;
2)confirmableKey,可选。该值用于指定confirm实现类在容器中的beanId,若没有confirm逻辑则不必指定;
3)cancellableKey,可选。该值用于指定cancel实现类在容器中的beanId,若没有cancel逻辑则不必指定;注意:若try阶段执行了写操作则必须有相应的取消逻辑;

3.1.1、Spring Cloud
@RestController
@Compensable(
  interfaceClass = IAccountService.class 
, confirmableKey = "accountServiceConfirm"
, cancellableKey = "accountServiceCancel"
)
public class AccountController implements IAccountService {

	@Transactional
	@ResponseBody
	@RequestMapping(value = "/increase", method = RequestMethod.POST)
	public void increaseAmount(@RequestParam("acctId") String acctId, @RequestParam("amount") double amount) 
               throws ServiceException {
	    // TODO ...
	}

	@Transactional
	@ResponseBody
	@RequestMapping(value = "/decrease", method = RequestMethod.POST)
	public void decreaseAmount(@RequestParam("acctId") String acctId, @RequestParam("amount") double amount) 
               throws ServiceException {
	    // TODO ...
	}

}
3.1.2、Dubbo
@Service("accountService")
@Compensable(
  interfaceClass = IAccountService.class 
, confirmableKey = "accountServiceConfirm"
, cancellableKey = "accountServiceCancel"
)
public class AccountServiceImpl implements IAccountService {

	@Transactional
	public void increaseAmount(String accountId, double amount) 
               throws ServiceException {
	    // TODO ...
	}

	@Transactional
	public void decreaseAmount(String accountId, double amount) 
               throws ServiceException {
	    // TODO ...
	}

}

3.2、TCC型服务Confirm业务定义

@Service("accountServiceConfirm")
public class AccountServiceConfirm implements IAccountService {
	static final Logger logger = LoggerFactory.getLogger(AccountServiceConfirm.class);

	@Transactional
	public void increaseAmount(String accountId, double amount) 
               throws ServiceException {
	    // TODO ...
	    logger.info("done increase: acct= {}, amount= {}", accountId, amount);
	}

	@Transactional
	public void decreaseAmount(String accountId, double amount) 
               throws ServiceException {
	    // TODO ...
	    logger.info("done decrease: acct= {}, amount= {}", accountId, amount);
	}

}

注意:
1)全局事务决定提交时,可补偿型service的confirm逻辑总是会被执行;
2)全局事务决定提交时,可能会存在某个分支事务try操作没有执行成功的情况,此时该分支的confirm逻辑仍然会被调用。存在该情况的原因是:分支事务执行出错并抛出异常(如ServiceException),其业务逻辑通过Transactional定义了该异常应该回滚事务(或容器通过判断其异常类型最终决定回滚),因而导致分支的try阶段操作没有生效;然而发起方捕捉到了分支抛出的异常,此时如果发起方可以处理分支执行出错的逻辑,则不再向外抛出异常;最终发起方的容器认为执行成功,并决定提交全局事务,因此就会通知分支事务管理器提交分支事务,而分支事务会回调分支事务中涉及的所有service的confirm逻辑。
3)confirm逻辑被回调时,若不确定try阶段事务是否成功执行,则可以通过CompensableContext.isCurrentCompensableServiceTried()来确定。
4)confirm阶段仅负责本service的confirm逻辑,而不应该再执行远程调用。如果try阶段调用过远程服务,则事务上下文已传播至远程节点,全局事务提交时,将由其所在节点的事务管理器负责执行confirm逻辑。

3.3、TCC型服务Cancel业务定义

@Service("accountServiceCancel")
public class AccountServiceCancel implements IAccountService {
	static final Logger logger = LoggerFactory.getLogger(AccountServiceCancel.class);

	@Transactional
	public void increaseAmount(String accountId, double amount) 
               throws ServiceException {
	    // TODO ...
	}

	@Transactional
	public void decreaseAmount(String accountId, double amount) 
               throws ServiceException {
	    // TODO ...
	}
}

注意:
1) 全局事务决定回滚时,分支事务中可补偿型service的cancel逻辑不一定会被执行,原因是:参与该分支事务的Try方法可能抛出异常导致其本地事务回滚,因此该服务的Try操作是没有生效的;
2) 全局事务决定回滚时,主事务中可补偿型service的cancel逻辑并不一定会被执行;原因是:主事务控制着全局事务的最终完成方向,当其最终决定回滚全局事务时,有机会通过将自己本地Try阶段的事务直接rollback来完成撤销try阶段操作,而不必通过cancel逻辑来实现。
3) cancel阶段仅负责本service的cancel逻辑,而不应该再执行远程调用。如果try阶段调用过远程服务,则事务上下文已传播至远程节点,全局事务回滚时,将由其所在节点的事务管理器负责执行cancel逻辑。

3.4、TCC型服务的简化配置

简化配置仅适合一个接口只提供一个方法的服务场景,当一个接口有多个方法时,必须使用一般配置。

3.4.1、Spring Cloud
@Compensable(interfaceClass = ITransferService.class, simplified = true)
@RestController
public class SimplifiedController implements ITransferService {

	@ResponseBody
	@RequestMapping(value = "/simplified/transfer", method = RequestMethod.POST)
	@Transactional
	public void transfer(@RequestParam String sourceAcctId, @RequestParam String targetAcctId, @RequestParam double amount) {
		// TODO
	}

	@CompensableConfirm
	@Transactional
	public void confirmTransfer(String sourceAcctId, String targetAcctId, double amount) {
		// TODO
	}

	@CompensableCancel
	@Transactional
	public void cancelTransfer(String sourceAcctId, String targetAcctId, double amount) {
		// TODO
	}

}
3.4.2、Dubbo
@Service("transferService")
@Compensable(interfaceClass = ITransferService.class, simplified = true)
@RestController
public class TransferServiceImpl implements ITransferService {

	@Transactional
	public void transfer(String sourceAcctId, String targetAcctId, double amount) {
		// TODO
	}

	@CompensableConfirm
	@Transactional
	public void confirmTransfer(String sourceAcctId, String targetAcctId, double amount) {
		// TODO
	}

	@CompensableCancel
	@Transactional
	public void cancelTransfer(String sourceAcctId, String targetAcctId, double amount) {
		// TODO
	}

}

3.5、CompensableContextAware & CompensableContext

尽管Confirm/Cancel阶段的执行会使用和Try阶段一样的参数,有时候将try阶段的计算结果直接传递给confirm/cancel阶段使用还是有必要的。例如,处于性能的考虑不想在confirm/cancel阶段再次计算数据的状态,又或者confirm/cancel阶段无法计算出try阶段执行时的历史状态时,可以考虑将临时结果放置在CompensableContext变量中供confirm/cancel阶段直接使用。

3.5.1、 在Try阶段设置CompensableContext变量
@Service("accountService")    
@Compensable(interfaceClass = IAccountService.class    
    , confirmableKey = "accountServiceConfirm", cancellableKey = "accountServiceCancel"
)    
public class AccountServiceImpl implements IAccountService, CompensableContextAware {    
    private CompensableContext compensableContext;    
    
    @Transactional    
    public void increaseAmount(String accountId, double amount) 
           throws ServiceException {    
        // TODO ...    
        Object value = null; // TODO    
        this.compensableContext.setVariable("resultInTryPhase", value);    
    }    
    
    public void setCompensableContext(CompensableContext aware) {    
        this.compensableContext = aware;    
    }    
}

注意:需要使用CompensableContext的service,必须实现CompensableContextAware接口。

3.5.2、 在Confirm/Cancel阶段使用CompensableContext变量
 @Service("accountServiceConfirm")    
 public class AccountServiceConfirm implements IAccountService, CompensableContextAware {    
     static final Logger logger = LoggerFactory.getLogger(AccountServiceConfirm.class);    

     private CompensableContext compensableContext;    

     @Transactional    
     public void increaseAmount(String accountId, double amount) 
            throws ServiceException {    
         Object value = this.compensableContext.getVariable("resultInTryPhase");    
         boolean svcTried = this.compensableContext.isCurrentCompensableServiceTried();    
         // TODO ...
         logger.info("done increase: acct= {}, amount= {}", accountId, amount);    
     }    

     public void setCompensableContext(CompensableContext aware) {    
         this.compensableContext = aware;    
     }    

}    

注意:
1) Confirm/Cancel阶段仅可以获取变量,而不能再新建/更新/删除变量;
2) 如果Confirm/Cancel阶段想知道其对应的try阶段是否执行成功,可以通过CompensableContext.isCurrentCompensableServiceTried()获知。

四、可补偿型业务服务的幂等性说明

ByteTCC不要求service的实现逻辑具有幂等性。事实上,ByteTCC也不推荐在业务层面保障业务操作幂等性,因为在业务层面实现幂等性,其复杂度非常高。因此ByteTCC在实现时也做了这方面的考虑。ByteTCC在TCC事务提交/回滚时,虽然也可能会多次调用confirm/cancel方法,但是ByteTCC可以确保每个confirm/cancel方法仅被"执行并提交"一次。所以,在使用ByteTCC时可以仅关注业务逻辑,而不必考虑事务相关的细节。

4.1、 “仅执行并提交一次”的说明:

1)Confirm操作虽然可能被多次调用,但是其参与的LocalTransaction均由ByteTCC事务管理器控制,一旦Confirm操作所在的LocalTransaction事务被ByteTCC事务管理器成功提交,则ByteTCC事务管理器会标注该Confirm操作成功,后续将不再执行该Confirm操作。
2)Cancel操作的控制原理同Confirm操作。需要说明的是,Cancel操作只有在Try阶段所在的LocalTransaction被成功提交的情况下才会被调用,Try阶段所在的LocalTransaction被回滚时Cancel操作不会被执行。

五、TCC(Try/Confirm/Cancel)模式

一个业务被划分为两个阶段:一个未决的操作,和随后的一个确认/取消操作,即Try/Confirm/Cancel。ByteTCC提供TCC模式支持。

注意:
1)TCC模式下,全局事务最终决定提交时,事务管理器需要对Try阶段的未决操作,显式地回调其相应的Confirm操作;
2)TCC模式无法保障Confirm操作一定会成功,失败的原因可能是由业务逻辑错误,或者数据不正确所致;
3)TCC模式相对业务补偿模式,占用了更多资源(如Try阶段/Confirm阶段都需要操作数据库);

六、saga模式

使用一个反向的业务操作,来撤销先前的业务操作,以此来保障事务一致性。ByteTCC提供saga模式支持。

注意:
1)相对于TCC模式模式,saga模式并不需要Confirm操作;
2)并非所有的业务操作,都能提供出相应的反向操作;

七、ByteTCC对Spring Cloud/Dubbo集群的支持

ByteTCC计划对负载均衡的支持粒度,可分为两种:a、按事务进行负载均衡;b、按请求进行负载均衡。

7.1、按事务进行负载均衡

在某个事务T内,consumer端应用app1首次向provider端应用app2(集群环境)发起请求时,ByteTCC使用random负载均衡策略将其随机分发到一个app2实例(如inst2);后续app1在该事务T内再次向app2发起请求时,将始终落在inst2(即首次请求的处理实例)上。

说明
1)使用SpringCloud,需导入SpringCloudSecondaryConfiguration配置类;
2)使用Dubbo,需导入DubboSecondaryConfiguration配置类;
3)使用SpringBoot,需导入SpringBootSecondaryConfiguration配置类。

7.2、按请求进行负载均衡

consumer端应用app1向provider端应用app2(集群环境)发起请求时,ByteTCC始终按业务系统指定的负载均衡策略将请求分发到一个app2实例。

说明
1)使用SpringCloud,需导入SpringCloudConfiguration配置类;
2)使用Dubbo,需导入DubboSupportConfiguration配置类;
3)使用SpringBoot,需导入SpringBootConfiguration配置类。

八、ByteTCC对长事务的支持

ByteTCC使用org.bytesoft.compensable.UserCompensable接口来处理长事务。

8.1、开启长事务

public Xid compensableBegin() throws NotSupportedException, SystemException;

注意:该方法返回一个Xid参数,即全局事务的ID,应用程序应该设法记录该XID,以便后续恢复需要。由于全局事务是由业务代码开启,在业务代码未显式的提交/回滚全局事务之前,ByteTCC事务将不会提交/回滚该全局事务。倘若此时发起该全局事务的业务系统宕机,则需要业务系统重启后显式的对全局事务进行恢复,此时就需要使用该全局事务XID。

8.2、提交长事务

public void compensableCommit() throws RollbackException
    , HeuristicMixedException, HeuristicRollbackException
    , SecurityException, IllegalStateException, SystemException;

注意:
1)该方法仅可在执行compensableBegin()方法后发起方系统未重启的情况下被调用;
2)该方法必须在与compensableBegin()操作相同的线程中调用。

8.3、回滚长事务

public void compensableRollback() 
    throws IllegalStateException, SecurityException, SystemException;

注意:
1)该方法仅可在执行compensableBegin()方法后发起方系统未重启的情况下被调用;
2)该方法必须在与compensableBegin()操作相同的线程中调用。

8.4、(恢复)开启长事务

public void compensableRecoveryBegin(Xid xid) 
    throws NotSupportedException, SystemException;

注意:
1)业务系统在调用compensableBegin()方法后,并在未调用/未成功调用compensableRollback()/compensableCommit()之前宕机时,重启后需要调用该方法,使用compensableBegin()方法返回的XID作为参数(该XID需要业务系统自行记录);
2) 业务系统重启并执行compensableRecoveryBegin后,可以在该事务中继续执行未完成的业务逻辑,ByteTCC会将宕机重启之前、之后的业务操作一并提交/回滚。

8.5、(恢复)提交长事务

public void compensableRecoveryCommit() 
    throws RollbackException, HeuristicMixedException, HeuristicRollbackException
         , SecurityException, IllegalStateException, SystemException;

注意:
1)该方法仅可在执行compensableRecoveryBegin()方法后被调用;
2)该方法必须在与compensableRecoveryBegin()操作相同的线程中调用。

8.6、(恢复)回滚长事务

public void compensableRecoveryRollback() 
    throws IllegalStateException, SecurityException, SystemException;

注意:
1)该方法仅可在执行compensableRecoveryBegin()方法后被调用;
2)该方法必须在与compensableRecoveryBegin()操作相同的线程中调用。

九、ByteTCC对多数据源的支持

以在同一个业务系统中的ConsumerService(访问数据源consumerDs)和ProviderService(访问数据源providerDs)两个service为例,其中ConsumerService.invoke()方法调用ProviderService.execute()方法(二者使用了不同的数据源)。

public class ProviderServiceImpl implements ProviderService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void execute() {
        // TODO: operation on data-source: providerDs
    }
}

注意:如果被调用的服务(ProviderService)和调用它的服务(ConsumerService)使用的是不同的数据源,则被调用的服务(ProviderService)方法的Transactional.propagation必须设置为Propagation.REQUIRES_NEW。

public class ConsumerServiceImpl implements ConsumerService {
    private ProviderService service;
    @Transactional
    public void invoke() {
        // TODO: operation on data-source: consumerDs
        service.execute();
    }
}

注意:不管是ConsumerService还是ProviderService,每个方法中都不可以操作超过一个以上的DataSource。

由于ProviderService.execute()传播级别设置为Propagation.REQUIRES_NEW,因此ByteTCC会在Try阶段中为ConsumerService.invoke()和ProviderService.execute()这两个方法操作分别开启独立的分支事务(这两个分支事务归属于同一个TCC事务),并记录每个service和其分支事务的对应关系以及分支事务的提交/完成情况;当TCC全局事务最终决定提交/回滚时,ByteTCC再根据Try阶段各service及对应分支事务完成的情况,决定每个service是否需要确认/取消,并最终执行确认/取消操作。

十、ByteTCC对Spring Cloud的其他支持

10.1、对Context Path的支持

当服务提供方应用使用了Context Path时,服务消费方需要在配置中体现,如:

org:
  bytesoft:
    bytetcc:
      contextpath:
        springcloud-sample-provider: /sample-provider

上述配置将告知ByteTCC,应用springcloud-sample-provider使用的context-path为/sample-provider。

10.2、对IRule的支持

ByteTCC默认通过org.bytesoft.bytetcc.supports.springcloud.rule.CompensableRuleImpl来提供路由决策。如果业务系统希望对Rule进行扩展,可以考虑通过org.bytesoft.bytetcc.NFCompensableRuleClassName配置向ByteTCC注册自己的Rule实现。例如,

org:
  bytesoft:
    bytetcc:
      NFCompensableRuleClassName: xxx.RuleImpl

注意:xxx.RuleImpl需要实现org.bytesoft.bytetcc.supports.springcloud.rule.CompensableRule接口。

十一、ByteTCC对数据源的自动装配

11.1、自动装配

从0.5.x开始,byteTCC默认会自动将配置的数据源封装到LocalXADataSource,不再需要象0.4.x版本那样需要手工封装。

11.2、手工装配

对于象HiKariCP等少部分存在兼容缺陷的连接池管理框架,需要和0.4.x版本一样使用手工装配。

	@Bean(name = "dataSource")
	public DataSource getDataSource() {
		LocalXADataSource dataSource = new LocalXADataSource();
		dataSource.setDataSource(this.invokeGetDataSource());
		return dataSource;
	}

	public DataSource invokeGetDataSource() {
	    // ......
	}

使用手工装配时,需要配置属性org.bytesoft.bytetcc.datasource.autoconfig=false。

If you've found byteTCC useful, and would like to support future development of byteTCC, please consider donating. Any amount is appreciated.

WeiXin

Alipay

Clone this wiki locally