SpringBoot异步方法优化处理提高响应速度
1.前言
日常开发中,对于串行化的任务适当解耦耗时操作和业务逻辑,在保证结果准确性的前提下,使用异步方法适当进行并行化改造,可以提高接口响应速度,提升使用体验。
如下抽象的串行化工作流程:
业务查询,首先登记记录record
[cost 3s],之后依次执行searchA
[cost 1s]、searchB
[cost 2s]、searchC
[cost 2s]分别得到变量a、b、c,返回结果fx(a,b,c)
[计算耗时可忽略不记]。代码如下:
import com.zang.async.service.AsyncCaseService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.time.Duration;
import java.time.Instant;
@Slf4j
@RestController
public class AsyncCaseController {
@Resource
private AsyncCaseService asyncCaseService;
@PostMapping("/search/sync-test")
public int syncSearch(){
log.info("========test start=========");
Instant start = Instant.now();
asyncCaseService.record();
int a = asyncCaseService.searchA();
int b = asyncCaseService.searchB();
int c = asyncCaseService.searchC();
int result = a+b+c;
Instant end = Instant.now();
log.info("========test end=========cost time is {} seconds", Duration.between(start,end).getSeconds());
return result;
}
···
import org.springframework.stereotype.Service;
@Service
public class AsyncCaseServiceImpl implements AsyncCaseService{
@Override
public int searchA() {
try {
Thread.sleep(1000);//模拟业务处理耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
return 1;
}
@Override
public int searchB() {
//其他方法类似
执行结果:
2022-04-21 13:32:47.739 INFO 22764 --- [nio-8089-exec-2] com.zang.async.web.AsyncCaseController : ========test start=========
2022-04-21 13:32:55.762 INFO 22764 --- [nio-8089-exec-2] com.zang.async.web.AsyncCaseController : ========test end=========cost time is 8 seconds
经过分析,可以看到三个查询方法可以并行执行,等待都产生结果执行fx(a,b,c)
,record
方法执行的顺序和完成度不影响结果的返回,可以使用异步任务执行。改造逻辑抽象如下:
之后就代码实现展开阐述。
2.SpringBoot中的异步方法支持
SpringBoot已经提供了异步方法支持注解,因此不需要我们自己去创建维护线程或者线程池来异步的执行方法。
主要依靠两个注解:
@EnableAsync // 使用异步方法时需要提前开启(在启动类上或配置类上)
@Async // 被async注解修饰的方法由SpringBoot默认线程池(SimpleAsyncTaskExecutor)执行
2.1 获取(有返回值)异步方法的返回值
对于有返回值的异步方法,可使用java.util.concurrent.Future
类及其子类来接收异步方法返回值。
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;
import java.util.concurrent.Future;
@Service
public class AsyncCaseServiceImpl implements AsyncCaseService{
@Async
@Override
public Future<Integer> searchA() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return new AsyncResult<>(1);
}
//略
无返回值异步方法的异常捕获见3.3。
2.2 异步任务并行控制
接上节,在对Service中有返回值的方法进行异步改造的同时,业务处理侧需要添加并行控制,使并行的异步都返回结果才进行下一步操作:
import com.zang.async.service.AsyncCaseService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.Future;
@Slf4j
@RestController
public class AsyncCaseController {
@Resource
private AsyncCaseService asyncCaseService;
@PostMapping("/search/async-test")
public int asyncSearch() {
log.info("========test start=========");
Instant start = Instant.now();
asyncCaseService.record();
Future<Integer> searchAFuture = asyncCaseService.searchA();
Future<Integer> searchBFuture = asyncCaseService.searchB();
Future<Integer> searchCFuture = asyncCaseService.searchC();
while (true) {
if (searchAFuture.isDone() && searchBFuture.isDone() && searchCFuture.isDone()) {
break;
}
if (searchAFuture.isCancelled() || searchBFuture.isCancelled() || searchCFuture.isCancelled()) {
log.info("async work has cancelled , break");
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
int a = 0, b = 0, c = 0;
try {
a = searchAFuture.get();
b = searchBFuture.get();
c = searchCFuture.get();
} catch (Exception e) {
e.printStackTrace();
}
int result = a + b + c;
Instant end = Instant.now();
log.info("========test end=========cost time is {} seconds", Duration.between(start, end).getSeconds());
return result;
}
}
结果:
2022-04-21 14:23:35.486 INFO 19912 --- [nio-8089-exec-4] com.zang.async.web.AsyncCaseController : ========test start=========
2022-04-21 14:23:37.516 INFO 19912 --- [nio-8089-exec-4] com.zang.async.web.AsyncCaseController : ========test end=========cost time is 2 seconds
3.自定义线程池执行异步方法
@Async
使用了线程池org.springframework.core.task.SimpleAsyncTaskExecutor
来执行我们的异步方法,实际开发中我们也可以自定义自己的线程池,便于对线程池进行合理配置。
3.1 自定义线程池
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@EnableAsync
@Configuration
public class AsyncThreadPoolConfigure {
@Bean("asyncThreadPoolTaskExecutor")
public Executor asyncThreadPoolTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(4);
executor.setMaxPoolSize(4);
executor.setQueueCapacity(10);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("async-task-executor");
executor.setThreadGroupName("async-task-executor-group");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 所有任务结束后关闭线程池
//executor.setWaitForTasksToCompleteOnShutdown(true);
executor.initialize();
return executor;
}
}
3.2 在@Async注解上指定执行的线程池
@Async("asyncThreadPoolTaskExecutor")
@Override
public Future<Integer> searchA() {
try {
//略
以上,自定义线程池执行异步方法即完成。
3.3 自定义线程池监控
自定义的线程池配置的参数是否合理往往使人摸不着头脑,实际上,线程池执行器org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
为Spring自带的,在测试中可以创建新执行器,继承该执行器,重写submit
方法,对其增加监控,从而查看线程池状态,得到合适的线程池配置。
public class MonitorThreadPoolExecutor extends ThreadPoolTaskExecutor {
public void monitor(){
log.info("**** getActiveCount=={},getPoolSize=={},getLargestPoolSize=={},getTaskCount=={},getCompletedTaskCount=={},getQueue=={} ***",this.getThreadPoolExecutor().getActiveCount(),this.getThreadPoolExecutor().getPoolSize(),this.getThreadPoolExecutor().getLargestPoolSize(),this.getThreadPoolExecutor().getTaskCount(),this.getThreadPoolExecutor().getCompletedTaskCount(),this.getThreadPoolExecutor().getQueue().size());
}
@Override
public <T> Future<T> submit(Callable<T> task) {
monitor();
return super.submit(task);
}
}
在3.1自定义线程池时创建该监控执行器即可。
3.3 无返回值异步方法的异常捕获
以实现org.springframework.scheduling.annotation.AsyncConfigurer
接口的getAsyncExecutor
方法和getAsyncUncaughtExceptionHandler
方法改造配置类。
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.lang.reflect.Method;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@Slf4j
@EnableAsync
@Configuration
public class AsyncThreadPoolConfigure implements AsyncConfigurer {
//线程池创建方法为重写 getAsyncExecutor
@Bean("asyncThreadPoolTaskExecutor")
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(4);
executor.setMaxPoolSize(4);
executor.setQueueCapacity(10);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("async-task-executor");
executor.setThreadGroupName("async-task-executor-group");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 所有任务结束后关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.initialize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new AsyncExceptionHandler();
}
public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable throwable, Method method, Object... obj) {
log.error("Exception message is {}", throwable.getMessage());
log.error("Method name is {} ", method.getName());
for (Object param : obj) {
log.error("Parameter value - {}", param);
}
}
}
表现如下:
@Async("asyncThreadPoolTaskExecutor")
@Override
public void record() {
try {
Thread.sleep(3000);
log.info("current thread name is {}",Thread.currentThread().getName());
throw new RuntimeException("network not connect ");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
控制台:
2022-04-21 15:34:14.931 INFO 16596 --- [nio-8089-exec-1] com.zang.async.web.AsyncCaseController : ========test start=========
2022-04-21 15:34:16.965 INFO 16596 --- [nio-8089-exec-1] com.zang.async.web.AsyncCaseController : ========test end=========cost time is 2 seconds
2022-04-21 15:34:17.939 INFO 16596 --- [-task-executor1] c.z.async.service.AsyncCaseServiceImpl : current thread name is async-task-executor1
2022-04-21 15:34:17.940 ERROR 16596 --- [-task-executor1] c.z.a.c.AsyncThreadPoolConfigure : Exception message is network not connect
2022-04-21 15:34:17.941 ERROR 16596 --- [-task-executor1] c.z.a.c.AsyncThreadPoolConfigure : Method name is record
4.一些思考
异步方法的集成极为方便,可以有效提高接口响应速度,但是使用过程中要注意合理的分析业务逻辑及服务器资源承载能力,不可滥用。
对于强一致性的业务,需要注意,异步方法执行失败对于前部分的已执行的非异步操作是无影响的,因此在该场景异步并不可靠;
此外,对于并发量过大的任务,异步线程池的队列缓存也较为消耗服务器资源,需要合理规划,必要时建议采用更为可靠的消息队列等中间件。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
2017-04-21 [1-1] 把时间当做朋友(李笑来)Chapter 1 【心智的力量】 摘录
2017-04-21 Java反射总结
2017-04-21 No enclosing instance of type Demo is accessible. Must qualify the allocation with an enclosing instance of type Demo (e.g. x.new A() where x is an instance of Demo).