Spring Boot:如何将Rest服务控制器改为非阻塞模式
改造Spring Boot 1.5.x同步Controller为非阻塞异步模式
一、先搞懂为啥你的请求会阻塞
你的当前Controller是同步处理的:每个请求过来,Tomcat会分配一个线程全程处理,直到文件验证逻辑跑完才释放这个线程。负载高的时候,Tomcat的线程池很快就被占满,新请求只能排队等着,自然就出现阻塞了。
二、Spring 4.3.x能用上的异步方案
因为你用的是Spring Boot 1.5.x(对应Spring 4.3.x),还没到Spring 5的WebFlux时代,所以只能靠Spring MVC自带的异步特性来改造,主要有两种实用的方式:
- 用
Callable<T>作为接口返回值:适合简单的异步场景,把耗时逻辑丢到单独线程池执行,Tomcat请求线程提前释放 - 用
DeferredResult<T>作为接口返回值:更灵活,支持外部事件触发结果返回(比如调用异步服务、消息队列回调)
三、一步步动手改造
1. 先配置一个合适的异步线程池(强烈推荐)
默认Spring MVC用的SimpleAsyncTaskExecutor会每次创建新线程,高负载下容易出问题。咱们自定义一个线程池,通过配置类来搞定:
@Configuration @EnableAsync class AsyncConfig implements AsyncConfigurer { @Override Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor() // 根据你的服务器配置调整这些参数,比如CPU核心数是4的话,核心线程数设为8-10差不多 executor.setCorePoolSize(10) executor.setMaxPoolSize(20) executor.setQueueCapacity(100) executor.setThreadNamePrefix("ValidationAsync-") // 给线程起个名字,方便排查问题 executor.initialize() return executor } }
2. 改造你的ValidationController
方式一:用Callable快速改造
把原来的同步验证逻辑放到Callable的call()方法里,Spring会自动把这个任务提交到咱们配置的异步线程池,Tomcat的请求线程会立刻释放去处理其他请求。
@Controller @ResponseBody class ValidationController { @PostMapping("/validate") Callable<Map<String, Object>> validateFile(@RequestParam("clientId") String clientId, @RequestParam("file") MultipartFile file) { // 这里返回一个Callable对象,里面封装你的验证逻辑 return () -> { Map<String, Object> result = new HashMap<>() List<String> errors = new ArrayList<>() List<String> warnings = new ArrayList<>() // 把你原来的验证逻辑搬过来就行,现在这段代码会在异步线程里执行 doFileValidation(clientId, file, errors, warnings) result.put("errors", errors) result.put("warnings", warnings) return result } } // 把原来的验证逻辑抽成单独的方法,代码更清晰 private void doFileValidation(String clientId, MultipartFile file, List<String> errors, List<String> warnings) { // 你的业务验证逻辑:比如校验文件格式、根据clientId校验内容规则等等 if (file.isEmpty()) { errors.add("上传文件不能为空") } // 其他验证步骤... } }
方式二:用DeferredResult适配更复杂的异步场景
如果你的验证逻辑需要调用外部异步服务,或者要等其他事件触发结果,那DeferredResult更合适。它允许你在任意线程里设置返回结果,不用一直等着验证完成。
@Controller @ResponseBody class ValidationController { @Autowired private ValidationService validationService // 把验证逻辑抽到专门的服务类里 @PostMapping("/validate") DeferredResult<Map<String, Object>> validateFile(@RequestParam("clientId") String clientId, @RequestParam("file") MultipartFile file) { DeferredResult<Map<String, Object>> deferredResult = new DeferredResult<>() // 调用异步验证服务,在回调里设置结果 validationService.validateAsync(clientId, file) .whenComplete((result, throwable) -> { if (throwable != null) { // 处理异常,返回错误信息 deferredResult.setErrorResult("验证失败:" + throwable.getMessage()) } else { // 验证成功,返回结果 deferredResult.setResult(result) } }) return deferredResult } } // 异步验证服务示例 @Service class ValidationService { @Autowired private Executor asyncExecutor CompletableFuture<Map<String, Object>> validateAsync(String clientId, MultipartFile file) { // 用CompletableFuture把验证逻辑放到异步线程池执行 return CompletableFuture.supplyAsync(() -> { Map<String, Object> result = new HashMap<>() List<String> errors = new ArrayList<>() List<String> warnings = new ArrayList<>() doFileValidation(clientId, file, errors, warnings) result.put("errors", errors) result.put("warnings", warnings) return result }, asyncExecutor) } private void doFileValidation(String clientId, MultipartFile file, List<String> errors, List<String> warnings) { // 你的业务验证逻辑... } }
四、几个要注意的点
- 版本限制要记牢:Spring Boot 1.5.x不支持WebFlux,别想着用@RestController+WebFlux那套,老老实实搞Spring MVC的异步就行。
- 线程池参数要调对:核心线程数、最大线程数、队列容量得根据你的服务器配置来,比如CPU核心数多的话,可以适当加大核心线程数,避免线程太多导致上下文切换开销太大。
- 异常处理别漏了:Callable里的异常Spring会自动转换成500响应,但DeferredResult得手动处理异常,调用
setErrorResult()返回错误信息。 - Multipart异步支持:Spring Boot 1.5.x默认支持Multipart的异步处理,但如果你自定义了Tomcat配置,要确保开启了异步支持。
内容的提问来源于stack exchange,提问作者OTUser
相关产品推荐
相关产品推荐

