RestTemplate调用挂起排查:微服务A调用B后无法继续执行
当微服务A调用restTemplate.postForEntity发起POST请求,且请求耗时超过10-15分钟时,服务会挂起,无法执行后续的LOG.info("Jobs done")代码。
已确认微服务B中所有任务都已完成:jobService::executeJob内的所有任务均运行完毕,所有日志代码也已执行,但微服务A仍处于挂起状态,无法继续执行。
该问题仅在处理大负载POST请求时出现,调试环境无法复现。相关配置信息:
- 使用默认RestTemplate,设置了无限超时
- 耗时几分钟的轻量POST请求可正常运行
微服务A代码
public void doJobs(Collection<Job> jobs) { restTemplate.postForEntity("http://localhost:8080/jobs", jobs, Void.class); LOG.info("Jobs done"); }
微服务B代码
@PostMapping(value = "/jobs") public void post(@RequestBody Collection<Job> jobs) { ForkJoinPool forkJoinPool = null; try { forkJoinPool = new ForkJoinPool(3); forkJoinPool.submit(() -> jobs.parallelStream().forEach(jobService::executeJob)).get(); } catch (InterruptedException | ExecutionException e) { LOG.error("Error doing jobs", e); throw new RuntimeException(e); } catch (Exception e) { LOG.error("Unknown error occurred", e); throw e; } finally { LOG.info("Checking if fork join pool still active, shutting down if yes"); if (forkJoinPool != null) { LOG.info("Shutting down fork join pool..."); forkJoinPool.shutdown(); } } LOG.info("Jobs completed, fork join pool successfully shut down"); }
核心问题1:ForkJoinPool未等待完全终止就返回响应
微服务B的finally块中仅调用forkJoinPool.shutdown(),该方法只是发起关闭请求,不会等待所有工作线程终止。虽然forkJoinPool.submit(...).get()会等待提交的主任务完成,但parallelStream()内部的子任务可能仍在后台执行,导致Tomcat的响应输出流未正确关闭,微服务A的RestTemplate会一直等待完整响应,最终挂起。
核心问题2:parallelStream与自定义ForkJoinPool绑定失效
parallelStream()默认会使用JVM的公共ForkJoinPool,即使将其包装在自定义ForkJoinPool的任务中提交,仍有概率出现部分子任务跑到公共池执行的情况,导致get()返回后仍有后台任务在运行,干扰响应的正常结束。
修复方案
等待ForkJoinPool完全终止后再返回
在finally块的shutdown()后添加等待逻辑,确保所有线程结束:finally { LOG.info("Checking if fork join pool still active, shutting down if yes"); if (forkJoinPool != null) { LOG.info("Shutting down fork join pool..."); forkJoinPool.shutdown(); // 设置足够长的等待时间,匹配业务最大耗时 try { if (!forkJoinPool.awaitTermination(30, TimeUnit.MINUTES)) { forkJoinPool.shutdownNow(); LOG.warn("ForkJoinPool forced to shut down due to timeout"); } } catch (InterruptedException e) { forkJoinPool.shutdownNow(); Thread.currentThread().interrupt(); LOG.error("Interrupted while waiting for ForkJoinPool shutdown", e); } } }替换parallelStream,直接用ForkJoinPool提交任务
放弃parallelStream(),改用ForkJoinPool的原生API提交任务,确保所有任务都在自定义池内执行:// 替换原try块内的代码 forkJoinPool.submit(() -> { jobs.forEach(job -> forkJoinPool.submit(() -> jobService.executeJob(job)).join()); }).get();为RestTemplate设置合理超时
取消无限超时配置,根据业务最大预期耗时设置超时时间,避免服务长时间无响应挂起:SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); factory.setConnectTimeout(5000); // 连接超时5秒 factory.setReadTimeout(1800000); // 读取超时30分钟,按需调整 RestTemplate restTemplate = new RestTemplate(factory);
内容的提问来源于stack exchange,提问作者valeesi

