-
-
Notifications
You must be signed in to change notification settings - Fork 911
User Guide 0.4.x
- 仅支持使用@Transactional来配置事务,而不能通过xml来配置事务;
- 针对一个特定的可补偿型服务接口,业务系统提供的Try、Confirm、Cancel三个实现类,其Try实现类必须定义@Compensable注解,而Confirm、Cancel实现类则不能定义Compensable注解;
- 可补偿型服务的Try/Confirm/Cancel实现类/实现方法必须定义Transactional注解,且propagation必须是Required, RequiresNew, Mandatory中的一种(即业务代码必须参与事务,从0.3.0开始强制要求);
- 业务系统不可使用随机端口(本约束与采用的负载均衡分发粒度相关,当使用ByteTCC按请求粒度负载均衡时可忽略该约束);
- 用@Compensable注解的TCC型服务Controller必须实现一个业务接口(业务系统自行指定,但需要与Compensable.interfaceClass一致);
- 在每个参与tcc事务的数据库中创建bytejta表(ddl见bytetcc-supports.jar/bytetcc.sql);
- JDK版本:7.0及以上版本;
- 服务提供方Controler必须添加@Compensable注解;
- 不允许对Feign/Ribbon/RestTemplate等HTTP请求自行进行封装,但允许拦截;
- 如果需要定制instanceId, 格式必须为${ip}:${自行指定}:${port};
- 0.4.x版本仅支持Spring Boot 1.x、Spring Cloud 1.x版本;
- 当前版本强制要求必须使用xml文件来配置dubbo;
- 必须且仅可指定一个<dubbo:application name="..." />元素,其name不能为空,且必须唯一;
- 必须且仅可指定一个<dubbo:protocol port="..." />元素,其port不能为空,也不能为-1;
- 定义dubbo服务提供者时(<dubbo:service />):a、filter必须为compensable;b、loadbalance必须为compensable;c 、cluster必须为failfast;d、retries必须为-1;e、group必须为org.bytesoft.bytetcc;
- 定义dubbo服务消费者时(<dubbo:reference />):a、filter必须为compensable;b、loadbalance必须为compensable;c 、cluster必须为failfast;d、retries必须为-1;e、group必须为org.bytesoft.bytetcc;
- 通过url来配置<dubbo:service />时,url的ip、port需要是dubbo发现机制发现的可选地址中的一个;
@ImportResource({ "classpath:bytetcc-supports-springcloud.xml" })
@Import(SpringCloudConfiguration.class)
<import resource="classpath:bytetcc-supports-dubbo.xml" />
@SpringBootApplication(scanBasePackages = "com.yourcompany.service...")
<context:component-scan base-package="com.yourcompany.service..." />
@Bean(name = "dataSource")
public DataSource getDataSource() {
LocalXADataSource dataSource = new LocalXADataSource();
dataSource.setDataSource(this.invokeGetDataSource());
return dataSource;
}
public DataSource invokeGetDataSource() {
// ......
}
注意:补偿型service中应该使用org.bytesoft.bytejta.supports.jdbc.LocalXADataSource封装过的数据源。
<bean id="dontUseThisDataSourceDirectly" class="org.apache.commons.dbcp.BasicDataSource">
<!-- ...... -->
</bean>
<bean id="dataSource"
class="org.bytesoft.bytejta.supports.jdbc.LocalXADataSource">
<property name="dataSource" ref="dontUseThisDataSourceDirectly" />
</bean>
注意:补偿型service中应该尽量使用org.bytesoft.bytejta.supports.jdbc.LocalXADataSource封装过的数据源(如本例中的'dataSource',而不是'dontUseThisDataSourceDirectly')。
TCC型服务可以有两种配置方式:简化配置、一般配置,业务开发者可视情况选择任意一种。
通过@Compensable注解定义的service为可补偿型service。@Compensable注解需要定义三个参数:
1)interfaceClass,必需。该值用于指定confirm/cancel针对的业务接口,该接口同时被用于校验confirm/cancel实现类。confirm/cancel实现类如果没有实现该业务接口则会被认为无效;
2)confirmableKey,可选。该值用于指定confirm实现类在容器中的beanId,若没有confirm逻辑则不必指定;
3)cancellableKey,可选。该值用于指定cancel实现类在容器中的beanId,若没有cancel逻辑则不必指定;注意:若try阶段执行了写操作则必须有相应的取消逻辑;
@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 ...
}
}
@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 ...
}
}
@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逻辑。
@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逻辑。
简化配置仅适合一个接口只提供一个方法的服务场景,当一个接口有多个方法时,必须使用一般配置。
@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
}
}
@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
}
}
尽管Confirm/Cancel阶段的执行会使用和Try阶段一样的参数,有时候将try阶段的计算结果直接传递给confirm/cancel阶段使用还是有必要的。例如,处于性能的考虑不想在confirm/cancel阶段再次计算数据的状态,又或者confirm/cancel阶段无法计算出try阶段执行时的历史状态时,可以考虑将临时结果放置在CompensableContext变量中供confirm/cancel阶段直接使用。
@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接口。
@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时可以仅关注业务逻辑,而不必考虑事务相关的细节。
1)Confirm操作虽然可能被多次调用,但是其参与的LocalTransaction均由ByteTCC事务管理器控制,一旦Confirm操作所在的LocalTransaction事务被ByteTCC事务管理器成功提交,则ByteTCC事务管理器会标注该Confirm操作成功,后续将不再执行该Confirm操作。
2)Cancel操作的控制原理同Confirm操作。需要说明的是,Cancel操作只有在Try阶段所在的LocalTransaction被成功提交的情况下才会被调用,Try阶段所在的LocalTransaction被回滚时Cancel操作不会被执行。
一个业务被划分为两个阶段:一个未决的操作,和随后的一个确认/取消操作,即Try/Confirm/Cancel。ByteTCC提供TCC模式支持。
注意:
1)TCC模式下,全局事务最终决定提交时,事务管理器需要对Try阶段的未决操作,显式地回调其相应的Confirm操作;
2)TCC模式无法保障Confirm操作一定会成功,失败的原因可能是由业务逻辑错误,或者数据不正确所致;
3)TCC模式相对业务补偿模式,占用了更多资源(如Try阶段/Confirm阶段都需要操作数据库);
使用一个反向的业务操作,来撤销先前的业务操作,以此来保障事务一致性。ByteTCC提供saga模式支持。
注意:
1)相对于TCC模式模式,saga模式并不需要Confirm操作;
2)并非所有的业务操作,都能提供出相应的反向操作;
ByteTCC计划对负载均衡的支持粒度,可分为两种:a、按事务进行负载均衡;b、按请求进行负载均衡。
在某个事务T内,consumer端应用app1首次向provider端应用app2(集群环境)发起请求时,ByteTCC使用random负载均衡策略将其随机分发到一个app2实例(如inst2);后续app1在该事务T内再次向app2发起请求时,将始终落在inst2(即首次请求的处理实例)上。
注意
1)ByteTCC 0.4.x版本默认支持“按事务进行负载均衡”的特性。
consumer端应用app1向provider端应用app2(集群环境)发起请求时,ByteTCC始终按业务系统指定的负载均衡策略将请求分发到一个app2实例。
注意
1)ByteTCC 0.4.x版本暂不提供“按请求进行负载均衡”的特性,如果您希望使用该特性,请联系[email protected]。
ByteTCC使用org.bytesoft.compensable.UserCompensable接口来处理长事务。
public Xid compensableBegin() throws NotSupportedException, SystemException;
注意:该方法返回一个Xid参数,即全局事务的ID,应用程序应该设法记录该XID,以便后续恢复需要。由于全局事务是由业务代码开启,在业务代码未显式的提交/回滚全局事务之前,ByteTCC事务将不会提交/回滚该全局事务。倘若此时发起该全局事务的业务系统宕机,则需要业务系统重启后显式的对全局事务进行恢复,此时就需要使用该全局事务XID。
public void compensableCommit() throws RollbackException
, HeuristicMixedException, HeuristicRollbackException
, SecurityException, IllegalStateException, SystemException;
注意:
1)该方法仅可在执行compensableBegin()方法后发起方系统未重启的情况下被调用;
2)该方法必须在与compensableBegin()操作相同的线程中调用。
public void compensableRollback()
throws IllegalStateException, SecurityException, SystemException;
注意:
1)该方法仅可在执行compensableBegin()方法后发起方系统未重启的情况下被调用;
2)该方法必须在与compensableBegin()操作相同的线程中调用。
public void compensableRecoveryBegin(Xid xid)
throws NotSupportedException, SystemException;
注意:
1)业务系统在调用compensableBegin()方法后,并在未调用/未成功调用compensableRollback()/compensableCommit()之前宕机时,重启后需要调用该方法,使用compensableBegin()方法返回的XID作为参数(该XID需要业务系统自行记录);
2) 业务系统重启并执行compensableRecoveryBegin后,可以在该事务中继续执行未完成的业务逻辑,ByteTCC会将宕机重启之前、之后的业务操作一并提交/回滚。
public void compensableRecoveryCommit()
throws RollbackException, HeuristicMixedException, HeuristicRollbackException
, SecurityException, IllegalStateException, SystemException;
注意:
1)该方法仅可在执行compensableRecoveryBegin()方法后被调用;
2)该方法必须在与compensableRecoveryBegin()操作相同的线程中调用。
public void compensableRecoveryRollback()
throws IllegalStateException, SecurityException, SystemException;
注意:
1)该方法仅可在执行compensableRecoveryBegin()方法后被调用;
2)该方法必须在与compensableRecoveryBegin()操作相同的线程中调用。
以在同一个业务系统中的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是否需要确认/取消,并最终执行确认/取消操作。
当服务提供方应用使用了Context Path时,服务消费方需要在配置中体现,如:
org:
bytesoft:
bytetcc:
contextpath:
springcloud-sample-provider: /sample-provider
上述配置将告知ByteTCC,应用springcloud-sample-provider使用的context-path为/sample-provider。
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接口。
If you've found byteTCC useful, and would like to support future development of byteTCC, please consider donating. Any amount is appreciated.
WeiXin
Alipay