springboot利用@Async提升API接口并发能力

简介
  • 你是否还在为某些接口业务复杂、响应速度慢、并发量上不去而苦恼,今天给大家推荐一些小技巧,如何对复杂业务进行拆分、解耦。面对高并发可以记住这五点:1、异步,削峰填谷;2、缓存,缓存相对稳定高频热点数据,降低执行业务逻辑的性能开销;3、并行,缩短业务响应时间;4、优化你的业务代码,高效执行业务逻辑;5、限流和降级,保护你的核心服务在高并发下能正常工作。

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

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

原文:传送门

注:本文基于springboot2.1.3.RELEASE 版本


1、应用场景分析和优化方案
  1. 聚合查询接口,需要组装返回所有后端api的响应数据

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

    优化方案

    • 并行异步调用后端api接口

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

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

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

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

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

    优化方案

    • 从对接口响应内容的影响按业务进行拆分,将与接口响应数据相关的业务逻辑全拎出来,剩下的业务流程按需在各个阶段进行异步化处理,这部分可以走MQ、异步线程处理等等

    • 将拎出来的那部分业务逻辑再进行拆分,找出可以并行处理的业务进行异步并行执行,如果依赖异步执行的数据时,可以监听并等待异步业务执行成功后再进行处理;

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

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

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

    优化方案:

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

    如DB、缓存、队列、或者一些服务器组件的性能瓶颈不在本篇文章内容,以后会有篇幅做专门讲解;

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异步化执行线程池

    自定义异步执行线程池的大小、线程存活时间、队列信息等等,详情可以参考线程池的使用说明,这里就不展开讨论。

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

说明:

  • 异步化注解@Async标注的方法返回值只能是void或者Future
  • @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
* @date Mar 8, 2020
* @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
    * @date Mar 13, 2020
    * @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
    * @date Mar 8, 2020
    * @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();
    		}
    	}
    
    }
    
    
  • 6
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值