news 2026/7/2 6:01:33

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/6/30 0:20:19

Vue-springboot新疆在线旅游网站的设计与实现

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

作者头像 李华
网站建设 2026/6/29 10:07:33

sourcefare速成手册(6) - 集成soular,使用soular用户统一认证登录

sourcefare 是一款开源免费的代码扫描工具&#xff0c;支持免费私有化部署&#xff0c;轻量、简洁易用。本文将详细介绍如何安装sourcefaresoular&#xff0c;实现统一认证登录。 1、soular 安装 1.1 安装 本文以CentOS操作系统为例。 下载&#xff0c;CentOS安装包下载地址…

作者头像 李华
网站建设 2026/7/1 20:29:28

Arbess速成手册(9) - 集成GitLab实现Python项目自动化构建并主机部署

Arbess 是一款开源免费的 CI/CD 工具&#xff0c;支持免费私有化部署&#xff0c;一键安装零配置&#xff0c;页面设计简洁明了。本文将详细介绍如何安装Arbess、GitLab&#xff0c;创建流水线实现 Python 项目自动化部署。 1、GitLab 安装与配置 本章节将介绍如何使用CentOS…

作者头像 李华
网站建设 2026/7/1 2:56:52

如何正确配置Dify响应类型:90%工程师忽略的关键细节

第一章&#xff1a;Dify响应类型配置的核心概念在构建智能应用时&#xff0c;Dify平台通过灵活的响应类型配置机制&#xff0c;使开发者能够精确控制AI模型输出的格式与结构。这一机制不仅提升了前后端数据交互的稳定性&#xff0c;也增强了用户体验的一致性。响应类型的定义与…

作者头像 李华
网站建设 2026/6/13 9:04:51

GitHub镜像网站fork项目参与GLM社区贡献

GitHub镜像网站Fork项目参与GLM社区贡献 在国产大模型加速落地的今天&#xff0c;一个现实问题始终困扰着许多开发者&#xff1a;如何稳定、高效地获取前沿开源项目并参与共建&#xff1f;尤其当核心仓库位于GitHub&#xff0c;而网络访问受限时&#xff0c;这一挑战尤为突出。…

作者头像 李华