当前位置: 首页 > ai >正文

【案例解析】一次 TIME_WAIT 导致 TPS 断崖式下降的排查与优化

大家好,我是G探险者!

背景

在某次系统长稳压测过程中,我们遭遇了一个诡异的问题:TPS 周期性地出现“断崖式下跌”,随后又慢慢恢复正常,周而复始。这种现象极大地影响了系统的稳定性预期,因此我们启动了问题排查与优化流程。

最终定位到一个“老朋友”——TCP 的 TIME_WAIT 状态。本文将完整还原这一问题的现象、排查路径、技术原理及优化方案,希望能为你在高并发场景下提供参考。


问题现象

测试同事使用 JMeter 持续压测服务,观察到 TPS 呈周期性波动:

  • 每隔一段时间,TPS 会突降至一个较低值;
  • 数十秒后,TPS 又恢复到正常水平;
  • 如此循环往复,严重影响系统的稳定性指标。

我们首先从操作系统层面入手,进入服务 Pod 执行如下命令:

netstat -anp | grep 9080

结果发现:

  • 有大量连接处于 TIME_WAIT 状态;
  • 每当 TPS 下降,TIME_WAIT 数量会暴涨。

结合现象和连接状态,我们初步判断:服务在调用下游服务时频繁建立和关闭 TCP 连接,最终导致系统本地临时端口资源耗尽,从而影响 TPS。


初步分析

RestTemplate 用法检查

服务通过 RestTemplate 调用下游服务,其创建方式如下:

public static RestTemplate createRestTemplate() {OkHttp3ClientHttpRequestFactory factory = new OkHttp3ClientHttpRequestFactory();factory.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);factory.setReadTimeout(DEFAULT_READ_TIMEOUT);return new RestTemplate(factory);
}

关键问题来了:

  • 每次创建 RestTemplate 都是全新对象;
  • 底层 OkHttpClient 未显示传入(隐式新建);
  • 未启用连接池,导致每次请求都是一次完整的 TCP 短连接

TIME_WAIT 是什么?

在 TCP 协议中,TIME_WAIT 是主动关闭连接的一方所进入的状态,它存在的目的是:

  • 防止旧连接残留数据污染新连接;
  • 确保被动关闭方若未收到最后一个 ACK,还可以重发。

默认情况下,Linux 会将连接保留在 TIME_WAIT 状态长达 60 秒(2×MSL)。在高并发场景下:

  • 短连接频繁创建 → 每次都进入 TIME_WAIT
  • 数万连接堆积 → 本地端口资源被占满;
  • 导致新连接 connect() 超时 → TPS 出现断崖。

解决方案:引入连接池

我们尝试对 OkHttpClient 显式配置连接池并注入 RestTemplate,核心代码如下:

@Configuration
public class RestClientConfig {@Beanpublic OkHttpClient okHttpClient() {ConnectionPool pool = new ConnectionPool(100, 30, TimeUnit.MINUTES);Dispatcher dispatcher = new Dispatcher();dispatcher.setMaxRequests(200);dispatcher.setMaxRequestsPerHost(100);return new OkHttpClient.Builder().dispatcher(dispatcher).connectionPool(pool).connectTimeout(Duration.ofSeconds(5)).readTimeout(Duration.ofSeconds(30)).followRedirects(false).build();}@Beanpublic RestTemplate restTemplate(OkHttpClient okHttpClient) {OkHttp3ClientHttpRequestFactory factory =new OkHttp3ClientHttpRequestFactory(okHttpClient);return new RestTemplate(factory);}
}

效果观察

调整部署后重新压测,结果如下:

  • TIME_WAIT 数量大幅下降;
  • TPS 波动消失,长期保持平稳;
  • 网络端口资源利用率恢复正常。

技术要点总结

维度建议原因
RestTemplate 构建使用单例 Bean避免重复创建连接池
OkHttpClient 配置显式配置 ConnectionPool 和 Dispatcher控制最大连接数与请求并发度
系统内核参数(可选)调整 ip_local_port_range 和 tcp_tw_reuse提高本地端口复用能力(仅作兜底)
下游配合下游服务需支持 keep-alive否则连接池复用无效
监控指标TIME_WAIT 数、连接池状态、TPS 曲线实时观测变化,辅助优化

附:一些底层原理问答

Q: 为什么 TIME_WAIT 会阻塞新连接?

A: TCP 协议要求 TIME_WAIT 状态的套接字在未过期前,不能被复用为同样四元组的新连接。连接数太多 → 可用本地端口耗尽 → 后续请求卡在 connect()

Q: 客户端为什么是 TIME_WAIT

A: 主动调用 close() 的一方进入 TIME_WAIT,在我们的场景中,服务调用下游时请求发起方是客户端,它主动关闭连接。

Q: 为什么“加了连接池”后问题解决?

A: 连接池使得请求可以复用已有连接,避免频繁建链与断链,自然 TIME_WAIT 数量大大减少。


结语

这是一个典型的“连接未复用 → TIME_WAIT 滥发 → 系统资源耗尽 → TPS 周期性下降”的问题。看似是一个 TPS 波动的问题,实则背后隐藏着连接池使用不当、短连接滥用、系统资源上限等多个技术点。

性能测试不仅是服务能力的验证,也是架构合理性的“压力测试”。本次问题的排查,也提醒我们在设计服务间通信时,连接池和复用策略是高并发系统的生命线

http://www.xdnf.cn/news/13053.html

相关文章:

  • ThreadLocal 源码
  • RabbitMq安装
  • deepseek: GPU 配套
  • 联邦学习同态加密以及常见问题
  • Vue动态/异步组件
  • 1991-2024年上市公司个股换手率数据
  • Haption 力反馈遥操作机器人:6 自由度 + 低延迟响应,解锁精准远程操控体验
  • 设置Outlook关闭时最小化
  • mybatisX的使用,简化springboot的开发,不用再写entity、mapper以及service了!
  • JDK 17 序列化是怎么回事
  • Prompt‏ 工程和优化技巧
  • 产品经理课程(十三)
  • SE(Secure Element)加密芯片与MCU协同工作的典型流程
  • 在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案
  • Origin图像数字化工具——如何复制其他人的图表作为对比数据
  • Linux 下部署安装docker
  • 京东商品sku抓取攻略|已封装API接口调用示例
  • SpringBoot配置
  • 【如何做好技术架构】
  • 大模型真的像人一样“思考”和“理解”吗?​
  • Python编程基础(五) | 字典
  • 深度伪造视频时代的“火眼金睛”:用Python打造假视频识别神器
  • 循环语句之for
  • Latex vscode安装、配置与使用-Windows
  • 文件上传漏洞防御全攻略
  • 从零手写Java版本的LSM Tree (六):WAL 写前日志
  • Chrome二级标签无法选中的解决方案
  • LMKD(Low Memory Killer Daemon)原理初识
  • Linux与量子计算:面向未来的架构演进
  • 在 Spring Boot 项目里,MYSQL中json类型字段使用