spring boot · 2024-03-31 0

SpringBoot 中异步请求和异步调用

一、异步请求

可以先释放容器分配给请求的线程与相关资源,减轻系统负担,释放了容器所分配线程的请求,其响应将被延后,可以在耗时处理完成(例如长时间的运算)时再对客户端进行响应。就是增加了服务器对客户端请求的吞吐量。

二、异步实现

1.Servlet方式实现异步请求

// curl -X GET "http://localhost:8080/servletReq"
// 执行 controller 方法前,ServletResponseMethodArgumentResolver 会处理 HttpServletResponse 参数,
// 并设置 mavContainer.setRequestHandled(true);
// 使得处理器不会查找 view,渲染 view
@GetMapping("/servletReq")
public void servletReq(HttpServletRequest request, HttpServletResponse response) {
    System.out.println("main threadName:" + Thread.currentThread().getName());

    AsyncContext asyncContext = request.startAsync();
    // 设置监听器:可设置其开始、完成、异常、超时等事件的回调处理
    asyncContext.addListener(new AsyncListener() {
        @Override
        public void onTimeout(AsyncEvent event) throws IOException {
            System.out.println(String.format("work threadName:%s. timeout", Thread.currentThread().getName()));
        }

        @Override
        public void onStartAsync(AsyncEvent event) throws IOException {
            System.out.println(String.format("work threadName:%s. startAsync", Thread.currentThread().getName()));
        }

        @Override
        public void onError(AsyncEvent event) throws IOException {
            System.out.println(String.format("work threadName:%s. error", Thread.currentThread().getName()));
        }

        @Override
        public void onComplete(AsyncEvent event) throws IOException {
            System.out.println(String.format("work threadName:%s. complete", Thread.currentThread().getName()));
        }
    });
    // 设置超时时间 20s
    asyncContext.setTimeout(20000);
    asyncContext.start(new Runnable() {
        @Override
        public void run() {
            try {
                System.out.println(String.format("work threadName:%s. success", Thread.currentThread().getName()));
                PrintWriter writer = asyncContext.getResponse().getWriter();

                for (int i = 0; i < 3; i++) {
                    TimeUnit.SECONDS.sleep(1);
                    writer.println("this is data");
                    writer.flush();
                }
            } catch (Exception e) {
                System.err.println(e.getMessage());
            }
            // 异步请求完成通知
            // 此时整个请求才完成
            asyncContext.complete();
        }
    });
}

结果:

main threadName:http-nio-8080-exec-1
work threadName:http-nio-8080-exec-2. success
work threadName:http-nio-8080-exec-3. complete

2.返回的 callable

// curl -X GET "http://localhost:8080/callableReq"
@GetMapping("/callableReq")
@ResponseBody
// CallableMethodReturnValueHandler 会处理返回值
// 使用 WebAsyncManager 中的 taskExecutor 执行线程
// taskExecutor 默认是 springboot 定义的 TaskExecutionAutoConfiguration#applicationTaskExecutor
// 可使用实现 WebMvcConfigurer#configureAsyncSupport,进而修改 WebAsyncManager 中的 taskExecutor
public Callable<String> callableReq() {
    System.out.println("main threadName:" + Thread.currentThread().getName());

    Callable<String> callable = () -> {
        TimeUnit.SECONDS.sleep(2);
        System.out.println(String.format("work threadName:%s. success", Thread.currentThread().getName()));
        return "callable!";
    };

    return callable;
}
@Configuration(proxyBeanMethods = false)
public class RequestAsyncPoolConfigurer implements WebMvcConfigurer {

    private static ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();

    static {
        threadPoolTaskExecutor.setCorePoolSize(5);
        threadPoolTaskExecutor.setMaxPoolSize(10);
        threadPoolTaskExecutor.setThreadNamePrefix("my-callable-req-task-");
        threadPoolTaskExecutor.initialize();
    }

    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
        // 处理 callable 超时
        configurer.setDefaultTimeout(5 * 1000);
        configurer.setTaskExecutor(threadPoolTaskExecutor);
    }
}

结果:

main threadName:http-nio-8080-exec-1
work threadName:my-callable-req-task-1. success

3.返回 WebAsyncTask

private static ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();

static {
    threadPoolTaskExecutor.setCorePoolSize(5);
    threadPoolTaskExecutor.setMaxPoolSize(10);
    threadPoolTaskExecutor.setThreadNamePrefix("my-web-async-req-task-");
    threadPoolTaskExecutor.initialize();
}

// curl -X GET "http://localhost:8080/webAsyncReq"
@GetMapping("webAsyncReq")
@ResponseBody
// AsyncTaskMethodReturnValueHandler 会处理返回值
public WebAsyncTask<String> webAsyncReq() {
    System.out.println("main threadName::" + Thread.currentThread().getName());

    Callable<String> result = () -> {
        TimeUnit.SECONDS.sleep(2);
        System.out.println(String.format("work threadName:%s. success", Thread.currentThread().getName()));
        return "success";
    };

    // WebAsyncTask<String> task = new WebAsyncTask<>(3000L, result);
    WebAsyncTask<String> task = new WebAsyncTask<>(5000L, threadPoolTaskExecutor, result);

    task.onTimeout(() -> {
        System.out.println(String.format("work threadName:%s. timeout", Thread.currentThread().getName()));
        return "timeout";
    });
    return task;
}

结果:

main threadName::http-nio-8080-exec-1
work threadName:my-web-async-req-task-1. success

4.返回 DeferredResult

// curl -X GET "http://localhost:8080/deferredResultReq"
@GetMapping("/deferredResultReq")
@ResponseBody
// DeferredResultMethodReturnValueHandler 会处理返回值
public DeferredResult<String> deferredResultReq() {
    System.out.println("main threadName::" + Thread.currentThread().getName());
    // 设置超时时间
    DeferredResult<String> result = new DeferredResult<String>(5 * 1000L);
    // 处理超时事件 采用委托机制
    result.onTimeout(() -> {
        System.out.println(String.format("work threadName:%s. timeout", Thread.currentThread().getName()));
        result.setResult("timeout!");
    });
    result.onCompletion(() -> {
        System.out.println(String.format("work threadName:%s. complete", Thread.currentThread().getName()));
        System.out.println("complete!");
    });

    new Thread(() -> {
        // 处理业务逻辑
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(String.format("work threadName:%s. success", Thread.currentThread().getName()));
        // 返回结果
        result.setResult("success!");
    }).start();

    return result;
}

结果:

main threadName::http-nio-8080-exec-2
work threadName:Thread-6. success
work threadName:http-nio-8080-exec-1. complete