SpringBoot巧用 @Async 提升API接口并发能力


prtyaa
prtyaa 2023-12-31 21:16:01 51094 赞同 0 反对 0
分类: 资源
简介 你是否还在为某些接口业务复杂、响应速度慢、并发量上不去而苦恼,今天给大家推荐一些小技巧,如何对复杂业务进行拆分、解耦。

面对高并发可以记住这五点:

  • 异步,削峰填谷;
  • 缓存,缓存相对稳定高频热点数据,降低执行业务逻辑的性能开销;
  • 并行,缩短业务响应时间;
  • 优化你的业务代码,高效执行业务逻辑;
  • 限流和降级,保护你的核心服务在高并发下能正常工作。

本文将介绍如何缩短API接口的响应时间、提升系统单位时间内的并发量和吞吐量,内容有

  • 应用场景分析
  • 常用解决方案和技巧
  • 接口异步化工具@Async的介绍
  • 异步化应用启动类配置
  • 利用@Async实现业务并行,提升接口响应速度
注:本文基于springboot2.1.3.RELEASE 版本

1、应用场景分析和优化方案

1.聚合查询接口,需要组装返回所有后端api的响应数据

这类接口按传统的编程思维,会串行调用所有后端api,然后再组装,这样会导致接口响应时间会随着聚合的接口数呈比例增长,时间复杂度O(N)

优化方案

分析:这样优化后,我们的聚合查询类接口响应时间最多只是与最慢的那个后端api接口速度相当而已,时间复杂度降到了O(1)

注意事项:此方式要求聚合查询接口内的各个业务接口间无依赖关系。

  • 并行异步调用后端api接口
  • 主线程监听所有接口调用情况,等待到最后一个执行完成后一起返回

2.复杂多IO型业务接口,业务逻辑等待IO操作居多。

这类接口面对巨大流量压力时,往往表现为服务并发和吞吐量上不去,但服务器CPU、内存等资源充足

优化方案

分析:优化思想就是,先砍掉部分和接口响应处理关系不大的业务,让他们在后台异步处理;再将其他业务操作按照业务操作间有无依赖关系进行拆分,可以并行的就尽量异步并行执行;最终统一处理响应内容再返回。

  • 从对接口响应内容的影响按业务进行拆分,将与接口响应数据相关的业务逻辑全拎出来,剩下的业务流程按需在各个阶段进行异步化处理,这部分可以走MQ、异步线程处理等等
  • 将拎出来的那部分业务逻辑再进行拆分,找出可以并行处理的业务进行异步并行执行,如果依赖异步执行的数据时,可以监听并等待异步业务执行成功后再进行处理;

3.复杂多计算型业务接口,多个复杂耗时的计算流程。

这类接口往往耗费非常多的CPU,导致服务器并发和吞吐量上不去。

优化方案

  • 增加服务器CPU配置
  • 如果数据实时性要求不高,可以对接口响应进行一定时效的缓存;
  • 将计算逻辑先从业务范围进行拆分,交给不同的服务去执行;
  • 增加服务实例个数,采用分布式计算方法MapReduce;

4.其他接口性能瓶颈场景

如DB、缓存、队列、或者一些服务器组件的性能瓶颈不在本篇文章内容,以后会有篇幅做专门讲解;学习资料:Java进阶视频资源

2、常用解决方案和技巧

1,异步,削峰填谷;

  • 一般指将瞬时的大流量请求放到消息队列中,让系统逐个去处理,不至于瞬时流量毛刺导致服务完全不可用;
  • 这样可以使服务器在其他时段内也能保持负荷工作,节约了服务器的性能;
  • 还预留了一些时间让系统在面对突然的高负载时,增加新的服务实例进行服务;

2,缓存,缓存相对稳定高频热点数据,降低对后端业务服务和中间件服务的性能开销;

  • 一般分为对热点数据进行缓存;
  • 对高频访问接口响应进行缓存;
  • 缓存又分分布式缓存、本地缓存,各个服务器中间件也提供了各个等级的缓存;

3,并行,缩短业务响应时间;

  • 将可以并行的业务剥离出来,异步并行执行,缩短整体业务执行时间,提升系统单位时间吞吐量;

4,优化你的业务代码,高效执行业务逻辑;

  • 这个没得说,根据实际情况梳理出业务执行流程,进行合理的优化即可;

5,限流和降级,保护你的核心服务在高并发下能正常工作;

  • 这个一般在流量入口进行限制,保证我们的后端业务不被大流量击垮;
  • 限流有很多种策略,常用的有对用户访问频率进行控制,对整体流量进行控制,避免后端业务处理不过来;
  • 降级,其实是最后的无赖之举,断臂求生,把所有资源都提供给核心业务,保证核心业务的正常服务,边缘业务暂停服务;

3、接口异步化工具@Async的介绍

简介:

该工具可以为你的应用提供方便快捷的异步化执行业务的能力,只需要添加一个注解@Async既可以使你的业务异步执行,这里的异步执行,指的是新开一个线程执行你的业务;该注解可以使用到类上,也可以使用在方法上。

3.1 组件介绍

1,@EnableAsync启用异步化能力注解

推荐该注解配置在springboot的Config类或者启动类上,用于开启异步化能力,做一些异步化线程池和相关组件的初始化工作。

2,@Async开启异步化模式注解

基于@Async标注的方法,称之为异步方法;这些方法将在执行的时候,将会在独立的线程中被执行,调用者无需等待它的完成,即可继续其他的操作。

该注解标注在类上,就代表调用该类的所有方法均会自动异步执行;标注在方法上,该方法就会异步执行;当调用该方法时,Async的切面会先向异步线程池申请一个线程,然后使用该线程执行该方法内的业务逻辑。

3,AsyncConfigurer全局配置接口

用于配置自定义异步线程池和异步线程执行异常捕获器,灵活定制合适的线程池和异常处理规则。

4,AsyncUncaughtExceptionHandler异步化运行时全局异常捕获接口

自定义异步线程池运行时异常统一处理方案。

5,AsyncExecutor异步化执行线程池

自定义异步执行线程池的大小、线程存活时间、队列信息等等,详情可以参考线程池的使用说明,这里就不展开讨论。学习资料:Java进阶视频资源

3.2 异步化方法使用示例和说明

说明:

  • 异步化注解@Async标注的方法返回值只能是void或者Future<T>
  • @Async所修饰的方法不要定义为static类型,这样异步调用不会生效
  • @Async所修饰的方法不能和@Transactional一起使用,因为会启用新的子线程来执行方法内的业务,主线程内的事务注解无法控制子线程的业务操作,原因就是事务存在线程隔离的原因,如果要加事务,请在方法内嵌套其他事务标注后的方法即可生效

示例:

无参数异步化接口

@Async
public void executeTask(){
   //业务操作
}

带参数异步化接口

@Async
public Future<Dto> task2(){
   //业务操作
   
   //返回操作结果
   return new AsyncResult<>(new Dto("danyuan",22));
}

@Data
@AllArgsConstructor
public class Dto implements Serializable{
/**
 *serialVersionUID
 */
private static final long serialVersionUID = 1L;

private String name;

private Integer age;

}

4、异步化应用启动类配置

应用配置启动类示例如下:

/**  
* Title StartAsyncServer.java  
* Description  
* @author danyuan
* @version 1.0.0
* site: www.danyuanblog.com
*/
package com.danyuanblog.test;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.lang.reflect.Method;
import java.util.concurrent.Executor;

import lombok.extern.slf4j.Slf4j;

import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@SpringBootApplication
@EnableAsync
public class StartAsyncServer implements AsyncConfigurer {

public static void main(String[] args) {
 SpringApplication.run(StartAsyncServer.class, args);
}

@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
 
 return new ExceptionHandler();
};

@Slf4j(topic="异步线程池运行时异常捕获器")
static class ExceptionHandler implements AsyncUncaughtExceptionHandler{

 /**
  * @author danyuan
  */
 @Override
 public void handleUncaughtException(Throwable e, Method method,
   Object... params) {//全局捕获异步执行异常并处理
  ByteArrayOutputStream outputStream = new ByteArrayOutputStream(1024);
        PrintStream printStream = new PrintStream(outputStream);
        e.printStackTrace(printStream);
        log.error(outputStream.toString());
 }
 
}
/**
 * @author danyuan
 */
@Override
public Executor getAsyncExecutor() {
 ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
       executor.setCorePoolSize(20);
       executor.setMaxPoolSize(400);
       executor.setQueueCapacity(10000);
       executor.setThreadNamePrefix("TestAsyncExecutor-");
       executor.initialize();
       return executor;
}
}

5、利用@Async实现业务并行,提升接口响应速度

业务场景是这样的,task1、task2之间无依赖关系,task3依赖与task2的操作结果,代码示例如下:

AsyncTestService.java

/**  
* Title AsyncTestService.java  
* Description  
* @author danyuan
* @version 1.0.0
* site: www.danyuanblog.com
*/
package com.danyuanblog.test.asyc;

import java.util.concurrent.Future;

import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;

@Service
public class AsyncTestService {

@Async
public void task1(){
 System.out.println("task1 execute begin .....");
 try {
  Thread.sleep(1000);
 } catch (InterruptedException e) {
  e.printStackTrace();
 }
 System.out.println("task1 execute success !");
}

@Async
public Future<Dto> task2(){
 System.out.println("task2 execute begin .....");
 try {
  Thread.sleep(6000);
 } catch (InterruptedException e) {
  e.printStackTrace();
 }
 System.out.println("task2 execute success !");
 return new AsyncResult<>(new Dto("danyuan",22));
}

@Async
public void task3(Dto dto){
 System.out.println("task3 execute begin .....");
 try {
  Thread.sleep(1000);
 } catch (InterruptedException e) {
  e.printStackTrace();
 }
 if(dto.getAge() > 18){
  System.out.println(dto.getName() "已成年!");
 }else{
  System.out.println(dto.getName() "未成年!");
 }
 System.out.println("task3 execute success !");
}

}

AsycTestController.java

/**  
* Title AsycTestController.java  
* Description  业务异步化测试
* @author danyuan
* @version 1.0.0
* site: www.danyuanblog.com
*/
package com.danyuanblog.test.asyc;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AsycTestController {

@Autowired
private AsyncTestService asyncTestService;
/**
 * 测试异步化任务
 * @author danyuan
 */
@GetMapping("/testAsync")
public void testAsync(){
 asyncTestService.task1();
 Future<Dto> result = asyncTestService.task2();
 while(true){//task3需要等待task2执行完成
  if(result.isDone() || result.isCancelled()){
   break;
  }
  try {
   Thread.sleep(1000);
  } catch (InterruptedException e) {
   e.printStackTrace();
  }
 }
 try {
  asyncTestService.task3(result.get());
 } catch (ExecutionException | InterruptedException e) {
  e.printStackTrace();
 }
}

}

如果您发现该资源为电子书等存在侵权的资源或对该资源描述不正确等,可点击“私信”按钮向作者进行反馈;如作者无回复可进行平台仲裁,我们会在第一时间进行处理!

评价 0 条
prtyaaL2
粉丝 1 资源 1949 + 关注 私信
最近热门资源
银河麒麟桌面操作系统备份用户数据  126
统信桌面专业版【全盘安装UOS系统】介绍  121
银河麒麟桌面操作系统安装佳能打印机驱动方法  114
银河麒麟桌面操作系统 V10-SP1用户密码修改  105
最近下载排行榜
银河麒麟桌面操作系统备份用户数据 0
统信桌面专业版【全盘安装UOS系统】介绍 0
银河麒麟桌面操作系统安装佳能打印机驱动方法 0
银河麒麟桌面操作系统 V10-SP1用户密码修改 0
作者收入月榜
1

prtyaa 收益393.62元

2

zlj141319 收益218元

3

1843880570 收益214.2元

4

IT-feng 收益209.03元

5

风晓 收益208.24元

6

777 收益172.71元

7

Fhawking 收益106.6元

8

信创来了 收益105.84元

9

克里斯蒂亚诺诺 收益91.08元

10

技术-小陈 收益79.5元

请使用微信扫码

加入交流群

请使用微信扫一扫!