转载请注明出处:http://blog.csdn.net/l1028386804/article/details/73731363
有一定分布式开发经验的朋友都知道,产品/项目/系统最初为了能够快速迭代上线,往往不太注重产品/项目/系统的高可靠性、高性能与高扩展性,采用单体应用和单实例数据库的架构方式快速迭代开发;当产品/项目/系统做到一定规模的时候,原有的系统架构则不足以支撑义务发展需要,往往相同的业务则需要重复写很多次,导致代码大量冗余,难以维护和扩展,这时不得不对原有产品/项目/系统进行拆分,引入分布式的系统架构;而对原有产品/项目/系统进行拆分的过程中,对于业务和数据的拆分和迁移则成为了最为棘手的问题,尤其是在原有业务不能下线,拆分后的业务同时上线的场景下这种问题更加突出;项目拆分后,业务被拆分为多个独立的子业务分散到多个子系统中,而原有的单一数据库则被拆分到多个数据库中,拆分后的数据库则同样又面临着让人头疼的分布式事务的问题。
本文就针对项目拆分后数据库的分布式事务问题,基于tcc-transaction分布式TCC型事务进行框架的搭建,同时引入相关的实战案例,来解决让人头疼的分布式事务问题。
介绍:tcc-transaction是开源的TCC补偿性分布式事务框架,Git地址:https://github.com/changmingxie/tcc-transaction
TCC为Try、Confirm、Cancel的缩写:try阶段预留资源尝试提交,confirm阶段确定提交,cancel取消提交释放资源。
1.2.x项目指南地址:https://github.com/changmingxie/tcc-transaction/wiki/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%971.2.x
本文的例子为引入一个本人实际工作中的一个开发场景:创建资产,将资产信息同时同步到Mongo与ES的流程(ES代码不列出了,与mongo类似),整个流程保证数据一致
因为是第三方包,所以需要自己打包到本地仓库。但包中spring版本为3.2.12.RELEASE,如果本地项目为4.x,比如本人的项目spring版本为4.3.4.RELEASE,如果不修改tcc中的spring版本,将报错无法启动,所以需要对原有框架源码进行相应的修改。
源码修改比较简单,如下
4.3.4.RELEASE
org.quartz-scheduler
quartz
2.2.1
c3p0
c3p0
com.alibaba
dubbo
2.5.3
public void init() {
try {
MethodInvokingJobDetailFactoryBean jobDetail = new MethodInvokingJobDetailFactoryBean();
jobDetail.setTargetObject(transactionRecovery);
jobDetail.setTargetMethod("startRecover");
jobDetail.setName("transactionRecoveryJob");
jobDetail.setConcurrent(false);
jobDetail.afterPropertiesSet();
CronTriggerFactoryBean cronTrigger = new CronTriggerFactoryBean();
cronTrigger.setBeanName("transactionRecoveryCronTrigger");
cronTrigger.setJobDetail(jobDetail.getObject());
cronTrigger.setCronExpression(transactionConfigurator.getRecoverConfig().getCronExpression());
cronTrigger.afterPropertiesSet();
scheduler.scheduleJob(jobDetail.getObject(), cronTrigger.getObject());
scheduler.start();
} catch (Exception e) {
throw new SystemException(e);
}
}
各位也可参考如下的修改: https://github.com/changmingxie/tcc-transaction/pull/84/files
mvn -Dmaven.test.skip=true install
参考1.2.x使用指南,引入两个依赖(本人项目dubbo/dubbox框架,我使用并打包时版本为1.2.3.1)。调用方和提供方都需要引入依赖。
原文中是配置在web.xml中,我个人试了一下放在dubbo web项目的web.xml中,但配置并没有被加载。该文件的意义只是希望项目启动时被加载,于是直接在dubbo中的一个spring的配置文件中引入,如下:
该文件里面提供各种aop逻辑,项目启动时扫描指定注解,并做增强。
需要为tcc配置数据源,可以是MySQL或其他nosql,本文使用mysql,其他可参见原指南文档。
mysql配置如下:
com.alibaba.dubbo.remoting.TimeoutException
需要注意的点:
根据以上的tbSufifix配置,脚本如下:
CREATE TABLE `tcc_transaction_asset` (
`TRANSACTION_ID` int(11) NOT NULL AUTO_INCREMENT,
`DOMAIN` varchar(100) DEFAULT NULL,
`GLOBAL_TX_ID` varbinary(32) NOT NULL,
`BRANCH_QUALIFIER` varbinary(32) NOT NULL,
`CONTENT` varbinary(8000) DEFAULT NULL,
`STATUS` int(11) DEFAULT NULL,
`TRANSACTION_TYPE` int(11) DEFAULT NULL,
`RETRIED_COUNT` int(11) DEFAULT NULL,
`CREATE_TIME` datetime DEFAULT NULL,
`LAST_UPDATE_TIME` datetime DEFAULT NULL,
`VERSION` int(11) DEFAULT NULL,
PRIMARY KEY (`TRANSACTION_ID`),
UNIQUE KEY `UX_TX_BQ` (`GLOBAL_TX_ID`,`BRANCH_QUALIFIER`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
如果表名称不对,启动过程会报错,这时,对数据表做相应调整即可。
/**
* @author liuyazhuang
* 资产相关的业务发布Dubbo服务
*/
public interface AssetCardService {
/**
* 测试预保存资产(状态为待确认)
*/
@Compensable
int testSaveAssetCard(AssetCardModel model);
/**
* 确认保存资产到mysql(状态为正常)
*/
int confirmMysqlSaveAssetCard(AssetCardModel model);
/**
* 取消保存资产到msyql(更新状态为删除)
*/
int cancelMysqlSaveAssetCard(AssetCardModel model);
/**
* 预保存资产到mongo(状态为待确认)
*/
@Compensable
void processMongo(AssetCardModel model);
/**
* 确认保存资产到mongo(状态为正常)
*/
void confirmMongoSaveAssetCard(AssetCardModel model);
/**
* 取消保存资产到mongo(更新状态为删除)
*/
void cancelMongoSaveAssetCard(AssetCardModel model);
}
需要注意的点:
/**
* @author liuyazhuang
* 资产相关的业务发布Dubbo服务的实现
*/
@Service
@Component
public class AssetCardServiceImpl implements AssetCardService{
@Override
@Compensable(confirmMethod = "confirmMysqlSaveAssetCard", cancelMethod = "cancelMysqlSaveAssetCard", transactionContextEditor = DubboTransactionContextEditor.class)
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = { Exception.class })
public int testSaveAssetCard(AssetCardModel model){
// 保存mysql,data状态为-1
model.setDataStatus(-1);
assetCardDao.insert(model);
// mongo处理
assetCardService.processMongo(model);
return model.getId();
}
@Override
public int confirmMysqlSaveAssetCard(AssetCardModel model){
System.out.println("============================================================================");
System.out.println("=================mysql:confirm");
System.out.println("============================================================================");
// 更新mysql data_status为0
model.setDataStatus(0);
assetCardDao.updateByPrimaryKey(model);
return model.getId();
}
@Override
public int cancelMysqlSaveAssetCard(AssetCardModel model){
System.out.println("============================================================================");
System.out.println("=================mysql:cancel");
System.out.println("============================================================================");
// 更新mysql data_status为-1
model.setDataStatus(-1);
assetCardDao.updateByPrimaryKey(model);
return model.getId();
}
@Compensable(confirmMethod = "confirmMongoSaveAssetCard", cancelMethod = "cancelMongoSaveAssetCard", transactionContextEditor = DubboTransactionContextEditor.class)
@Override
public void processMongo(AssetCardModel model) {
// 保存mongo,data_statu为-1
model.setDataStatus(-1);
assetCardDaoWrapper.saveMongo(model);
}
@Override
public void confirmMongoSaveAssetCard(AssetCardModel model){
System.out.println("============================================================================");
System.out.println("=================mongo:confirm");
System.out.println("============================================================================");
// 更新mongo data_status为0
model.setDataStatus(0);
assetCardDaoWrapper.updateMongo(model);
}
@Override
public void cancelMongoSaveAssetCard(AssetCardModel model){
System.out.println("============================================================================");
System.out.println("=================mongo:cancel");
System.out.println("============================================================================");
// 更新mongo data_status为-1
model.setDataStatus(-1);
assetCardDao.updateByPrimaryKey(model);
assetCardDaoWrapper.updateMongo(model);
}
}
注意点:
@Override
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = { Exception.class })
public long testSaveAssetCard(AssetCardModel assetCardModel) throws AssetException {
assetCardModel.setId(IdGenerator.getId());
return assetCardService.testSaveAssetCard(assetCardModel);
}
注意点:
源码中提供tcc-transaction-server web项目,该项目提供界面查看事务日志,打包后部署即可,我们这里就不在作详细的描述。
业务流程使用记录:
前提:用户下单,建立订单,创建支付记录,支付记录状态为待支付
try:
用户金额冻结
调用积分处理TCC:
try:预增加积分
confirm:更新增加积分状态
cancel:取消增加的积分
confirm:
订单支付状态更新为已支付
订单支付记录支付状态更新为已支付
用户金额扣款(以上三个操作在同一本地事务)
cancel:
判断订单支付状态与订单记录支付状态为未支付
用户冻结金额释放