• Springboot @Async 失效的坑


    异步应用场景

    为了提高接口的响应性能,当业务非常复杂的情况下,可以将一部分跟业务关联性不是特别强的逻辑进行异步处理。如日志记录、短信发送、增加积分等。通常而言会将此类业务逻辑通过异步的方式进行处理,从而加快接口的响应速度,常用的解决方案有:

    • 使用JDK 自定义线程池 让代码异步执行
    • 在springboot 中 使用@Async注解进行异步处理
    • 使用中间件如mq 消息通知让下游异步消费 如RocketMQ、KAFKA

    使用第一种方式,需要精通线程池运行原理,结合实际的业务场景对队列大小进行合理的设置。队列设置过大过小都会存在内存溢出的风险。

    第三种方式是最合理的方式,它能够通过MQ进行削峰填谷,通过合理的参数配置,保证数据不会丢失。但是架构改动过大,对小型的单体应用来讲,工作量过大,成本过高。

    在springboot 大行其道的情况下,考虑开发成本,以及项目时间关系选用第二种方式来解决代码异步执行的问题。

    真实业务场景

    线上问题

    一个工单的分页列表,前端控制了每个列表最大的显示条数为100条。在业务流程中存在工单转移的操作,转移一笔工单至少包含以下几个重要的步骤:

    1. 新增工单处理日志,如什么时间点将工单转移给某人
    2. 修改工单当前处理人
    3. 发送企业微信给B端的跟进人(转移人)
    4. 发送im信息给C端的用户

    由于公司采用微服务架构,因此每个业务模块拆分的很细,在上述步骤中需要从其他系统中通过rpc调用接口拿到需要的数据才能完成整个业务流程数据的拼装,如需要从crm系统拿到组织架构信息,获取转移人的组织架构、需要从udb用户中心获取转移人的企业微信昵称等。

    因此在批量转移的时候,前端会出现调用超时的问题,原因是dubbo接口默认的超时时间是15秒,由于业务复杂,导致在15秒内执行不完业务逻辑。

    解决方案

    1. 将sql处理改为批量执行,如新增处理日志 (batch insert);修改当前工单,使用case when 的方式一次性修改完成(批量update)
    2. 将发送消息改成异步处理 加快前端接口的响应速度
    3. 让接口提供方提供批量查询的接口,避免rpc 循环调用在网络上的消耗

    优化完成之后,接口的响应速度由15秒多,变成了1秒。但是过程中遇到坑了。特此记录一下

    技术实现

    优化过程

    在这里插入图片描述

    @Async 注解定义为可以放置在方法上和类上,当使用在类上表明类所有的方法都能异步执行。在Springboot中是需要在方法上加上该注解就可以完美的实现异步执行。

    原始方法伪代码如下

    /**
     * 原始代码 采用流水式的代码一步步实现 业务逻辑
     */
    public void doBusiness(Object args){
      //1.  新增工单处理日志,如什么时间点将工单转移给某人
     
      //2. 修改工单当前处理人
      
      //3. 发送企业微信给B端的跟进人(转移人)
      
      //4. 发送im信息给C端的用户
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    那么异步问题就很好处理了,只需要将方法抽离形成多个子方法, 每个方法执行自己的业务处理逻辑,然后再方法加上@Async注解不就ok了么,伪代码如下

    public void doBusiness(Object args){
     	//2. 修改工单当前处理人
      
      this.doAsyncBusiness();
    }
    
    
    // 单独抽离一个异步执行的方法 加上@Async注解
    @Async
    private void doAsyncBusiness(Object args){
      //1.  新增工单处理日志,如什么时间点将工单转移给某人
      
      //3. 发送企业微信给B端的跟进人(转移人)
      
      //4. 发送im信息给C端的用户
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    打完收工,重启应用,进行测试,然而并没有像预期中的那样,接口的响应速度还是15秒左右。接着排查原因,可以肯定的是@Async是可以提供异步方法执行。应该是我们使用方式不对导致。

    @Async 限制

    熟悉Springboot AOP的同学可能会发现更改后的代码存在明显的问题

    • 首先AOP代理机制要求 被代理的方法必须是 public , private 方法不能被代理
    • 其次AOP代理机制会生成一个代理类 执行代理方法 注意 this.doAsyncBusiness() 调用的是本对象的方法 ;
    • 在启动类上加上@EnableAsync注解

    综上所述,原因我们已经通过AOP代理的原理找到了。下面摘自官方文档的一段话:

    • it must be applied to public methods only
    • self-invocation – calling the async method from within the same class – won’t work

    The reasons are simple – 「the method needs to be public」 so that it can be proxied. And 「self-invocation doesn’t work」 because it bypasses the proxy and calls the underlying method directly.

  • 相关阅读:
    【微信小程序】vertical属性、文章列表
    Ubuntu安装NVIDIA独立显卡驱动出现X service error问题解决方法
    cv2.imread无法读取图片
    java-php-net-python-代驾网站计算机毕业设计程序
    浅谈Python异步编程
    1200*C. Challenging Cliffs(模拟&构造&贪心)
    视频播放器—纹理-渲染-窗口
    STM32CubeMX教程23 FSMC - IS62WV51216(SRAM)驱动
    C++11标准模板(STL)- 算法(std::fill_n)
    android apk 加固后重新签名
  • 原文地址:https://blog.csdn.net/u013433591/article/details/127758630