简介
- 你是否还在为某些接口业务复杂、响应速度慢、并发量上不去而苦恼,今天给大家推荐一些小技巧,如何对复杂业务进行拆分、解耦。面对高并发可以记住这五点:1、异步,削峰填谷;2、缓存,缓存相对稳定高频热点数据,降低执行业务逻辑的性能开销;3、并行,缩短业务响应时间;4、优化你的业务代码,高效执行业务逻辑;5、限流和降级,保护你的核心服务在高并发下能正常工作。
本文将介绍如何缩短API接口的响应时间、提升系统单位时间内的并发量和吞吐量,内容有
- 应用场景分析
- 常用解决方案和技巧
- 接口异步化工具@Async的介绍
- 异步化应用启动类配置
- 利用@Async实现业务并行,提升接口响应速度
原文:传送门
注:本文基于springboot2.1.3.RELEASE 版本
1、应用场景分析和优化方案
聚合查询接口,需要组装返回所有后端api的响应数据
这类接口按传统的编程思维,会串行调用所有后端api,然后再组装,这样会导致接口响应时间会随着聚合的接口数呈比例增长,时间复杂度O(N)
优化方案
并行异步调用后端api接口
主线程监听所有接口调用情况,等待到最后一个执行完成后一起返回
分析:这样优化后,我们的聚合查询类接口响应时间最多只是与最慢的那个后端api接口速度相当而已,时间复杂度降到了O(1)。
注意事项: 此方式要求聚合查询接口内的各个业务接口间无依赖关系。
复杂多IO型业务接口,业务逻辑等待IO操作居多。
这类接口面对巨大流量压力时,往往表现为服务并发和吞吐量上不去,但服务器CPU、内存等资源充足
优化方案
从对接口响应内容的影响按业务进行拆分,将与接口响应数据相关的业务逻辑全拎出来,剩下的业务流程按需在各个阶段进行异步化处理,这部分可以走MQ、异步线程处理等等
将拎出来的那部分业务逻辑再进行拆分,找出可以并行处理的业务进行异步并行执行,如果依赖异步执行的数据时,可以监听并等待异步业务执行成功后再进行处理;
分析:优化思想就是,先砍掉部分和接口响应处理关系不大的业务,让他们在后台异步处理;再将其他业务操作按照业务操作间有无依赖关系进行拆分,可以并行的就尽量异步并行执行;最终统一处理响应内容再返回。
复杂多计算型业务接口,多个复杂耗时的计算流程。
这类接口往往耗费非常多的CPU,导致服务器并发和吞吐量上不去。
优化方案:
- 增加服务器CPU配置
- 如果数据实时性要求不高,可以对接口响应进行一定时效的缓存;
- 将计算逻辑先从业务范围进行拆分,交给不同的服务去执行;
- 增加服务实例个数,采用分布式计算方法MapReduce;
其他接口性能瓶颈场景
如DB、缓存、队列、或者一些服务器组件的性能瓶颈不在本篇文章内容,以后会有篇幅做专门讲解;
2、常用解决方案和技巧
-
异步,削峰填谷;
- 一般指将瞬时的大流量请求放到消息队列中,让系统逐个去处理,不至于瞬时流量毛刺导致服务完全不可用;
- 这样可以使服务器在其他时段内也能保持负荷工作,节约了服务器的性能;
- 还预留了一些时间让系统在面对突然的高负载时,增加新的服务实例进行服务;
-
缓存,缓存相对稳定高频热点数据,降低对后端业务服务和中间件服务的性能开销;
- 一般分为对热点数据进行缓存;
- 对高频访问接口响应进行缓存;
- 缓存又分分布式缓存、本地缓存,各个服务器中间件也提供了各个等级的缓存;
-
并行,缩短业务响应时间;
- 将可以并行的业务剥离出来,异步并行执行,缩短整体业务执行时间,提升系统单位时间吞吐量;
-
优化你的业务代码,高效执行业务逻辑;
- 这个没得说,根据实际情况梳理出业务执行流程,进行合理的优化即可;
-
限流和降级,保护你的核心服务在高并发下能正常工作;
- 这个一般在流量入口进行限制,保证我们的后端业务不被大流量击垮;
- 限流有很多种策略,常用的有对用户访问频率进行控制,对整体流量进行控制,避免后端业务处理不过来;
- 降级,其实是最后的无赖之举,断臂求生,把所有资源都提供给核心业务,保证核心业务的正常服务,边缘业务暂停服务;
3、接口异步化工具@Async的介绍
简介:
该工具可以为你的应用提供方便快捷的异步化执行业务的能力,只需要添加一个注解
@Async
既可以使你的业务异步执行,这里的异步执行,指的是新开一个线程执行你的业务;该注解可以使用到类上,也可以使用在方法上。
3.1 组件介绍
-
@EnableAsync
启用异步化能力注解推荐该注解配置在springboot的Config类或者启动类上,用于开启异步化能力,做一些异步化线程池和相关组件的初始化工作。
-
@Async
开启异步化模式注解基于
@Async
标注的方法,称之为异步方法;这些方法将在执行的时候,将会在独立的线程中被执行,调用者无需等待它的完成,即可继续其他的操作。该注解标注在类上,就代表调用该类的所有方法均会自动异步执行;标注在方法上,该方法就会异步执行;当调用该方法时,Async的切面会先向异步线程池申请一个线程,然后使用该线程执行该方法内的业务逻辑。
-
AsyncConfigurer
全局配置接口用于配置自定义异步线程池和异步线程执行异常捕获器,灵活定制合适的线程池和异常处理规则。
-
AsyncUncaughtExceptionHandler
异步化运行时全局异常捕获接口自定义异步线程池运行时异常统一处理方案。
-
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(); } } }