背景
在 Servlet 3.0 中提供了在处理 servlet 或 filter 时可以在任何潜在阻塞的地方,进行异步化,将阻塞部分的处理交给另外一个线程,当前线程则可以继续处理下一个请求
SpringBoot 中的支持
pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.3.RELEASE</version>
</dependency>
公共阻塞方法:
public String execute() {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out
.println(Thread.currentThread().getName() + "real end "
+ DateFormatUtils.ISO_8601_EXTENDED_TIME_FORMAT.format(System.currentTimeMillis()));
return "complete";
}
1. Callable
@GetMapping("/callable")
public Callable<String> callableAsync() {
System.out
.println(Thread.currentThread().getName() + "start callable "
+ DateFormatUtils.ISO_8601_EXTENDED_TIME_FORMAT.format(System.currentTimeMillis()));
Callable<String> rs = this::execute;
System.out
.println(Thread.currentThread().getName() + "end callable "
+ DateFormatUtils.ISO_8601_EXTENDED_TIME_FORMAT.format(System.currentTimeMillis()));
return rs;
}
callable 就比较便捷了,至于原因则是 spring 会将 callable 的返回值包装成 WebAsyncTask ,具体功能在 3 中详述
2. DefferedResult (DeferredResult、ListenableFuture、CompletionStage)
@GetMapping("/deferred")
public DeferredResult<String> deferred() {
//1
DeferredResult deferredResult = new DeferredResult(10 * 1000L, "degrade");
System.out
.println(Thread.currentThread().getName() + "start deferred "
+ DateFormatUtils.ISO_8601_EXTENDED_TIME_FORMAT.format(System.currentTimeMillis()));
//2
Executors.newSingleThreadExecutor().execute(()->{
deferredResult.setResult(execute());
});
System.out
.println(Thread.currentThread().getName() + "end deferred "
+ DateFormatUtils.ISO_8601_EXTENDED_TIME_FORMAT.format(System.currentTimeMillis()));
return deferredResult;
}
使用 DeferredResult 的好处显而易见,可设置超时、以及超时之后的降级处理措施,并且 DeferredResult 是 public 的,意味着程序可以在一些特殊的场景个性化,如:增加继承之后增加一些字段
3. WebAsyncTask
@GetMapping("/webAsyncTask")
public WebAsyncTask<String> webAsyncTaskAsync() {
System.out
.println(Thread.currentThread().getName() + "start webAsyncTask "
+ DateFormatUtils.ISO_8601_EXTENDED_TIME_FORMAT.format(System.currentTimeMillis()));
WebAsyncTask webAsyncTask = new WebAsyncTask(10 * 1000L,
"simpleAsyncTaskExecutor", this::execute);
webAsyncTask.onTimeout(() -> "timeout");
webAsyncTask.onError(() -> "error");
System.out
.println(Thread.currentThread().getName() + "end webAsyncTask "
+ DateFormatUtils.ISO_8601_EXTENDED_TIME_FORMAT.format(System.currentTimeMillis()));
return webAsyncTask;
}
@Bean
public SimpleAsyncTaskExecutor simpleAsyncTaskExecutor() {
SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor(
"async-executor-");
taskExecutor.setConcurrencyLimit(10);
return taskExecutor;
}
WebAsyncTask 则高度的给程序提供了大量个性化的设置入口,如持有四大回调方法:当前正在执行、超时、错误异常、完成回调之后以及处理线程池的配置、超时配置
总结
以上边是 Spring 中对 servlet 3.0 异步的支持,利用异步的特性能够很好的将请求读写与具体业务处理分开,从而使得程序具备隔离性的支撑(不同耗时的接口,使用不同的处理线程池,避免耗时严重的接口影响正常的业务处理程序),但是异步的方式并不一定会降低时延,如在请求的线程池足够用时,即便改造成异步,接口的单位处理速度不会发生改变,反而需要注意多线程带来的 CPU 上下文切换的开销,对于偏 IO 类型且响应的时长较高时,因为发生 IO 阻塞时,并不会消耗 CPU, 因此这时候,我们使用异步将带来更大的吞吐量