Java17+Spring Boot批量调用第三方API超时问题求解决方案
解决思路
1. 优化多线程线程池配置
之前的多线程方案大概率是参数不合理导致的问题,第三方API普遍有并发调用限制,盲目开大量线程只会触发限流或耗尽连接池:
- 先确认API允许的并发调用上限,比如允许同时10次调用,就把线程池核心线程数设为10,最大线程数设为15左右,保留缓冲空间;
- 用
ThreadPoolExecutor自定义线程池,搭配有界任务队列,拒绝策略选CallerRunsPolicy,避免任务丢失的同时防止线程池过载; - 示例代码:
ThreadPoolExecutor executor = new ThreadPoolExecutor( 10, // 核心线程数 15, // 最大线程数 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100), // 有界任务队列 Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy() );
2. 分批次异步处理
如果API不支持批量查询,就分批次提交异步任务,避免瞬间打满API并发限制:
- 每次提交50-100个任务到线程池,等当前批次全部完成后再提交下一批,防止本地任务队列溢出;
- 结合Spring的
CompletableFuture做异步编排,用CompletableFuture.allOf()等待批次任务完成; - 示例代码:
int batchSize = 50; List<Owner> owners = ownerRepository.findAll(); for (int i = 0; i < owners.size(); i += batchSize) { int end = Math.min(i + batchSize, owners.size()); List<CompletableFuture<Void>> futures = owners.subList(i, end).stream() .map(owner -> CompletableFuture.runAsync(() -> { // 调用API获取共有人信息 CoOwner coOwner = thirdPartyApiClient.getCoOwner(owner.getId()); owner.setCoOwner(coOwner); }, executor)) .collect(Collectors.toList()); // 等待当前批次完成 CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); } // 统一写入文件 saveToFile(owners);
3. 增加重试与超时控制
API调用偶尔会出现超时或失败,不加处理会拖慢整体进度:
- 用
Guava Retryer或Spring的@Retryable实现重试逻辑,针对IOException、TimeoutException等异常重试3次,每次间隔1秒; - 给每个API调用设置超时时间,比如用
HttpClient设置5秒连接超时和读取超时,避免单个请求卡住; - 示例代码:
HttpClient httpClient = HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(5)) .build(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://third-party-api.com/co-owner/" + ownerId)) .timeout(Duration.ofSeconds(5)) .build(); HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
4. 断点续传机制
10000条数据中途失败后从头再来太浪费时间,可通过中间态持久化实现断点续传:
- 给所有者表新增
processed字段,默认false,处理完一条就标记为true,每次启动只查询未处理的记录; - 或者用临时文件记录已处理的ID列表,启动时读取列表跳过已处理数据。
5. 离线异步任务调度
如果不需要实时获取数据,把任务改成离线定时执行:
- 用Spring的
@Scheduled注解设置每天凌晨低峰期执行,比如@Scheduled(cron = "0 0 2 * * ?"); - 执行过程中按批次打印日志,方便跟踪进度和排查问题。
6. 优化数据库查询性能
有时候问题根源在数据库查询:
- 给所有者表的查询字段加索引,确保
findAll()快速返回数据; - 如果一次性查询10000条内存压力大,改用分页查询,每次查1000条,处理完再查下一页。
内容的提问来源于stack exchange,提问作者curious_coder
相关产品推荐
相关产品推荐

