前言
最近和朋友聊天,他说他承接的外包项目遇到了分布式事务问题,问我有没啥解决方案,我本可以直接跟他说,分布式事务方案网上一大堆,什么tcc、可靠消息一致性、最大努力通知之类的,直接网上找个试下,比如直接用阿里的seata。但我并没有这么做,因为分布式事务,本来就是一个很复杂的课题,真正落地的时候,会发现有时候是多种分布式方案一起混用,而非一种方案走到黑。
因此我就跟他说,能不用分布式事务,就尽量不用,后来我就问了一下他的业务场景,场景也不是很复杂,就是邀请好友注册,然后可以增加积分,朋友实现逻辑的伪代码大概如下
@Transactional(rollbackFor = Exception.class)
public Boolean inviteUser(..){
userService.add(..);
integralService.addIntegration(..,20)
}
其中integralService是一个远程积分服务,20为增加的积分值。这代码乍一看是没问题,我想可能很多朋友都会这么写。后边我就问朋友说你们这个业务场景是否允许如下场景
- 允不允许邀请的用户入库成功,而积分入库失败?
- 允不允许邀请的用户入库失败,而积分入库成功?
朋友思考了一下,说第二种不允许,第一种方式可以通过补偿的方式增加积分。
现在我们回过头来看这段代码,我抛出以下两个问题,看文章的朋友可以思考下
- 如果添加积分请求耗时特别长,这段代码有没有问题?
- 如果添加积分因为网络抖动原因出了异常,这段代码有没有问题?
这边说下我的想法
- 耗时过长,会导致长事务的发生,在并发场景下,可能会导致数据库连接得不到释放
- 网络抖动出了异常,可能会导致用户服务的添加逻辑进行回滚
解决耗时过长,有些朋友可能想到可以采用异步的方式,积分抖动异常,可以通过添加熔断机制,比如积分超时没响应,就直接进行熔断
今天我再说一种方案,就是在事务提交后再进行调用,罗里吧嗦一大堆,才刚要进入正题,哈哈
如何在spring的事务中正确的进行远程调用
通过spring的事务同步管理器
这个是个什么鬼,这是我直译,它的真身是长如下
org.springframework.transaction.support.TransactionSynchronizationManager
这玩意有啥用,可以利用它注册一个事务同步器,这个事务同步器,可以允许在事务提交后,做一些事情,核心代码如下
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
//做你想做的业务
}
});
看了代码,想必大家都知道怎么改造上面邀请用户,添加积分了吧
@Transactional(rollbackFor = Exception.class)
public Boolean inviteUser(..) {
userService.add(..);
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
integralService.addIntegration(..,20)
}
});
但大家发现没有,每次都要写这么一坨代码,看着是不是很恶心,有没有什么改造的方案。
答案有的,通过注解+aop来整合实现,具体实现逻辑,可以查看下面demo链接中的
com.github.lybgeek.transactional
我这边就不贴具体代码了,为什么不贴,是因为我要介绍另外一种方案,就是基于spring的事件驱动实现
通过TransactionalEventListener注解+ApplicationEventPublisher
这是spring的事件驱动实现,或者说是观察者实现方式,不过TransactionalEventListener注解是spring4.2版本之后才提供的注解
通过这种方式如何改造上面邀请用户,添加积分的实现?
1、在邀请用户注册方法中,进行事件发布
伪代码如下
@Transactional(rollbackFor = Exception.class)
public Boolean inviteUser(..) {
userService.add(..);
applicationEventPublisher.publishEvent(..);
});
2、编写一个事务监听器,并在里面触发添加积分实现
伪代码如下
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void addIntegration(..){
integralService.addIntegration(..,20)
}
这边有个细节点要注意,就是监听事件的参数要和发布的参数一致
3、实现核心源码
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronization transactionSynchronization = createTransactionSynchronization(event);
TransactionSynchronizationManager.registerSynchronization(transactionSynchronization);
}
else if (this.annotation.fallbackExecution()) {
if (this.annotation.phase() == TransactionPhase.AFTER_ROLLBACK && logger.isWarnEnabled()) {
logger.warn("Processing " + event + " as a fallback execution on AFTER_ROLLBACK phase");
}
processEvent(event);
}
else {
// No transactional event execution at all
if (logger.isDebugEnabled()) {
logger.debug("No transaction is active - skipping " + event);
}
}
}
不知道大家发现没有,他本质上还是使用了TransactionSynchronizationManager,只是对他再一次进行封装
总结
在和朋友交流后,发现他们那个外包项目开发人员就只有三个,然后服务拆分了10来个,我就问他说这个外包项目业务有很复杂吗,他说其实还好,我就问他说业务不复杂,开发人员也不多,为什么不用单体架构,而要用微服务。他给我的答案是甲方爸爸觉得他们项目未来会承载很大的业务量,所以必须得用微服务,而且现在的主流技术栈是微服务。听到这个答复,我是该说是过度设计还是高瞻远瞩呢?技术日新月异,鬼知道后面会不会出现更厉害的东西,架构从来都不是一步到位,而是逐步演进
demo链接
https://github.com/lyb-geek/springboot-learning/tree/master/springboot-transation-after-commit