中国水运建设行业协会网站上海网站关键词优化
2026/4/18 19:45:33 网站建设 项目流程
中国水运建设行业协会网站,上海网站关键词优化,中国十大公关公司,济南微信网站开发我在容器里跑 Spring Boot 时遇到过这种现象#xff1a; docker logs 看着应用还在输出日志进入容器 curl http://localhost:8080/health 没有输出#xff0c;一直卡住偶尔还能看到日志里出现 http-nio-8080-exec-XX 的线程号越来越大 明明健康检查接口只返回一个常量#x…我在容器里跑 Spring Boot 时遇到过这种现象docker logs看着应用还在输出日志进入容器curl http://localhost:8080/health没有输出一直卡住偶尔还能看到日志里出现http-nio-8080-exec-XX的线程号越来越大明明健康检查接口只返回一个常量为什么会卡死这篇文章用一个真实案例带你一步步定位根因并给出正确的修复方式。1. 现象复现健康检查接口很简单却一直卡住示例代码非常简单RestControllerRequestMapping(/health)publicclassHealthController{GetMappingpublicIntegerhealth(){return0;}}理论上它不可能卡住。但容器内执行curlhttp://localhost:8080/health却一直没有输出。与此同时日志里还经常出现OkHttp查询日志说明 HTTP 请求线程正在做外部调用2. 关键理解健康检查“卡住”通常不是接口逻辑卡而是线程池被耗尽Spring Boot 默认使用 TomcatServlet 模型时每个请求需要占用一个 Tomcat 工作线程常见线程名http-nio-8080-exec-*。当以下情况出现时大量请求在 Tomcat 线程里做阻塞操作或者外部调用没超时导致线程一直卡住导致 Tomcat 线程池逐渐被占满那么新的请求包括健康检查就会出现TCP 连接建立了但一直拿不到可用线程处理 → curl 就会一直“卡住”这就是为什么“健康检查只是 return 0”也会卡的根本原因请求根本没机会执行到 Controller。3. 快速判断curl 卡在哪一步建议新手必做进入容器执行一定要加超时curl-v--max-time3http://127.0.0.1:8080/health两种典型结果情况 AConnection refused / Trying 一直卡住说明端口没监听或网络不通应用没启动好。情况 B显示 Connected 但一直不返回说明端口有服务但应用层没返回多数是线程池耗尽/阻塞。4. 实战排查容器里没有 pgrep 怎么办照样抓线程栈很多生产镜像很精简alpine/busybox没有pgrep。没关系有jcmd就够了。4.1 先确认 Java 进程 PID常见就是 1 或 7ps-ef|grep[j]ava假设 PID 是 7。4.2 导出线程 dumpjcmd7Thread.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实际上在等网络 IO4.5 把某一个卡住的 http-nio 线程完整栈打出来例如http-nio-8080-exec-54用下面命令截取它的块sed-n/http-nio-8080-exec-54/,/^$/p/tmp/th.txt你也可以把所有http-nio里“包含RestTemplate/OkHttp”的线程名先找出来grep-nhttp-nio-8080-exec-n/tmp/th.txt|headgrep-nRestTemplate\|OkHttp\|socketRead0\|RoundRobinLoadBalancer\|nacos/tmp/th.txt|head-n804.6 直接搜卡死关键词egrep-nsocketRead0|OkHttp|RestTemplate|postForEntity|Hikari|Jedis|Lettuce|nacos|RoundRobinLoadBalancer|deadlock|BLOCKED/tmp/th.txt|head-n200如果你看到大量http-nio-8080-exec-*线程栈里包含java.net.SocketInputStream.socketRead0org.springframework.web.client.RestTemplate.postForEntityokhttp3.RealCall.execute那几乎就是外部 HTTP 调用没设置超时导致线程被卡死。4.6 查有没有死锁jcmd7Thread.find_deadlock5. 解决办法拆分两个 RestTemplateLoadBalanced 和 直连 IP 的普通 RestTemplate从thread dump看根因已经非常明确Tomcat的大量http-nio-8080-exec-*线程不是在处理业务逻辑而是卡在RestTemplate的Apache HttpClient连接池里“等连接”直连IP:PORT带连接池超时dependencygroupIdorg.apache.httpcomponents/groupIdartifactIdhttpclient/artifactId/dependencyBeanpublicRestTemplatedirectRestTemplate(RestTemplateBuilderb){// 1) 连接池PoolingHttpClientConnectionManagercmnewPoolingHttpClientConnectionManager();cm.setMaxTotal(200);cm.setDefaultMaxPerRoute(30);// 2) 超时connectionRequestTimeout 防止“等连接池卡死”RequestConfigrcRequestConfig.custom().setConnectionRequestTimeout(1_000)// 等连接池 1s.setConnectTimeout(5_000)// 建连 5s.setSocketTimeout(60_000)// 读 60s服务名调用一般稍长点.build();CloseableHttpClienthttpClientHttpClients.custom().setConnectionManager(cm).setDefaultRequestConfig(rc).evictIdleConnections(30,TimeUnit.SECONDS).disableAutomaticRetries().build();HttpComponentsClientHttpRequestFactoryfactorynewHttpComponentsClientHttpRequestFactory(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();}服务名调用dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-loadbalancer/artifactId/dependencydependencygroupIdorg.apache.httpcomponents/groupIdartifactIdhttpclient/artifactId/dependencyBeanLoadBalancedpublicRestTemplatelbRestTemplate(RestTemplateBuilderb){// 1) 连接池PoolingHttpClientConnectionManagercmnewPoolingHttpClientConnectionManager();cm.setMaxTotal(200);cm.setDefaultMaxPerRoute(50);// 2) 超时关键connectionRequestTimeout 防止“等连接池卡死”RequestConfigrcRequestConfig.custom().setConnectionRequestTimeout(1_000)// 等连接池 1s.setConnectTimeout(1_000)// 建连 1s.setSocketTimeout(5_000)// 读 5s服务名调用一般稍长点.build();CloseableHttpClienthttpClientHttpClients.custom().setConnectionManager(cm).setDefaultRequestConfig(rc).evictIdleConnections(30,TimeUnit.SECONDS).disableAutomaticRetries().build();HttpComponentsClientHttpRequestFactoryfactorynewHttpComponentsClientHttpRequestFactory(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(namelbRestTemplate)privateRestTemplatelbRestTemplate;Resource(namedirectRestTemplate)privateRestTemplatedirectRestTemplate;关掉 LoadBalancer Retry避免请求风暴spring.cloud.loadbalancer.retry.enabledfalse不同版本属性略有差异6. 让健康检查永远可用管理端口隔离即使你修复了超时生产环境仍建议把健康检查放到独立端口避免业务流量影响探活。management.server.port8081 management.endpoints.web.exposure.includehealth,metrics,threaddump management.endpoint.health.probes.enabledtrue探活改用curl--max-time2http://127.0.0.1:8081/actuator/health这样业务 8080 再忙健康检查也更稳定。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询