Spring Boot异步控制器配置后仍超时:80K+数据查询遇问题
嘿,我看了你遇到的问题——配置了异步但还是超时,核心问题其实出在异步没真正生效和单次返回80K+数据的性能瓶颈上,咱们一步步拆解解决:
1. 先把异步的坑填上:Controller里的阻塞调用是白忙活
你当前Controller里的CompletableFuture.allOf(positionList).join()和positionList.get()完全是画蛇添足!这俩方法会让处理请求的主线程一直等着异步任务做完,等于异步配置白搭了。Spring MVC本身支持返回CompletableFuture,会自动帮你释放主线程,等异步任务完成后再给客户端响应,根本不需要手动阻塞。
改完的Controller代码应该是这样:
@RequestMapping(value = "/v1/lr/fullpositionasync", produces = {APPLICATION_JSON_UTF8_VALUE}, method = RequestMethod.GET) @ResponseBody public CompletableFuture<List<Position>> retrieveTradePositionsFullAsync(HttpServletRequest request, HttpServletResponse response) { try { // 直接返回Service的异步任务就行,不用额外处理 return positionService.getFullPosition(); } catch(Exception e){ log.info("Error Occurred in Controller is:"+e.getMessage()); // 这里要返回失败的CompletableFuture,不然容器会一直挂着等结果 return CompletableFuture.failedFuture(e); } }
2. 治标又治本:解决80K数据返回的性能问题
说实话,单次返回80K条记录,不管异步与否,序列化JSON、网络传输都会耗很久,这才是超时的根源。有两个靠谱的优化方向:
方向一:分页查询(最推荐)
让客户端传pageNum和pageSize参数,后端只返回当前页的数据,比如每页1000条,80K数据分80次请求,每次响应时间能压到几十毫秒级。
方向二:流式返回(如果必须一次性给全量数据)
用Spring的StreamingResponseBody把数据流式输出,不用把所有数据加载到内存里,同时客户端能边接收边处理,减少等待时间。示例代码如下:
Controller层:
@RequestMapping(value = "/v1/lr/fullpositionstream", produces = {APPLICATION_JSON_UTF8_VALUE}, method = RequestMethod.GET) public StreamingResponseBody retrieveTradePositionsStream(HttpServletRequest request, HttpServletResponse response) { response.setContentType("application/json; charset=UTF-8"); return outputStream -> { ObjectMapper objectMapper = new ObjectMapper(); // 这里建议改成流式查询的DAO方法,避免一次性加载80K数据占满内存 Stream<Position> positionStream = positionDao.retrieveDataStream(); outputStream.write("[".getBytes(StandardCharsets.UTF_8)); Iterator<Position> iterator = positionStream.iterator(); boolean first = true; while (iterator.hasNext()) { if (!first) { outputStream.write(",".getBytes(StandardCharsets.UTF_8)); } first = false; objectMapper.writeValue(outputStream, iterator.next()); } outputStream.write("]".getBytes(StandardCharsets.UTF_8)); }; }
DAO层改成流式查询(避免内存爆炸):
public Stream<Position> retrieveDataStream() { return jdbcTemplate.query(ALL_POSITION_QUERY, rs -> { List<Position> batch = new ArrayList<>(100); do { Position position = new Position(); // 手动映射字段,或者复用你的BeanPropertyRowMapper逻辑 position.setId(rs.getLong("id")); // ...其他字段映射 batch.add(position); // 每100条输出一批,控制内存占用 if (batch.size() == 100) { Stream<Position> stream = batch.stream(); batch.clear(); return stream; } } while (rs.next()); return batch.stream(); }).flatMap(Function.identity()); }
3. 优化线程池配置:别搞1000个线程吓数据库
你现在的线程池setMaxPoolSize(1000)太夸张了!这么多线程同时去抢数据库连接,会直接把数据库连接池耗干,反而导致更多超时。建议根据数据库连接池的大小来配置:
@Bean(name = "asyncExecutor") public Executor asyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // 核心线程数建议设为CPU核心数*2,比如8核CPU就设16 executor.setCorePoolSize(Runtime.getRuntime().availableProcessors() * 2); // 最大线程数别超过数据库连接池的配置(比如数据库连接池设的是20,这里就设20) executor.setMaxPoolSize(20); // 加个任务队列,避免一下子涌进来的请求把线程池撑爆 executor.setQueueCapacity(100); executor.setWaitForTasksToCompleteOnShutdown(true); executor.setThreadNamePrefix("AsynchThreadForEndPoint-"); executor.initialize(); log.info("Executor is :"+executor.toString()); return executor; }
4. 别忘了查数据库本身的性能
最后,存储过程AllPositionProcedure()返回80K数据的速度怎么样?可以去数据库里单独执行这个存储过程看看耗时:
- 检查存储过程里的SQL有没有加索引
- 看看能不能只返回客户端需要的字段,减少数据量
- 考虑把存储过程改成支持分页的版本
总结一下:异步只是释放了Web容器的线程,但解决超时的核心还是要减少单次请求的数据量,优化查询和传输性能。
内容的提问来源于stack exchange,提问作者V3nky

