news 2026/7/8 1:16:55

Java 容器里 Spring Boot 接口“卡死”:curl 一直不返回的真实原因与排查实战(Tomcat 线程耗尽)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java 容器里 Spring Boot 接口“卡死”:curl 一直不返回的真实原因与排查实战(Tomcat 线程耗尽)

我在容器里跑 Spring Boot 时遇到过这种现象:

  • docker logs看着应用还在输出日志
  • 进入容器curl http://localhost:8080/health没有输出,一直卡住
  • 偶尔还能看到日志里出现http-nio-8080-exec-XX的线程号越来越大

明明健康检查接口只返回一个常量,为什么会卡死?这篇文章用一个真实案例,带你一步步定位根因,并给出正确的修复方式。


1. 现象复现:健康检查接口很简单,却一直卡住

示例代码(非常简单):

@RestController@RequestMapping("/health")publicclassHealthController{@GetMappingpublicIntegerhealth(){return0;}}

理论上它不可能卡住。但容器内执行:

curlhttp://localhost:8080/health

却一直没有输出。

与此同时,日志里还经常出现OkHttp查询日志(说明 HTTP 请求线程正在做外部调用)


2. 关键理解:健康检查“卡住”,通常不是接口逻辑卡,而是线程池被耗尽

Spring Boot 默认使用 Tomcat(Servlet 模型)时,每个请求需要占用一个 Tomcat 工作线程(常见线程名:http-nio-8080-exec-*)。

当以下情况出现时:

  • 大量请求在 Tomcat 线程里做阻塞操作
  • 或者外部调用没超时导致线程一直卡住
  • 导致 Tomcat 线程池逐渐被占满

那么新的请求(包括健康检查)就会出现:

TCP 连接建立了,但一直拿不到可用线程处理 → curl 就会一直“卡住”

这就是为什么“健康检查只是 return 0”也会卡的根本原因:请求根本没机会执行到 Controller


3. 快速判断:curl 卡在哪一步(建议新手必做)

进入容器执行(一定要加超时):

curl-v--max-time3http://127.0.0.1:8080/health

两种典型结果:

情况 A:Connection refused / Trying 一直卡住

说明端口没监听或网络不通(应用没启动好)。

情况 B:显示 Connected 但一直不返回

说明端口有服务,但应用层没返回,多数是线程池耗尽/阻塞


4. 实战排查:容器里没有 pgrep 怎么办?照样抓线程栈

很多生产镜像很精简(alpine/busybox),没有pgrep。没关系,有jcmd就够了。

4.1 先确认 Java 进程 PID(常见就是 1 或 7)

ps-ef|grep'[j]ava'

假设 PID 是 7。

4.2 导出线程 dump

jcmd7Thread.print-l>/tmp/th.txt

-l会把锁信息也打出来,排查死锁/阻塞很有用。

4.3 快速统计:Tomcat 工作线程有多少

grep-c'^"http-nio-8080-exec'/tmp/th.txtgrep'^"http-nio-8080-exec'/tmp/th.txt|head

如果数量接近你配置的server.tomcat.threads.max,基本可以判定:线程池已满

4.4 汇总每个 http-nio 线程的状态

awk' /^"http-nio-8080-exec-/ {name=$1} /java.lang.Thread.State:/ && name!="" {print name, $0; name=""} '/tmp/th.txt|head-n120

重点关注:

  • BLOCKED(锁竞争/死锁)
  • WAITING/TIMED_WAITING(在等某个条件/队列)
  • RUNNABLE但栈里是 socketRead0(实际上在等网络 IO)

4.5 把某一个卡住的 http-nio 线程完整栈打出来

例如http-nio-8080-exec-54,用下面命令截取它的块:

sed-n'/"http-nio-8080-exec-54"/,/^$/p'/tmp/th.txt

你也可以把所有http-nio里“包含RestTemplate/OkHttp”的线程名先找出来:

grep-n'"http-nio-8080-exec'-n/tmp/th.txt|headgrep-n'RestTemplate\|OkHttp\|socketRead0\|RoundRobinLoadBalancer\|nacos'/tmp/th.txt|head-n80

4.6 直接搜卡死关键词

egrep-n'socketRead0|OkHttp|RestTemplate|postForEntity|Hikari|Jedis|Lettuce|nacos|RoundRobinLoadBalancer|deadlock|BLOCKED'/tmp/th.txt|head-n200

如果你看到大量http-nio-8080-exec-*线程栈里包含:

  • java.net.SocketInputStream.socketRead0
  • org.springframework.web.client.RestTemplate.postForEntity
  • okhttp3.RealCall.execute

那几乎就是:外部 HTTP 调用没设置超时,导致线程被卡死

4.6 查有没有死锁

jcmd7Thread.find_deadlock

5. 解决办法:拆分两个 RestTemplate:@LoadBalanced 和 直连 IP 的普通 RestTemplate

thread dump看,根因已经非常明确:Tomcat的大量http-nio-8080-exec-*线程不是在处理业务逻辑,而是卡在RestTemplateApache HttpClient连接池里“等连接”

  • 直连IP:PORT,带连接池+超时
<dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId></dependency>
@BeanpublicRestTemplatedirectRestTemplate(RestTemplateBuilderb){// 1) 连接池PoolingHttpClientConnectionManagercm=newPoolingHttpClientConnectionManager();cm.setMaxTotal(200);cm.setDefaultMaxPerRoute(30);// 2) 超时(connectionRequestTimeout 防止“等连接池卡死”)RequestConfigrc=RequestConfig.custom().setConnectionRequestTimeout(1_000)// 等连接池 1s.setConnectTimeout(5_000)// 建连 5s.setSocketTimeout(60_000)// 读 60s(服务名调用一般稍长点).build();CloseableHttpClienthttpClient=HttpClients.custom().setConnectionManager(cm).setDefaultRequestConfig(rc).evictIdleConnections(30,TimeUnit.SECONDS).disableAutomaticRetries().build();HttpComponentsClientHttpRequestFactoryfactory=newHttpComponentsClientHttpRequestFactory(httpClient);factory.setConnectTimeout(5_000);factory.setReadTimeout(60_000);// 3) 用 builder 构建,便于继承 Boot 的 messageConverters 等returnb.requestFactory(()->factory).setConnectTimeout(Duration.ofSeconds(5))// Spring 层兜底.setReadTimeout(Duration.ofSeconds(60)).build();}
  • 服务名调用
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId></dependency>
@Bean@LoadBalancedpublicRestTemplatelbRestTemplate(RestTemplateBuilderb){// 1) 连接池PoolingHttpClientConnectionManagercm=newPoolingHttpClientConnectionManager();cm.setMaxTotal(200);cm.setDefaultMaxPerRoute(50);// 2) 超时(关键:connectionRequestTimeout 防止“等连接池卡死”)RequestConfigrc=RequestConfig.custom().setConnectionRequestTimeout(1_000)// 等连接池 1s.setConnectTimeout(1_000)// 建连 1s.setSocketTimeout(5_000)// 读 5s(服务名调用一般稍长点).build();CloseableHttpClienthttpClient=HttpClients.custom().setConnectionManager(cm).setDefaultRequestConfig(rc).evictIdleConnections(30,TimeUnit.SECONDS).disableAutomaticRetries().build();HttpComponentsClientHttpRequestFactoryfactory=newHttpComponentsClientHttpRequestFactory(httpClient);factory.setConnectTimeout(1_000);factory.setReadTimeout(5_000);// 3) 用 builder 构建,便于继承 Boot 的 messageConverters 等returnb.requestFactory(()->factory).setConnectTimeout(Duration.ofSeconds(1))// Spring 层兜底.setReadTimeout(Duration.ofSeconds(5)).build();}

使用

@Resource(name="lbRestTemplate")privateRestTemplatelbRestTemplate;@Resource(name="directRestTemplate")privateRestTemplatedirectRestTemplate;

关掉 LoadBalancer Retry(避免请求风暴)

spring.cloud.loadbalancer.retry.enabled=false

不同版本属性略有差异

6. 让健康检查永远可用:管理端口隔离

即使你修复了超时,生产环境仍建议把健康检查放到独立端口,避免业务流量影响探活。

management.server.port=8081 management.endpoints.web.exposure.include=health,metrics,threaddump management.endpoint.health.probes.enabled=true

探活改用:

curl--max-time2http://127.0.0.1:8081/actuator/health

这样业务 8080 再忙,健康检查也更稳定。


版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/7 22:58:19

CSDN官网私信功能联系GLM技术博主获取帮助

GLM-4.6V-Flash-WEB&#xff1a;如何让多模态大模型真正“跑得起来”&#xff1f; 在AI从实验室走向产线的今天&#xff0c;一个残酷的现实摆在开发者面前&#xff1a;很多号称“SOTA”的视觉语言模型&#xff0c;推理一次要等半秒以上&#xff0c;部署环境依赖几十个库&#x…

作者头像 李华
网站建设 2026/7/1 15:11:27

Vue-springboot一品萫茶馆管理系统的设计与实现

目录摘要关键词开发技术核心代码参考示例1.建立用户稀疏矩阵&#xff0c;用于用户相似度计算【相似度矩阵】2.计算目标用户与其他用户的相似度总结源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;摘要 随着信息技术的快速发展&#xff…

作者头像 李华
网站建设 2026/7/1 13:19:03

Vue-springboot学生成绩动态追踪系统课程资源课件下载设计与实现

目录摘要开发技术核心代码参考示例1.建立用户稀疏矩阵&#xff0c;用于用户相似度计算【相似度矩阵】2.计算目标用户与其他用户的相似度总结源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;摘要 该系统基于Vue.js与Spring Boot框架&…

作者头像 李华
网站建设 2026/7/1 13:27:48

Vue-springboot学生特长课程培训管理系统

目录Vue-SpringBoot学生特长课程培训管理系统摘要开发技术核心代码参考示例1.建立用户稀疏矩阵&#xff0c;用于用户相似度计算【相似度矩阵】2.计算目标用户与其他用户的相似度总结源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;Vue-S…

作者头像 李华
网站建设 2026/7/1 13:19:08

Spring Boot + 执行管道:让业务流程清晰可控

不知道你有没有遇到过这种情况&#xff1a;一个订单创建的接口&#xff0c;刚上线的时候只有几十行代码&#xff0c;逻辑也很简单。但随着业务迭代&#xff0c;三个月后变成了这样&#xff1a;public void createOrder(OrderRequest request) {// 参数校验if (request.getUserI…

作者头像 李华