文章目录
前言
异步支持是在Servlet 3.0中引入的,简单来说,它允许在请求接收器线程之外的另一个线程中处理HTTP请求。
从Spring 3.2开始可用的DeferredResult有助于将长时间运行的计算从http-worker线程卸载到单独的线程。
尽管另一个线程将占用一些资源来进行计算,但同时不会阻止工作线程,并且可以处理传入的客户端请求。
异步请求处理模型非常有用,因为它有助于在高负载期间很好地扩展应用程序,尤其是对于IO密集型操作。
使用DeferredResult 进行的同步和异步通信,还将比较异步如何更好地扩展以应对高负载和IO密集型用例。
1. 阻塞的REST服务
让我们从开发标准的阻塞REST服务开始:
@GetMapping("/process-blocking")
public ResponseEntity<?> handleReqSync(Model model) {
// ...
return ResponseEntity.ok("ok");
}
这里的问题是请求处理线程被阻塞,直到处理完完整的请求并返回结果为止。对于长时间运行的计算,这是次优的解决方案。
2. 使用DeferredResult的非阻塞REST
为了避免阻塞,我们将使用基于回调的编程模型,在该模型中,我们将DeferredResult返回到servlet容器,而不是实际结果 。
@GetMapping("/async-deferredresult")
public DeferredResult<ResponseEntity<?>> handleReqDefResult(Model model) {
LOG.info("Received async-deferredresult request");
DeferredResult<ResponseEntity<?>> output = new DeferredResult<>();
ForkJoinPool.commonPool().submit(() -> {
LOG.info("Processing in separate thread");
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
}
output.setResult(ResponseEntity.ok("ok"));
});
LOG.info("servlet thread freed");
return output;
}
请求处理在单独的线程中完成,一旦完成,我们将对DeferredResult对象调用setResult操作。
让我们看一下日志输出,以检查我们的线程是否按预期运行:
[nio-8080-exec-6] com.baeldung.controller.AsyncDeferredResultController:
Received async-deferredresult request
[nio-8080-exec-6] com.baeldung.controller.AsyncDeferredResultController:
Servlet thread freed
[nio-8080-exec-6] java.lang.Thread : Processing in separate thread
在内部,将通知容器线程,并将HTTP响应传递到客户端。容器(servlet 3.0或更高版本)将保持打开连接,直到响应到达或超时。
3. DeferredResult回调
我们可以使用DeferredResult注册三种回调类型:完成,超时和错误回调。
让我们使用*onCompletion()*方法定义异步请求完成时执行的代码块:
deferredResult.onCompletion(() -> LOG.info("Processing complete"));
同样,我们可以使用onTimeout()注册自定义代码以在发生超时时调用。为了限制请求处理时间,我们可以在DeferredResult对象创建过程中传递一个超时值 :
DeferredResult<ResponseEntity<?>> deferredResult = new DeferredResult<>(500l);
deferredResult.onTimeout(() ->
deferredResult.setErrorResult(
ResponseEntity.status(HttpStatus.REQUEST_TIMEOUT)
.body("Request timeout occurred.")));
如果发生超时,我们将通过DeferredResult注册的超时处理程序来设置不同的响应状态。
让我们通过处理一个超过5秒的定义超时值的请求来触发超时错误:
ForkJoinPool.commonPool().submit(() -> {
LOG.info("Processing in separate thread");
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
...
}
deferredResult.setResult(ResponseEntity.ok("OK")));
});
让我们看一下日志:
[nio-8080-exec-6] com.baeldung.controller.DeferredResultController:
servlet thread freed
[nio-8080-exec-6] java.lang.Thread: Processing in separate thread
[nio-8080-exec-6] com.baeldung.controller.DeferredResultController:
Request timeout occurred
在某些情况下,由于某些错误或异常长时间运行的计算将失败。在这种情况下,我们还可以注册一个*onError()*回调:
deferredResult.onError((Throwable t) -> {
deferredResult.setErrorResult(
ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("An error occurred."));
});
如果发生错误,在计算响应时,我们将通过此错误处理程序设置不同的响应状态和消息正文。
🍎QQ群【837324215】
🍎关注我的公众号【Java大厂面试官】,一起学习呗🍎🍎🍎