Soak 测试是性能测试里比较容易做的一类,接下来猫哥将从技术原理层面讲透。

0. 一句话定义

Soak 测试(浸泡/耐久性测试)= 在"接近生产常态的稳定负载"下,让系统连续运行数小时到数天(通常 12–72 小时甚至数周),专门观察那些"随时间累积才会出现"的退化。 它不追求"把系统打崩"(那是压力测试),追求的是"看它能不能一直跑而不偷偷变坏"——正如 TestDevLab 的比喻:不是看车能跑多快,而是看连续开 48 小时会不会过热、漏油。

1. 它在性能测试光谱里的位置

类型负载时长要暴露的问题
负载测试峰值附近分钟~小时当前规模下容量够不够
压力测试超峰值,逐步加压拐点:在哪个负载点崩溃、错误率飙升
尖峰测试突增秒~分钟突发流量下的快速响应
Soak 测试生产常态/略低小时~天时间累积型缺陷:泄漏、漂移、缓慢退化

关键区别:Soak 用平稳的常规负载(不是峰值),唯一变量是时间。所以它的"信号"是趋势线的斜率,不是某一时刻的快照——asserthired 的原话:"任何不随时间平坦的东西都值得怀疑。"

2. 它到底能暴露哪几类问题(按机理分类)

这些问题的共同点是:单次泄漏很小(微乎其微),但乘以时间就变成灾难——所以 30 分钟的压测永远看不到。

① 内存泄漏(最经典)

  • 堆内:对象被无意识持有(静态集合、事件监听器、ThreadLocal、缓存未设 TTL),GC 回收不掉 → "GC 后堆底(post-GC heap floor)"持续爬升,这是判定泄漏的主信号(不是看瞬时堆峰值,要看每次 GC 后能降到的最低点是否越来越高)。
  • 非堆/Native:DirectByteBuffer、JNI 分配、C/C++ 库——JVM 堆没涨但进程 RSS 一直涨。
  • 最终:堆填满 → Full GC 越来越频繁、STW 变长 → 最终 OOM 重启。

② 句柄 / 文件描述符(FD)泄漏

  • 流/文件/网络连接打开后没 close()(注意:try-with-resources 也会漏——如果底层 close() 静默失败)。
  • 症状:Too many open fileslsof -p <pid> 看到 FD 数单调递增。进程 FD 有 ulimit 上限,耗尽后连日志都写不进去。

③ 连接池泄漏 / 耗尽

  • 数据库、Redis、HTTP 客户端、消息队列的连接借出未归还(连接池 maxActive 用满且不释放)。
  • 症状:连接数随时间逼近池上限 → 获取连接超时 → 错误率爬升,且这种"缓慢的"超时最容易被误判为网络抖动。对金融系统尤其致命——一个服务耗尽连接会连带拖垮调用它的下游(雪崩传导)。

④ 线程 / 协程泄漏

  • 线程创建后未终止(线程池任务吞掉异常导致线程永不归还、异步回调泄漏)。症状:线程数持续增长,最终 OutOfMemoryError: unable to create new native thread 或上下文切换暴增。

⑤ 缓存无界增长

  • 缓存/TTL/淘汰策略缺失 → 内存/存储无限膨胀。症状:缓存命中率变化 + RSS 持续爬升。

⑥ 磁盘 / 临时文件 / 日志堆积

  • 临时文件未清理、日志无滚动策略 → 磁盘水位持续上升 → 写满后服务雪崩。金融场景还有对账文件、快照文件、消息积压落盘。

⑦ GC 退化与延迟漂移

  • 随着堆被泄漏物填满,GC 压力上升 → P95/P99 延迟随时间缓慢爬升(这是"性能退化"信号,与前面的"资源耗尽"信号并列)。

⑧ 后台任务 / 定时器漂移、任务堆积

  • 定时任务、调度器、死信队列随时间堆积;任务执行时间漂移导致重叠执行、锁竞争恶化。

⑨ 计数/统计漂移、时间边界问题

  • 计数器、水位、过期判断跨日/跨周才出错;慢 SQL 累积、缓存陈旧数据随时间累积影响正确性。

3. 方法学:怎么做才有效(不是"挂机跑 72 小时")

负载模型

  • 生产常态负载(峰值 60%-80%),或按真实流量带波动的负载曲线(有峰谷、有夜间的低潮),比恒定打满更能模拟真实累积路径。
  • ramp-up 要快(尽快进入稳态),重点观察"稳态之后"的长时间段。

时长怎么定(为什么是 72 小时)

  • 原理是泄漏率 × 时间 = 可检测的累积量。泄漏率极低时(比如每小时漏几 MB、几天漏一个 FD),观测窗口太短,泄漏幅度会被正常抖动淹没,无法与"平台期"区分。
  • 经验上:通用应用至少 4 小时;长会话/流式/消息类 8–12 小时;要覆盖跨日边界、周定时任务、低频回收路径,通常跑到 72 小时甚至数周。72 小时能覆盖完整的自然日循环(白天高峰+夜间低潮)和多数定时任务周期,是金融/核心系统的常见门槛。

关键指标要"时间序列化",且看斜率

  • 内存:GC 后堆底(主信号)+ RSS(覆盖 native)
  • 资源:FD 数、线程数、各连接池高水位、磁盘/临时文件/日志增长
  • 性能:P50/P95/P99 延迟随时间的趋势、GC 暂停时长曲线(不要只看全程平均)
  • 正确率:错误率、超时率趋势
  • 做法:用线性回归拟合后半程(如最后 60%)的斜率,判断是"平台期"还是"单调爬升"。

通过/失败判据

  • 通过:所有指标曲线呈平台期(有抖动但无趋势),GC 后堆底稳定,连接数在池上限内有头部,延迟无单调爬升。
  • 失败:任一资源单调爬升(斜率显著不为 0)、延迟持续漂移、出现"Too many open files"/连接超时/中途重启。
  • 中途进程重启 = 测试自动失效,必须调查重启原因后才能标绿(重启会把泄漏归零,掩盖问题)。

4. 对应你课件那句话

"全链路压测(生产流量录制回放、流量倍增)暴露拐点;72 小时 soak 测试暴露内存/句柄泄漏"

这两者正好是互补的两类测试

  • 全链路压测回答"最多能扛多少"——通过流量倍增、录制回放制造峰值,找到延迟/错误率的拐点(容量上限),属于"空间维度"。
  • Soak 测试回答"能扛多久"——在常态负载下跑 72 小时,暴露只有时间累积才现形的内存/FD/连接池/磁盘泄漏,属于"时间维度"。
  • 一句话:压测找"能撑多高",soak 找"能撑多久";前者定容量上限,后者定稳定性底线。两者共同构成韧性测试的完整证据。

5. 常见坑与最佳实践

  • 坑:用峰值负载跑 soak——峰值会先触发别的瓶颈,泄漏信号被噪声淹没;soak 就该用常态负载。
  • 坑:只看瞬时快照/只看平均值——GC 平均暂停时间会掩盖"越到后期越频繁"的趋势。
  • 坑:测试工具自己泄漏——压测机长时间开合海量连接,先确认工具/脚本本身资源曲线平坦,否则误报。
  • 最佳实践:类生产环境 + 完整可观测性;每个关键资源一条时间序列曲线;对 GC 后堆底做回归拟合;设置自动阈值告警(如堆底 6 小时内上涨超 X% 即 fail);泄漏复现期抓 heap dump(jmap/-XX:+HeapDumpOnOutOfMemoryError)和 FD 快照(lsof -p)留证。
  • 金融场景:账务、核心交易类系统还要关注对账文件、消息积压、跨日批处理在长时间运行下的累积效应——soak 不仅是"不崩",还要"账不错、批处理不越拖越慢"。
Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐