SpringBoot 异步处理请求

背景

在 Servlet 3.0 中提供了在处理 servlet 或 filter 时可以在任何潜在阻塞的地方,进行异步化,将阻塞部分的处理交给另外一个线程,当前线程则可以继续处理下一个请求

SpringBoot 中的支持

pom.xml:

    
      org.springframework.boot
      spring-boot-starter-web
      2.1.3.RELEASE
    

公共阻塞方法:

  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 callableAsync() {
    System.out
        .println(Thread.currentThread().getName() + "start callable "
            + DateFormatUtils.ISO_8601_EXTENDED_TIME_FORMAT.format(System.currentTimeMillis()));
    Callable 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 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 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, 因此这时候,我们使用异步将带来更大的吞吐量

Asynchronous Processing
spring 官方文档

你可能感兴趣的:(SpringBoot 异步处理请求)