API性能优化之异步

     很多时候为了达到SLA承诺(Service-Level Agreement)即为“服务水平承诺,我们需要拼命的优化我们的api的性能,其中最有效的便是异步了

异步和同步:

定义:同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)。

同步:一件事当成一件事做,顺序执行;

异步:一件事当作多件事做,可以并行执行,或者干脆全部交给另一个人做,自己只负责告诉那个人一声。

异步请求和同步请求:

同步请求:提交请求->等待服务器处理->处理完毕返回这个期间客户端浏览器不能干任何事

异步请求: 请求通过事件触发->服务器处理(这是浏览器仍然可以作其他事情)->处理完毕

可以先释放容器分配给请求的线程与相关资源,减轻系统负担,释放了容器所分配线程的请求,其响应将被延后,可以在耗时处理完成(例如长时间的运算)时再对客户端进行响应。一句话:增加了服务器对客户端请求的吞吐量(实际生产上我们用的比较少,如果并发请求量很大的情况下,我们会通过nginx把请求负载到集群服务的各个节点上来分摊请求压力,当然还可以通过消息队列来做请求的缓冲)。

异步调用:

异步请求的处理。除了异步请求,一般上我们用的比较多的应该是异步调用。通常在开发过程中,会遇到一个方法是和实际业务无关的,没有紧密性的。比如记录日志信息等业务。这个时候正常就是启一个新线程去做一些业务处理,让主线程异步的执行其他业务。异步调用对于api调用的性能提升是十分显著的,但是有一个缺陷,没办法等待api返回的数据,所以只能针对那些在后台运行而不十分不要返回值的数据,或者在异步方法中再调用一个方法去主动往客户端推送数据。

SpringBoot中异步调用的使用

  • 需要在启动类加入@EnableAsync使异步调用@Async注解生效

   在需要异步执行的方法上加入此注解即可@Async

测试:

可以看出,调用异步方法时,是立即返回的,基本没有耗时。

注意事项

  • 在默认情况下,未设置TaskExecutor时,默认是使用SimpleAsyncTaskExecutor这个线程池,但此线程不是真正意义上的线程池,因为线程不重用,每次调用都会创建一个新的线程。可通过控制台日志输出可以看出,每次输出线程名都是递增的。所以最好我们来自定义一个线程池。

自定义线程池

创建一个自定义的ThreadPoolTaskExecutor线程池:

此时,使用的是就只需要在@Async加入线程池名称即可:

  • 调用的异步方法,不能为同一个类的方法(包括同一个类的内部类),简单来说,因为Spring在启动扫描时会为其创建一个代理类,而同类调用时,还是调用本身的代理类的,所以和平常调用是一样的。其他的注解如@Cache等也是一样的道理,说白了,就是Spring的代理机制造成的。所以在开发中,最好把异步服务单独抽出一个类来管理。下面会重点讲述。

@Async异步失效

  1. 调用同一个类下注有@Async异步方法:在spring中像@Async和@Transactional、cache等注解本质使用的是动态代理,其实Spring容器在初始化的时候Spring容器会将含有AOP注解的类对象“替换”为代理对象(简单这么理解),那么注解失效的原因就很明显了,就是因为调用方法的是对象本身而不是代理对象,因为没有经过Spring容器,那么解决方法也会沿着这个思路来解决。
  2. 调用的是静态(static )方法
  3. 调用(private)私有化方法

异步请求与异步调用的区别

  • 两者的使用场景不同,异步请求用来解决并发请求对服务器造成的压力,从而提高对请求的吞吐量;而异步调用是用来做一些非主线流程且不需要实时计算和响应的任务,比如同步日志到kafka中做日志分析等。
  • 异步请求是会一直等待response相应的,需要返回结果给客户端的;而异步调用我们往往会马上返回给客户端响应,完成这次整个的请求,至于异步调用的任务后台自己慢慢跑就行,客户端不会关心。

调用同一个类下注有@Async异步方法:

在spring中像@Async和@Transactional、cache等注解本质使用的是动态代理,其实Spring容器在初始化的时候Spring容器会将含有AOP注解的类对象“替换”为代理对象(简单这么理解),那么注解失效的原因就很明显了,就是因为调用方法的是对象本身而不是代理对象,因为没有经过Spring容器,那么解决方法也会沿着这个思路来解决。

将要异步执行的方法单独抽取成一个类,原理就是当你把执行异步的方法单独抽取成一个类的时候,这个类肯定是被Spring管理的,其他Spring组件需要调用的时候肯定会注入进去,这时候实际上注入进去的就是代理类了。其实我们的注入对象都是从Spring容器中给当前Spring组件进行成员变量的赋值,由于某些类使用了AOP注解,那么实际上在Spring容器中实际存在的是它的代理对象。那么我们就可以通过上下文获取自己的代理对象调用异步方法

@Controller

@RequestMapping("/app")

public class EmailController {

 //获取ApplicationContext对象方式有多种,这种最简单,其它的大家自行了解一下

 @Autowired

 private ApplicationContext applicationContext;

  

  @RequestMapping(value = "/email/asyncCall", method = GET)

  @ResponseBody

  public Map<String, Object> asyncCall () {

    Map<String, Object> resMap = new HashMap<String, Object>();

    try{

  //这样调用同类下的异步方法是不起作用的

      //this.testAsyncTask();

  //通过上下文获取自己的代理对象调用异步方法

   EmailController emailController = (EmailController)applicationContext.getBean(EmailController.class);

   emailController.testAsyncTask();

      resMap.put("code",200);

    }catch (Exception e) {

  resMap.put("code",400);

      logger.error("error!",e);

    }

    return resMap;

  }

 //注意一定是public,且是非static方法

 @Async

  public void testAsyncTask() throws InterruptedException {

    Thread.sleep(10000);

    System.out.println("异步任务执行完成!");

  }

}

开启cglib代理,手动获取Spring代理类,从而调用同类下的异步方法。

首先,在启动类上加上@EnableAspectJAutoProxy(exposeProxy = true)注解。

代码实现,如下:

@Service

@Transactional(value = "transactionManager", readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = Throwable.class)

public class EmailService {

  @Autowired

  private ApplicationContext applicationContext;

  @Async

  public void testSyncTask() throws InterruptedException {

    Thread.sleep(10000);

    System.out.println("异步任务执行完成!");

  }

  public void asyncCallTwo() throws InterruptedException {

    //this.testSyncTask();

//    EmailService emailService = (EmailService)applicationContext.getBean(EmailService.class);

//    emailService.testSyncTask();

    boolean isAop = AopUtils.isAopProxy(EmailController.class);//是否是代理对象;

    boolean isCglib = AopUtils.isCglibProxy(EmailController.class); //是否是CGLIB方式的代理对象;

    boolean isJdk = AopUtils.isJdkDynamicProxy(EmailController.class); //是否是JDK动态代理方式的代理对象;

    //以下才是重点!!!

 EmailService emailService = (EmailService)applicationContext.getBean(EmailService.class);

    EmailService proxy = (EmailService) AopContext.currentProxy();

    System.out.println(emailService == proxy ? true : false);

    proxy.testSyncTask();

    System.out.println("end!!!");

  }

}

参考资料:https://www.jb51.net/article/159256.htm

https://www.cnblogs.com/baixianlong/p/10661591.html

https://blog.csdn.net/Dongguabai/article/details/80782081

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值