Spring Boot微服务请求链调用出现Connection refused问题求助
这是典型的同步循环调用导致的线程死锁+Web容器线程池耗尽问题,具体逻辑链:
- App A的请求线程发起对App B的调用后,一直阻塞等待B的响应
- App B处理请求时反向调用App A,此时App A的Web容器线程池如果没有空闲线程(比如当前所有线程都在处理类似的阻塞请求),就无法接收新连接,直接返回「连接拒绝」
- 至于
SecurityContextHolder,默认是ThreadLocal存储上下文,可能在循环调用时引发认证上下文混乱,但这次的直接报错是连接拒绝,优先级先解决线程死锁问题
1. 打破循环调用(最优解)
重构业务逻辑从根源解决问题,推荐几种思路:
- 调用时提前传参:App A调用App B更新库存时,直接把B需要的A端数据一并传入,不需要B再反向请求
- 异步化处理:引入消息队列,将B需要A数据的操作改为异步消息,避免同步阻塞等待
- 抽离公共数据:把A和B共同依赖的数据放到独立的第三方服务(比如数据中心、配置服务),让双方都从第三方获取,消除循环依赖
2. 临时调整线程池配置
如果暂时无法重构业务,可以先增大App A的Web容器线程池,降低线程耗尽的概率:
以Tomcat为例,在application.yml中修改:
server: tomcat: threads: max: 200 # 增大最大线程数,默认200,可根据业务量调整 min-spare: 50 # 增加核心空闲线程数
Jetty、Undertow等容器可对应调整各自的线程池参数,原理一致
3. 处理内部调用的认证上下文
如果后续解决连接问题后出现认证失败,需要处理SecurityContextHolder的上下文传递:
在App B调用App A时,手动把当前线程的认证信息放到请求头中:
HttpHeaders headers = new HttpHeaders(); Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (auth != null && auth.getCredentials() instanceof String) { headers.setBearerAuth((String) auth.getCredentials()); } HttpEntity<Void> requestEntity = new HttpEntity<>(headers); restTemplate.exchange("http://appA/id", HttpMethod.GET, requestEntity, YourResponseClass.class);
也可以设置SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL)让子线程继承父线程上下文,但要注意异步场景下的线程安全问题
4. 排查服务可达性
确认App B能正确解析到App A的地址:
- 检查服务发现组件(如Eureka、Nacos)是否正常同步实例信息
- 在App B所在环境测试
curl http://appA/id,确认网络层面能连通
org.springframework.web.util.NestedServletException Request processing failed; nested exception is org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://appA/id": Connection refused (Connection refused); nested exception is java.net.ConnectException: Connection refused (Connection refused)
这个错误的直接原因是App B无法和App A建立TCP连接,本质是App A的Web容器没有可用线程处理新请求,而非网络不通(因为App A本身是运行状态,只是线程池被占满)
内容的提问来源于stack exchange,提问作者sone

