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


    前言

    最近和朋友聊天,他说他承接的外包项目遇到了分布式事务问题,问我有没啥解决方案,我本可以直接跟他说,分布式事务方案网上一大堆,什么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

  • 相关阅读:
    python中如何对数据进行各种排序?
    js原型链
    js局部变量,参数
    计算字符串中每个字符出现次数
    推荐几个web中常用js图表插件
    getElementsByTagName("div")和$("div")区别
    Hadoop集群(第6期)JDK和SSH无密码配置
    Hadoop集群(第5期)SecureCRT使用
    Hadoop集群(第4期)VSFTP安装配置
    /etc/vsftpd/vsftpd.conf
  • 原文地址:https://www.cnblogs.com/linyb-geek/p/14716149.html
Copyright © 2020-2023  润新知