背景
在 Servlet 3.0 中提供了在處理 servlet 或 filter 時可以在任何潛在阻塞的地方,進(jìn)行異步化,將阻塞部分的處理交給另外一個線程,當(dāng)前線程則可以繼續(xù)處理下一個請求
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 的好處顯而易見,可設(shè)置超時、以及超時之后的降級處理措施,并且 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 則高度的給程序提供了大量個性化的設(shè)置入口,如持有四大回調(diào)方法:當(dāng)前正在執(zhí)行、超時、錯誤異常、完成回調(diào)之后以及處理線程池的配置、超時配置
總結(jié)
以上邊是 Spring 中對 servlet 3.0 異步的支持,利用異步的特性能夠很好的將請求讀寫與具體業(yè)務(wù)處理分開,從而使得程序具備隔離性的支撐(不同耗時的接口,使用不同的處理線程池,避免耗時嚴(yán)重的接口影響正常的業(yè)務(wù)處理程序),但是異步的方式并不一定會降低時延,如在請求的線程池足夠用時,即便改造成異步,接口的單位處理速度不會發(fā)生改變,反而需要注意多線程帶來的 CPU 上下文切換的開銷,對于偏 IO 類型且響應(yīng)的時長較高時,因為發(fā)生 IO 阻塞時,并不會消耗 CPU, 因此這時候,我們使用異步將帶來更大的吞吐量