Spring Boot中双API数据处理性能优化方案咨询
性能优化方案
针对你遇到的Spring Boot应用串行调用API耗时过长、异步优化未达预期的问题,以下是几个可落地的优化方向:
1. 复用RestTemplate并配置HTTP连接池
每次调用新建RestTemplate是严重的性能浪费——RestTemplate本身是线程安全的,重复创建会导致TCP连接频繁建立/销毁,即使异步也会因为连接瓶颈拖慢速度。
解决方案:
创建全局单例的RestTemplate,并配置HttpClient连接池,复用TCP连接:
@Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate() { HttpClient httpClient = HttpClientBuilder.create() .setMaxConnTotal(100) // 最大总连接数 .setMaxConnPerRoute(50) // 每个域名的最大连接数 .setConnectionTimeToLive(30, TimeUnit.SECONDS) // 连接存活时间 .build(); HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient); factory.setConnectTimeout(5000); // 连接超时 factory.setReadTimeout(5000); // 读取超时 return new RestTemplate(factory); } }
之后在getDescriptionFromSecondAPI中注入这个全局RestTemplate,不再每次新建。
2. 优化线程池配置
Executors.newFixedThreadPool(100)的问题在于:
- 无界队列会导致任务堆积,线程数固定但连接池可能跟不上
- 未针对IO密集型任务优化(HTTP请求属于IO密集型,线程数建议设置为
CPU核心数 * 2 + 1,或根据连接池大小调整)
解决方案:
自定义ThreadPoolExecutor,配合连接池大小设置合理的线程数:
@Configuration @EnableAsync public class AsyncConfig { @Bean(name = "apiExecutor") public Executor apiExecutor() { int corePoolSize = Runtime.getRuntime().availableProcessors() * 2; ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(corePoolSize); executor.setMaxPoolSize(50); // 结合连接池总连接数调整 executor.setQueueCapacity(200); executor.setThreadNamePrefix("api-async-"); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 拒绝策略,避免任务丢失 executor.initialize(); return executor; } }
然后在调用第二个API的方法上添加@Async("apiExecutor"),返回CompletableFuture<String>:
@Async("apiExecutor") public CompletableFuture<String> getDescriptionFromSecondAPI(String id) { // 用注入的RestTemplate调用API String result = restTemplate.getForObject("xxx/api/desc/{id}", String.class, id); return CompletableFuture.completedFuture(result); }
处理时批量收集Future并等待结果:
List<FirstAPIResult> firstResults = getFirstAPIResults(); List<CompletableFuture<Void>> futures = new ArrayList<>(); for (FirstAPIResult result : firstResults) { CompletableFuture<String> descFuture = getDescriptionFromSecondAPI(result.getId()); CompletableFuture<Void> future = descFuture.thenAccept(desc -> result.setDescription(desc)); futures.add(future); } // 等待所有异步任务完成 CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
3. 尝试批量调用第二个API(如果支持)
如果第二个API提供批量查询接口,把所有需要查询的ID打包成一个请求,直接减少HTTP请求次数,这比并发单个请求效率提升更明显。
比如:
// 收集所有需要查询的ID List<String> ids = firstResults.stream().map(FirstAPIResult::getId).collect(Collectors.toList()); // 调用批量API Map<String, String> descMap = restTemplate.postForObject("xxx/api/desc/batch", ids, new ParameterizedTypeReference<Map<String, String>>() {}); // 批量设置描述 firstResults.forEach(result -> result.setDescription(descMap.get(result.getId())));
4. 增加缓存(如果数据允许)
如果第二个API返回的描述数据不会频繁变化,可以添加本地缓存(如Caffeine)或分布式缓存(如Redis),避免重复请求相同ID的数据:
@Cacheable(value = "apiDesc", key = "#id") public String getDescriptionFromSecondAPI(String id) { return restTemplate.getForObject("xxx/api/desc/{id}", String.class, id); }
内容的提问来源于stack exchange,提问作者Neelam Sharma
相关产品推荐
相关产品推荐

