聊聊如何在spring事务中正确进行远程调用

2021-04-29 14:43:30 浏览数 (1)

前言

最近和朋友聊天,他说他承接的外包项目遇到了分布式事务问题,问我有没啥解决方案,我本可以直接跟他说,分布式事务方案网上一大堆,什么tcc、可靠消息一致性、最大努力通知之类的,直接网上找个试下,比如直接用阿里的seata。但我并没有这么做,因为分布式事务,本来就是一个很复杂的课题,真正落地的时候,会发现有时候是多种分布式方案一起混用,而非一种方案走到黑。

因此我就跟他说,能不用分布式事务,就尽量不用,后来我就问了一下他的业务场景,场景也不是很复杂,就是邀请好友注册,然后可以增加积分,朋友实现逻辑的伪代码大概如下

代码语言:txt复制
    @Transactional(rollbackFor = Exception.class)
    public Boolean inviteUser(..){
        userService.add(..);
        integralService.addIntegration(..,20)
    }

其中integralService是一个远程积分服务,20为增加的积分值。这代码乍一看是没问题,我想可能很多朋友都会这么写。后边我就问朋友说你们这个业务场景是否允许如下场景

  • 允不允许邀请的用户入库成功,而积分入库失败?
  • 允不允许邀请的用户入库失败,而积分入库成功?

朋友思考了一下,说第二种不允许,第一种方式可以通过补偿的方式增加积分。

现在我们回过头来看这段代码,我抛出以下两个问题,看文章的朋友可以思考下

  • 如果添加积分请求耗时特别长,这段代码有没有问题?
  • 如果添加积分因为网络抖动原因出了异常,这段代码有没有问题?

这边说下我的想法

  • 耗时过长,会导致长事务的发生,在并发场景下,可能会导致数据库连接得不到释放
  • 网络抖动出了异常,可能会导致用户服务的添加逻辑进行回滚

解决耗时过长,有些朋友可能想到可以采用异步的方式,积分抖动异常,可以通过添加熔断机制,比如积分超时没响应,就直接进行熔断

今天我再说一种方案,就是在事务提交后再进行调用,罗里吧嗦一大堆,才刚要进入正题,哈哈

如何在spring的事务中正确的进行远程调用

通过spring的事务同步管理器

这个是个什么鬼,这是我直译,它的真身是长如下

代码语言:txt复制
org.springframework.transaction.support.TransactionSynchronizationManager

这玩意有啥用,可以利用它注册一个事务同步器,这个事务同步器,可以允许在事务提交后,做一些事情,核心代码如下

代码语言:txt复制
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
            @Override
            public void afterCommit() {
                //做你想做的业务
            }
        });

看了代码,想必大家都知道怎么改造上面邀请用户,添加积分了吧

代码语言:txt复制
 @Transactional(rollbackFor = Exception.class)
    public Boolean inviteUser(..) {
        userService.add(..);
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
            @Override
            public void afterCommit() {
                integralService.addIntegration(..,20)
            }
        });

但大家发现没有,每次都要写这么一坨代码,看着是不是很恶心,有没有什么改造的方案。

答案有的,通过注解 aop来整合实现,具体实现逻辑,可以查看下面demo链接中的

代码语言:txt复制
com.github.lybgeek.transactional

我这边就不贴具体代码了,为什么不贴,是因为我要介绍另外一种方案,就是基于spring的事件驱动实现

通过TransactionalEventListener注解 ApplicationEventPublisher

这是spring的事件驱动实现,或者说是观察者实现方式,不过TransactionalEventListener注解是spring4.2版本之后才提供的注解

通过这种方式如何改造上面邀请用户,添加积分的实现?

1、在邀请用户注册方法中,进行事件发布

伪代码如下

代码语言:txt复制
  @Transactional(rollbackFor = Exception.class)
  public Boolean inviteUser(..) {
        userService.add(..);
         applicationEventPublisher.publishEvent(..);
        });

2、编写一个事务监听器,并在里面触发添加积分实现

伪代码如下

代码语言:txt复制
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void addIntegration(..){
        integralService.addIntegration(..,20)
    }

这边有个细节点要注意,就是监听事件的参数要和发布的参数一致

3、实现核心源码

代码语言:txt复制
@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

0 人点赞