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.一些思考

异步方法的集成极为方便,可以有效提高接口响应速度,但是使用过程中要注意合理的分析业务逻辑及服务器资源承载能力,不可滥用。

对于强一致性的业务,需要注意,异步方法执行失败对于前部分的已执行的非异步操作是无影响的,因此在该场景异步并不可靠;

此外,对于并发量过大的任务,异步线程池的队列缓存也较为消耗服务器资源,需要合理规划,必要时建议采用更为可靠的消息队列等中间件。

posted @   雪山上的蒲公英  阅读(1537)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有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).
/* 返回顶部代码 */
点击右上角即可分享
微信分享提示

目录导航