
1. 一次真实的“假通过”压测可靠性测试到底在测什么说个我自己的经历。某次版本上线前负责某个核心订单服务的系统完成了全链路压力测试各项指标都“绿得发亮”QPS达标、平均响应时间在阈值内、错误率几乎为零。大家高高兴兴地发了上线邮件结果当天晚上大促流量一到服务直接雪崩。复盘的时候发现一个非常尴尬的事实当时的压测只验证了“系统在给定负载下能撑多久”完全没测试“当某个下游依赖挂了系统会不会跟着挂”也没有验证“进程重启之后流量恢复需要多久”。换句话说我们测的是性能不是可靠性。这就是我写这篇文章的动机。很多团队对“可靠性测试”的理解停留在“压测跑到不报错”这个层面但它实际上是一套完全不同的测试逻辑不是为了证明系统“快”而是为了证明系统“不容易坏坏了还能自己好”。这篇文章会围绕我参与的一个跨平台系统的可靠性测试项目展开把测试方案设计、故障注入、稳定性验证、监控配套这些部分拆开讲清楚。不论你是测试工程师、后端开发还是运维同学应该都能从中找到可以直接落地的东西。简单区分一下几个容易混淆的概念。压力测试关注的是系统在极端负载下的表现目标是找到性能拐点稳定性测试关注的是系统在长时间运行下的资源使用和性能劣化趋势而可靠性测试的关注点是系统在面对故障时能否按预期降级、恢复以及在恢复过程中用户受影响的程度。三者的工具和方法有交集但评估维度和通过标准完全不一样。这也是为什么用压测报告来证明系统“可靠”会出问题——它根本不是一个维度的东西。2. 设计可靠性测试方案的三个前置问题先搞懂你要防什么可靠性测试不能一上来就开着压测工具乱打那样测出来的结果既没法解释也没法指导改进。我在设计测试方案之前通常先回答三个问题系统的故障模型是什么、业务上哪些路径最怕出问题、以及故障发生后用户能接受的最长恢复时间是多少。2.1 故障模型把“可能坏的东西”全部列出来可靠性测试的本质是“模拟故障验证预期”。所以第一步就是穷举故障源。以我之前那个跨平台系统为例它依赖关系大概有这些层次客户端/浏览器、网关层、业务服务节点、缓存集群、数据库主从、消息队列、第三方支付/短信等外部服务。每一个层次都有对应的典型故障进程崩溃、节点宕机、网络分区、DNS解析失败、磁盘写满、连接池耗尽、下游接口超时、证书过期等等。我习惯把故障模型做成一张矩阵表格横轴是故障对象纵轴是故障类型和影响范围。做这个表的目的是让测试用例的优先级有依据而不是拍脑袋。故障对象典型故障类型预期影响测试优先级业务服务节点进程OOM、主进程退出局部请求失败需自动重启高数据库主库宕机、从库延迟写入阻塞只读降级高缓存集群节点宕机、key逐出流量穿透至DB响应变慢高消息队列消息积压、consumer宕机异步链路延迟数据最终一致中第三方接口超时、返回错误码核心链路阻塞或降级高基础设施磁盘空间不足、CPU steal日志写满、性能劣化中网络丢包、分区、DNS故障连接失败可用性下降中这张表做完之后测试用例并不是全部都要执行而是结合第二个问题来做取舍。2.2 业务路径与容忍阈值的确定故障模型里的每一项落到具体业务上影响是完全不同的。例如数据库主库宕机对“用户登录”和“历史订单查询”这两个场景的影响程度就不一样登录可能完全不可用而订单查询如果能走缓存或只读从库就能继续服务。所以第二步是把核心业务路径列出来逐条标注这个路径依赖哪些组件、每个组件故障时会怎样、对应的降级策略是什么。在这个阶段要和业务方对齐一个关键数字故障恢复时间目标。我们当时把核心下单链路的恢复目标定在30秒以内意思是即使某个依赖挂了系统要么在30秒内自动恢复要么降级策略在30秒内生效并返回可接受的结果比如提示用户稍后重试而不是一直转圈。这个数字直接决定了后面故障注入的判定标准。没有这个目标测试结果就是一笔糊涂账。2.3 测试范围的边界设定可靠性测试不可能覆盖所有故障组合尤其是多故障叠加的场景组合爆炸会让测试成本失控。我的建议是第一轮只做单故障验证把每个高优级故障都验证一遍第二轮再做两个故障同时发生的典型组合例如“缓存节点宕机 数据库主从切换同时进行”。再复杂的组合就只能靠日常的混沌演练来发现而不是放在版本发布前的专项测试里。边界设定的另一个含义是明确测试环境和非测试环境。可靠性测试必须放在一个和生产拓扑一致、但可以随便折腾的环境里。如果环境里只有一套数据库、一个服务实例很多故障注入测试的结果是无效的——因为你测的是单机行为不是系统行为。3. 稳定性测试的实操细节长时间运行的坑比你想的要多可靠性测试里有一块硬骨头就是稳定性测试也就是让系统在持续压力下跑几小时甚至几天观察资源占用、响应时间、GC频率、内存趋势等指标有没有劣化。这一部分虽然看起来简单就是“跑着别管”但实际操作中坑很多我把关键步骤和我踩过的坑展开说说。3.1 用阶梯加压而不是恒定压力跑长稳很多团队做长稳测试的习惯是压到目标QPS的某个百分比然后固定住跑12小时。这种做法的缺点是压力没有波动系统里的很多动态行为不会被触发。比如连接池的回收与重建、线程池的排队策略、缓存淘汰与回源、GC的分代晋升这些都是和负载波动强相关的。我推荐用阶梯加压模式比如以目标负载的30%为起点每30分钟升一档经过50%、70%、90%再到120%的短时峰值然后降回70%持续运行再重复一轮。这样做的好处是能同时观察系统在“负载上升”“峰值冲击”“负载回落”三个阶段的表现。阶梯加压还能暴露一类很隐蔽的问题资源释放不及时。有些系统在低负载时看起来一切正常但一旦经历高负载后降回低负载线程池里的空闲线程没有回收、连接池里的连接没有被清理、本地缓存里的过期数据没有被淘汰就会造成“负载降了但资源占用降不下来”的异常现象。这在固定压力模式下面完全看不出来。3.2 稳定性验证必须盯的四类指标稳定性测试的通病是只看QPS和响应时间这两个指标在系统真正劣化到严重程度之前往往还撑得住。我这边实际总结下来有四类指标是必须持续采样的内存趋势尤其是JVM堆内和堆外的使用曲线。如果老年代在持续缓慢增长、且每次GC之后回收比例越来越低基本可以判定存在内存泄漏或者缓存无界增长。这个曲线必须在测试结束时比开始时趋势平坦哪怕略有增长也需要定位原因。GC频率与停顿只记录Full GC次数是不够的还要看GC平均停顿时间和最长停顿时间。对在线服务来说一次长停顿如果超过了超时阈值的几倍就可能导致批量请求超时这就是可靠性问题。连接池与线程池水位包括数据库连接池的活动连接数、空闲连接数、等待队列长度线程池的活跃线程数、任务队列积压数。这些指标如果在高负载和低负载切换之后没有回到预期水位说明池化资源的回收逻辑有问题。错误率的时间分布稳定性测试中的错误率不能只看平均值要看错误在时间轴上的分布。如果错误都是集中在某个特定时段比如每小时整点那基本可以断定有一个定时任务或者日志切割逻辑在捣鬼。3.3 长稳测试中最容易忽略的环境因素环境因素对稳定性结果的影响非常大而且是那种“你不知道它有问题直到结果对不上”的类型。比如测试机器如果和线上机型不一致内存带宽、磁盘IO能力、网络延迟都可能不同导致测试结论无法生效。我能给的建议是如果环境条件受限至少保证CPU核数和内存大小一致并且在同一网段内跑否则网络延迟差异会把连接池配置的验证结果彻底带偏。另外一个容易被忽略的是时间同步。如果你的服务依赖时间戳来做分布式ID或者缓存过期判断而测试环境里的机器NTP同步有问题那么长稳测试过程中会出现一些莫名其妙的现象比如缓存大面积失效、日志时间乱序。进测试之前先检查所有节点的时间同步状态是一个很便宜但很值得做的步骤。4. 故障注入与恢复验证如何让系统在“死过”之后还能站起来可靠性测试和普通压测最大的分野就在于故障注入。这一节的目的是让系统真的经历故障观察它能不能按预期降级以及故障解除后能不能自动恢复到健康状态。故障注入做得好不好直接决定可靠性测试报告有没有说服力。4.1 选择故障注入工具的几个原则故障注入工具不少选择的时候我主要看四点一是是否支持跨平台部署覆盖Windows和Linux节点二是故障类型是否可编排能组合多个故障三是是否有恢复机制注入的故障能在指定时间后自动解除四是注入和恢复操作是否留审计日志。混合云的场景下还要额外关注工具对虚拟机、容器、物理机三种部署形态的支持情况。简单列一下我实际用过的方案类型供参考工具/方案适用场景短板自研脚本系统命令kill、tc、iptables等小规模环境、快速验证无法编排复杂场景、无审计开源混沌工具如注入类框架容器化、微服务架构对非容器化的传统应用支持一般平台型故障演练服务云上环境、跨地域集群绑定特定云厂商、本地化部署受限内部测试平台封装长期复用、标准化用例需要一定开发投入当初我们那个项目用了内部封装加自研补位的组合方式标准故障用平台预置的演练细节上的特殊故障比如模拟磁盘目录读权限异常用自研脚本补。在选择具体工具时建议先把最常用的故障类型列出来再看工具有没有覆盖避免为了上工具而把测试方案搞复杂。4.2 可以把哪些故障注入进去一份可直接套用的故障清单基于前面提到的故障模型我整理了一份可以在测试环境直接执行的故障清单每一项都包含操作方式和预期结果服务进程级故障直接杀掉业务进程观察守护进程能否拉起、拉起后流量恢复需要多久、是否有请求在重启窗口被丢弃。操作很简单kill -9 pid但这个动作要慎用因为强制杀进程最容易暴露“优雅退出”没有实现的问题。网络层故障通过iptables丢包或断连模拟网络分区。比如丢弃某个服务端口的所有入向包若干秒观察调用方是否会触发超时重试、是否有熔断降级。依赖超时故障给第三方依赖接口设置人为延迟例如在网关层加一个只针对该服务的延迟中间件超过配置的超时阈值观察系统是否报错还是降级返回。磁盘故障把一个节点的数据目录写满或用权限控制限制写入观察日志是否阻塞、请求是否堆积、进程是否因为日志写满而挂掉。资源耗尽型故障用压测工具单独把CPU或内存打满观察在资源争抢的情况下服务的核心接口是否还能响应响应的退化是否有规律。4.3 故障注入之后的判定流程从“故障恢复”到“数据一致性”故障注入不是把故障弄出来就完事了关键在故障解除后的判定。我的习惯是分三步走。第一步是看故障期间的可用性表现错误率是否符合预期、降级策略是否激活、是否有兜底返回值。第二步是看故障恢复后的自动回稳能力系统能不能回到健康水位、有没有进程需要手动重启、资源占用是否回落到正常区间。第三步还要做数据一致性核验这个步骤很多人会漏。比如模拟主库宕机后切回要检查这段时间内的写入是否丢失、缓存和数据库是否出现不一致、消息队列里的未消费消息是否在恢复后正常消费。我在这边就曾经遇到过一个案例某一次注入缓存集群节点故障后系统确实在几秒内恢复了表面看一切正常但事后核对数据时发现有部分请求因为缓存穿透把旧数据写回了数据库导致数据库里的数据和实际业务不一致。如果当时只看了可用性指标这个问题就会漏过去变成生产环境的一颗定时炸弹。4.4 恢复机制本身也要测自动拉起不是万能的故障恢复验证还有一个容易被忽略的测试点自动恢复机制本身的可靠性。很多系统配置了进程守护或者K8s的自动重启但守护进程本身的健康检查逻辑可能有问题。常见的情况是健康检查接口因为依赖了数据库连接池数据库抖动时健康检查也跟着失败于是守护进程反复杀进程、反复重启形成了“重启风暴”。我在实际测试中会专门设计一个用例把健康检查依赖的下游组件拖慢观察守护进程是否会误判。如果进程被反复拉起说明健康检查的“健康定义”太脆弱需要把健康检查改成“只检查本进程核心状态不依赖外部组件”的轻量模式。这个细节在常规测试里几乎没有人会主动去验证但它在真实故障中往往能决定一个系统能不能自愈。5. 可靠性测试闭环里的可观测性没有监控做支撑测了也白测可靠性测试的结果只有在“可观测”的前提下才有意义。如果系统在故障注入时发生了什么你根本看不到或者线上出了问题之后才发现测试环境里的监控指标和线上对不上那么整个测试的可信度就会大打折扣。这一节聊聊可观测性建设里和可靠性测试强相关的三个部分。5.1 指标、日志、链路追踪三个维度必须对齐可靠性测试中我会确保三个数据维度都能完整覆盖到被测链路的每个节点指标负责看趋势CPU、内存、QPS、错误率、延迟分布日志负责看上下文错误堆栈、业务异常、降级事件链路追踪负责看请求在跨服务调用之间的完整路径。这三个维度必须做到时间对齐。具体做法是在所有埋点里统一使用前端传入的TraceID或独立的RequestID并且确保日志和链路数据里都能按这个ID检索。没有统一ID排查故障时就要靠时间戳猜效率极低而且在故障注入测试这种“多个服务同时异常”的情况下基本无从查起。5.2 针对可靠性测试的专属监控页面常规监控面板在可靠性测试时并不够用因为它默认展示的是“系统正常运行时”的视图。我建议为可靠性测试单独做一个面板至少包含以下区块故障注入事件时间线每次注入和恢复的时间点用标记线打在图表上核心服务和依赖项的健康状态列表实时显示存活状态、健康检查结果错误率与失败原因分类汇总按超时、拒绝、异常、降级等维度区分资源水位和池化状态连接池、线程池的实时占用情况数据一致性检查结果比如缓存和数据库之间的校验结果、消息积压数量这个面板的用途是让测试执行者在故障注入的一瞬间就能定位到现象而不是等测试结束后翻日志。我们在实际项目中就靠这个面板在一次测试里同时定位到了超时重试风暴和缓存穿透两个问题省去了大量复盘时间。5.3 测试环境监控和生产的比对避免环境差异带来的误判可靠性测试得出的结论要能映射到生产环境否则测试就没有意义。我的习惯是在测试结束后抽取同一时间窗里测试环境和生产环境的几个关键指标做对比基础资源使用率、服务启动时间、接口延迟分位数。如果差距明显比如测试环境接口延迟是生产的一倍以上就需要先排查环境差异原因再决定测试结论是否还能生效。实际遇到过一个典型问题测试环境使用了共享的监控集群当故障注入导致大量日志写入时监控数据采集也受到了影响导致面板上的指标出现断点。这种“监控自身也被故障干扰”的情况并不少见。所以我通常会为可靠性测试环境准备独立的监控存储或至少独立的采集通道确保故障发生时监控数据还能完整记录。6. 可靠性测试结果分析这些指标组合才是最该盯的跑完所有用例之后怎么判断系统到底可不可靠可靠性测试没有单一通过标准而是要靠一组指标组合来评估。我分享几个我在实践里真正用到的评估维度和判断经验。6.1 MTBF和恢复时间一个被高估、一个被低估平均无故障时间和恢复时间是可靠性领域最常见的两个指标。但我的看法是在软件系统里平均无故障时间远没有恢复时间重要因为软件系统的故障迟早会发生而真正影响用户体验的是故障发生后到业务恢复正常之间的时间。所以我在测试报告里会重点看两个数字而不是平均无故障时间故障检测时间从故障注入到系统感知故障比如健康检查失败、监控告警触发的时间间隔故障恢复时间从系统感知故障到业务完全恢复的时间间隔这两个数字的时间线在测试中会被严格记录。比如注入数据库故障健康检查10秒后才感知到然后自动切换花了20秒再加上连接池重建耗时5秒整体恢复时间就是35秒。如果预先设定的是30秒恢复目标这个测试就不通过需要继续优化故障检测的灵敏度和切换流程。6.2 异常路径覆盖率不是故障越多越好有些团队喜欢堆故障用例的数量好像注入的故障越多就越专业其实不是。可靠性测试的核心指标之一是异常路径覆盖率也就是针对一个故障类型是否同时覆盖了“故障发生—故障持续—故障恢复”三个阶段的系统反应。举例来说测试下游接口超时至少要覆盖三个场景超时发生时调用方有没有走降级逻辑超时持续期间重试机制是否会自动触发、以及重试次数有没有上限下游恢复后调用方能否在不需要重启的情况下重新建立连接。这三个场景每一个都需要单独的用例来验证。我在实际的测试执行单里会给每个故障类型建一个矩阵表格三种状态分别打勾漏掉任何一个都不算覆盖率完成。6.3 从结果反推代码级的改进项可靠性测试的最终产出不是一份“通过/不通过”的报告而是一份改进清单。每一条改进项要能落到具体的代码或配置层面而不是“建议加强监控”“后续多关注”这种空话。举几个我在实践中整理过的改进项示例大家可以参考它的颗粒度“健康检查接口改为不依赖数据库连接池检测避免数据库抖动导致重启风暴”落到代码“下游调用超时时间从3秒调整为1秒失败快速返回触发降级缓存”落到配置“消费消息时增加批量拉取条数上限防止单条消息卡住整个消费线程”落到代码“在网关层对第三方接口增加独立熔断器避免单一下游故障拖垮所有依赖该下游的业务链路”落到架构配置“日志增加满磁盘时的滚动策略和自动清理避免磁盘写满导致进程假死”落到日志框架配置有了这样一份清单可靠性测试才真正和系统的稳定性建设挂上钩了。它不再是测试团队自嗨的仪式而是在帮整个研发团队补齐那些“正常情况下永远不会触发、故障来了一触即炸”的隐性短板。7. 一个五节点的跨平台系统可靠性测试的执行复盘理论说了不少最后还是得落到具体的执行过程上。这一节我用曾经跑过的某跨平台系统可靠性测试项目做一次复盘包括整体计划、执行顺序和过程中的关键处理。7.1 测试计划与时间安排那个系统拓扑比较标准两个业务服务节点、一套缓存集群、一套数据库主从、一个消息队列还有若干个外部依赖。整体测试周期安排了三周时间但真正的故障注入集中在一周内完成前面两周分别给了方案设计、环境准备和稳定性测试。阶段时间核心任务故障模型与用例设计3天输出故障矩阵和用例清单与业务方对齐恢复目标环境准备与检查2天拓扑核对、监控配置、时间同步、数据备份稳定性测试4天阶梯加压长稳观察资源劣化趋势故障注入测试3天按优先级逐项执行记录检测时间和恢复时间结果分析与改进2天输出改进清单确定下一迭代的验收标准7.2 执行过程中暴露的几个真问题这个项目最终暴露并解决了不少问题其中有几个印象特别深第一个是健康检查依赖外部组件。我们把数据库节点做故障注入时一个业务节点因为健康检查里带了数据库连通性检测被守护进程误判为“业务进程不健康”反复重启了好几次。这个时候系统的自动恢复机制反而成了故障放大器最后改成轻量健康检查后问题消失。第二个是连接池参数在故障恢复后不会自动收缩。高负载时连接池被撑到了上限峰值结束后连接数一直维持在高水位直到下一轮故障触发时没有足够的连接建立余量。这说明连接池缺少空闲回收机制后来在数据库连接池配置里增加了空闲连接回收时间参数。第三个是下游超时时间设置过长。第三方接口出现问题时由于超时设了3秒而下游实际故障时长是30秒导致调用线程长时间被占住线程池被打满核心接口也受到了影响。后来把超时调短并配合快速失败和重试上限恢复了系统的自我保护能力。7.3 测试执行时值得养成的三个习惯经验都是踩坑换来的这里分享三个我在执行可靠性测试时形成的习惯。第一个习惯是每个故障用例执行前先记录各组件的基线状态。包括各节点的CPU、内存、连接池水位、健康状态。没有基线故障恢复后就说不清楚哪些指标异常是故障导致的、哪些是执行前本来就有的问题。第二个习惯是故障注入后不急着看结果先观察5分钟。有些故障的影响不是立刻显现的比如慢日志、定时清理任务、异步队列消费延迟往往要几分钟后才会浮出水面。如果你一看到故障注入完成就手动恢复这些滞后影响就会被整个测试流程错过。第三个习惯是每次故障注入的恢复操作都保留执行记录。谁在什么时间、用哪个命令、恢复了哪个故障都要留痕。一方面是为了复盘可追溯另一方面是当多个故障并发注入时避免不同的人对同一个组件做冲突操作。我在项目里亲眼见过一次因为两个测试人员同时执行不同故障用例结果网络断连的故障刚注入就被另一个人误以为是不正常状态而手动恢复了最终两个用例都无效。8. 写在最后的几点个人体会做可靠性测试做了这么久最大的感触是这个方向的难点不在于工具和手段而在于坚持真实性。所谓真实就是真的让系统经历故障真的接受它可能会挂真的去度量挂了之后多久能恢复。很多团队不愿意走到这一步是因为测出来的问题要有人去改会打破迭代节奏。但换个角度想等到线上故障才暴露出这些问题代价只会更大。如果读者现在正打算在自己的项目里推行可靠性测试我的建议是先别追求大而全选择一个核心业务链路选定两类最常见的故障进程崩溃、下游超时把测试环境搭好跑通一次完整的“故障注入—观察—恢复—分析”闭环。这次跑通的价值会远远超出你的预期因为从那一刻起你对系统稳定性的认知就不再是基于假设而是基于实测数据。另外可靠性测试这件事不要想着做一次就一劳永逸。系统的代码、依赖、配置都在变每隔一段时间重新跑一遍同样的故障用例往往都会查出新的问题。把可靠性测试做成常态化、至少每个大版本迭代都覆盖一轮核心用例比一次性做一个大而全的方案更落地。我自己的实践经验是把最核心的十几个故障用例固化下来每次版本发布前在测试环境跑一遍比半年做一次大规模全量演练要靠谱得多。