SpringBoot 異步處理請求

背景

在 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, 因此這時候,我們使用異步將帶來更大的吞吐量

Asynchronous Processing
spring 官方文檔

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容