前言

本篇文章介绍异步请求

一、概述

1.1 什么是异步请求

异步请求是一种非阻塞的处理模式,客户端发起请求后,服务器立即返回而不等待业务逻辑完成,实际的处理在后台执行,完成后通过回调或其他机制通知客户端。

1.2 为什么要用异步请求

提高系统吞吐量和并发处理能力,假设我们有100个 tomcat I/O 线程,我们现在有一个需求:需要等待30s后再响应给前端,如果没用异步请求那这个接口一旦被频繁调用,会导致系统直接卡死,无法处理任何业务
如果使用了异步请求不阻塞 tomcat 的 I/O 线程,由少量线程处理大量的等待请求,一旦到期(超时),立即响应。

二、使用方式

2.1 原生方式

/**
 * 使用 request 开启异步处理, 这种方式其实是 Servlet 的处理模式, springmvc 与 springboot 不推荐
 * 如果使用这个只能返回 void, 如果返回别的, springMVC 会关闭 response
 */
@GetMapping("/servlet")
public void t2(HttpServletRequest request, HttpServletResponse response) throws IOException {
    // 表示我们要开启异步请求了
    AsyncContext asyncContext = request.startAsync();
    // 设置 60s 超时时间
    asyncContext.setTimeout(60_000);
    asyncContext.start(() -> {
        try {
            // 模拟耗时操作
            Thread.sleep(3000);
            asyncContext.getResponse().getWriter().write("异步任务完成\n");
            response.getWriter().flush();
            // 异步处理完成
            asyncContext.complete();
        } catch (Exception e) {
            System.out.println("异常");
        }
    });
    response.setContentType("text/plain;charset=utf8");
    response.getWriter().write("主线程完成\n");
    response.getWriter().flush();
}

2.2 DeferredResult

@GetMapping("/deferred")
public DeferredResult<String> t1() {
    DeferredResult<String> deferredResult = new DeferredResult<>();
    log.info("defer-1");
    // 模拟耗时操作,例如数据库查询或远程服务调用
    new Thread(() -> {
        try {
            Thread.sleep(3000); // 模拟耗时操作
            log.info("defer-2");
            deferredResult.setResult("DeferredResult 任务完成"); // 设置结果
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // 恢复中断状态
        }
    }).start();
    return deferredResult;
}

2.3 CompletableFuture

@GetMapping("/waitMethod2")
public CompletableFuture<String> waitMethod2(String id) {
    CompletableFuture<String> waitingFuture = new CompletableFuture<>();
    CompletableFuture<String> timeoutFuture = CompletableFuture
            .supplyAsync(() -> "超时返回",
                    CompletableFuture.delayedExecutor(30, TimeUnit.SECONDS));
    waitingRequests2Map.put(id, waitingFuture);
    return waitingFuture.applyToEither(timeoutFuture, Function.identity())
            .whenComplete((result, throwable) -> {
                waitingRequests2Map.remove(id);
                if (!waitingFuture.isDone()) waitingFuture.cancel(false);
                if (!timeoutFuture.isDone()) timeoutFuture.cancel(false);
            });
}

2.4 Callable

@GetMapping("/callable")
public Callable<String> callable() {
    return () -> {
        // 模拟耗时操作
        Thread.sleep(3000);
        return "Callable 线程任务完成";
    };
}

2.5 WebAsyncTask

@GetMapping("/webAsyncTask")
public WebAsyncTask<String> webAsyncTask() {
     return new WebAsyncTask<>(3000, () -> {
         // 模拟耗时操作
         Thread.sleep(3000);
         return "WebAsyncTask 线程任务完成";
     });
 }

三、使用异步请求处理等待30s后返回

3.1 使用 DeferredResult

@GetMapping("/waitMethod1")
public DeferredResult<String> waitMethod1(String id) {
    DeferredResult<String> deferredResult = new DeferredResult<>(30_000L, "timeout");
    // 设置超时回调
    deferredResult.onTimeout(() -> {
        waitingRequests1Map.remove(id);
        log.info("DeferredResult {} 超时结束", id);
    });

    // 设置完成回调
    deferredResult.onCompletion(() -> {
        waitingRequests1Map.remove(id);
        log.info("DeferredResult {} 正常结束", id);
    });
    waitingRequests1Map.put(id, deferredResult);
    return deferredResult;
}

@GetMapping("/endWaitMethod1")
public String endWaitMethod1(String id) {
    DeferredResult<String> deferredResult = waitingRequests1Map.get(id);
    if (deferredResult != null && !deferredResult.hasResult()) {
        deferredResult.setResult("cancel");
    }
    return "end";
}

3.2 使用 CompletableFuture

@GetMapping("/waitMethod2")
public CompletableFuture<String> waitMethod2(String id) {
    CompletableFuture<String> waitingFuture = new CompletableFuture<>();
    CompletableFuture<String> timeoutFuture = CompletableFuture
            .supplyAsync(() -> "超时返回",
                    CompletableFuture.delayedExecutor(30, TimeUnit.SECONDS));
    waitingRequests2Map.put(id, waitingFuture);
    return waitingFuture.applyToEither(timeoutFuture, Function.identity())
            .whenComplete((result, throwable) -> {
                waitingRequests2Map.remove(id);
                if (!waitingFuture.isDone()) waitingFuture.cancel(false);
                if (!timeoutFuture.isDone()) timeoutFuture.cancel(false);
            });
}

@GetMapping("/endWaitMethod2")
public String endWaitMethod2(String id) {
    CompletableFuture<String> future = waitingRequests2Map.get(id);
    if (future != null && !future.isDone()) {
        future.complete("cancel");
    }
    return "end";
}
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐