是否可以在 Spring Boot (TOMCAT) 中应用 bucketisation

Is this possible to apply bucketisation in Spring Boot (TOMCAT)

我暴露了2个api的

/endpoint/A/endpoint/B.

@GetMapping("/endpoint/A")
    public ResponseEntity<ResponseA> controllerA() throws InterruptedException {

        ResponseA responseA = serviceA.responseClient();
        return ResponseEntity.ok().body(responseA);
    }



@GetMapping("/endpoint/B")
    public ResponseEntity<ResponseA> controllerB() throws InterruptedException {

        ResponseA responseB = serviceB.responseClient();
        return ResponseEntity.ok().body(responseB);
    }

关于端点 A 内部调用 /endpoint/C 和端点 B 内部调用 [=38 的服务=]/endpoint/D

作为外部服务 /endpoint/D 需要更多时间,即从 获得响应/endpoint/A 需要更多时间,因此整个线程被卡住,影响 /endpoint/B.
我尝试使用具有以下实现的执行程序服务来解决此问题

@Bean(name = "serviceAExecutor")
    public ThreadPoolTaskExecutor serviceAExecutor(){

        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(100);
        taskExecutor.setMaxPoolSize(120);
        taskExecutor.setQueueCapacity(50);
        taskExecutor.setKeepAliveSeconds(120);
        taskExecutor.setThreadNamePrefix("serviceAExecutor");
        return taskExecutor;
    }

如果我在 /endpoint/A 上同时收到超过 200 个请求(大于默认的最大请求数) Tomcat 服务器中的线程)然后我没有收到来自 /endpoint/B 的响应,因为所有线程都忙于从端点 A 或在队列中。

请问有人可以建议有没有什么方法可以在每个公开的端点级别上应用分桶,一次只允许处理有限的请求并将剩余的请求放入 bucket/queue 以便其他端点上的请求可以正常工作?

编辑:- 以下是解决方法

@GetMapping("/endpoint/A")
        public CompletableFuture<ResponseEntity<ResponseA>> controllerA() throws InterruptedException {
    
            return CompletableFuture.supplyAsync(()->controllerHelperA());
        }



    @GetMapping("/endpoint/B")
        public CompletableFuture<ResponseEntity<ResponseB>> controllerB() throws InterruptedException {
    
            return CompletableFuture.supplyAsync(()->controllerHelperB());
        }

private ResponseEntity<ResponseA> controllerHelperA(){

        ResponseA responseA = serviceA.responseClient();
        return ResponseEntity.ok().body(responseA);
    }

private ResponseEntity<ResponseB> controllerHelperB(){

        ResponseB responseB = serviceB.responseClient();
        return ResponseEntity.ok().body(responseB);
    }

Spring MVC 支持在 Servlet API 3.0 中引入的异步 servlet API。为了让您的控制器 returns 成为 CallableCompletableFutureDeferredResult 时更容易,它将在后台线程中 运行 并释放请求处理线程以进行进一步处理.

@GetMapping("/endpoint/A")
public CompletableFuture<ResponseEntity<ResponseA>> controllerA() throws InterruptedException {
  return () {
    return controllerHelperA();
  }
}

private ResponseEntity<ResponseA> controllerHelperA(){

  ResponseA responseA = serviceA.responseClient();
  return ResponseEntity.ok().body(responseA);
}

现在这将在后台线程中执行。根据您的 Spring Boot 版本,如果您配置了自己的 TaskExecutor,它将

  1. 使用 SimpleAsycnTaskExecutor(这会在您的日志中发出警告),
  2. 默认提供 ThreadPoolTaskExecutor 可通过 spring.task.execution 命名空间进行配置
  3. 使用您自己的 TaskExecutor 但需要额外的配置。

如果您没有定义自定义 TaskExecutor 并且使用的是 Spring Boot 2.1 或更高版本 (IIRC) 的相对较新版本,您可以使用以下属性来配置 TaskExecutor.

spring.task.execution.pool.core-size=20
spring.task.execution.pool.max-size=120
spring.task.execution.pool.queue-capacity=50
spring.task.execution.pool.keep-alive=120s
spring.task.execution.thread-name-prefix=async-web-thread

通常这将用于在后台执行 Spring MVC 任务以及常规 @Async 任务。

如果您想要明确配置哪个 TaskExecutor 用于您的 Web 处理,您可以创建一个 WebMvcConfigurer 并实施 configureAsyncSupport 方法。

@Configuration
public class AsyncWebConfigurer implements WebMvcConfigurer {

  private final AsyncTaskExecutor taskExecutor;

  public AsyncWebConfigurer(AsyncTaskExecutor taskExecutor) {
    this.taskExecutor=taskExecutor;
  }

  public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
    configurer.setTaskExecutor(taskExecutor);
  }
}

您可以在构造函数参数上使用 @Qualifier 来指定要使用的 TaskExecutor