一、稳定性测试跟性能测试有什么区别
性能测试看的是"快不快",稳定性测试看的是"稳不稳"。
我们项目里定了一条硬性标准:所有做了性能测试的业务,都要在75% CPU压力下,进行7×24小时的稳定性测试。
为什么定75%?因为生产环境的CPU不可能一直空载,日常业务+定时任务混跑,CPU使用率长期在60%-80%之间是常态。我们在测试环境用75%的CPU压力跑七天,就是为了模拟生产环境的真实负载情况。
稳定性测试的目标就两个:
系统能不能持续稳定运行,不崩溃、不重启
系统性能会不会随时间衰减,第七天跟第一天一样快
我们跑了一轮七天稳定性测试,结果发现的问题还真不少。下面按时间线一个个说。
二、测试怎么设计的
JMeter的配置跟性能测试类似,但有一个关键区别:持续时间从几个小时拉长到了七天。
线程组的配置是这样的:
text
Number of Threads: 根据性能测试结果,压到CPU 75%左右的并发数 Ramp-Up Period: 300秒(5分钟慢慢加上去,别一启动就把系统冲垮) Loop Count: Forever(一直跑,不停) Scheduler: 勾选,Duration: 604800秒(7×24小时)
四种业务场景(采集任务、SparkSQL作业、质量规则、API接口)同时跑,混合加压,模拟真实生产环境。
监控方面,我们部署了:
| 监控层面 | 工具 | 采集频率 | 关注指标 |
|---|---|---|---|
| 操作系统 | Prometheus + Node Exporter | 15秒 | CPU、内存、磁盘、网络 |
| 数据库 | Oracle Enterprise Manager | 1分钟 | 会话数、等待事件、表空间 |
| 大数据组件 | Ambari / Cloudera Manager | 1分钟 | YARN队列、HDFS使用率、Spark作业 |
| 应用日志 | ELK(Elasticsearch + Logstash + Kibana) | 实时 | 错误日志、异常堆栈 |
七天跑下来,发现的问题依次是:
三、第一天到第三天:一切正常
前三天没什么异常。系统CPU稳定在73%-77%之间波动,内存使用率平稳,磁盘I/O正常,所有任务的成功率都在99.5%以上。
监控面板上一片绿色,大家还挺乐观的。
但到了第四天,开始出问题了。
四、第四天:磁盘被日志写满
现象:第四天下午,监控突然报警,磁盘使用率超过了90%。
登录服务器一看,/u01/logs目录下全是日志文件,最大的几个都超过了10GB。
bash
# 查看磁盘使用情况 df -h # /u01 使用率 92% # 查哪个目录占的空间最大 du -sh /u01/* | sort -rh | head -10 # /u01/logs 180G # /u01/data 120G # 看看日志文件多大 ls -lh /u01/logs/ # app.log.2026-01-18 12G # app.log.2026-01-17 11G # app.log.2026-01-16 10G # ...(四天的日志加起来180G)
问题根因:日志没有做轮转(log rotation)。所有的业务日志都往同一个文件里写,写满了就新建一个,但旧文件从来不删。四天就攒了180G的日志,把磁盘撑爆了。
临时处理:手动清理了15天前的日志,释放了150G空间。
bash
# 清理15天前的日志 find /u01/logs/ -name "*.log.*" -mtime +15 -exec rm -f {} \;长期解决方案:写了一个日志自动清理脚本,加入crontab,每月执行一次。
bash
#!/bin/bash # /opt/scripts/clean_old_logs.sh # 每月1号凌晨3点执行,清理30天前的日志 LOG_DIR="/u01/logs" RETENTION_DAYS=30 find $LOG_DIR -name "*.log.*" -mtime +$RETENTION_DAYS -exec rm -f {} \; find $LOG_DIR -name "*.out" -mtime +$RETENTION_DAYS -exec rm -f {} \; # 记录清理结果 echo "$(date): Cleaned logs older than $RETENTION_DAYS days" >> /var/log/log_clean.log加入crontab:
bash
# 每月1号凌晨3点执行 0 3 1 * * /opt/scripts/clean_old_logs.sh
反思:为什么是"每月执行一次"而不是"每天执行"?因为磁盘是1TB的,按照日志增长速度,30天大约产生1.4TB的日志,所以30天的保留策略刚好撑满。但生产环境的日志量可能会波动,我们后来改成"每天执行,保留30天"。
五、第五天到第六天:各种小问题陆续暴露
日志问题解决之后,第五天第六天相对平静,但还是发现了一些小问题:
问题一:连接池偶尔泄露
有个采集任务的连接池配置了maxIdle=10,但代码里忘了在finally块里释放连接。跑了五天后,连接池里积累了几十个"僵尸连接",导致新请求拿不到连接。
现象是监控上看到活跃连接数慢慢上涨,到了第五天下午突然有一个小时的成功率掉到了95%。
排查手段:查了应用的数据库连接池监控,发现活跃连接数一直在涨,从来不会降。抓了几个线程栈,看到有线程持有连接但不释放。
解决方案:修改代码,把连接释放逻辑放到finally块里,确保不管成功还是异常都能释放连接。
java
// 修改前(有泄漏) Connection conn = dataSource.getConnection(); // 执行业务逻辑... // 如果这里抛异常,conn就不会被关闭 // 修改后(用finally保证释放) Connection conn = null; try { conn = dataSource.getConnection(); // 执行业务逻辑... } finally { if (conn != null) { conn.close(); } }问题二:Kafka消费者Rebalance频繁
质量任务那边用的是Kafka消费者拉取数据,跑着跑着发现消费者组频繁发生Rebalance(重平衡),每次Rebalance的时候消费就暂停了,任务成功率短期下降。
排查后发现是消费者的max.poll.interval.ms设置得太短(默认300秒),但质量任务处理每条消息的时间较长,超过了300秒还没处理完,Kafka就认为消费者"死了",触发Rebalance。
解决方案:把超时时间调长。
properties
# Kafka消费者配置 max.poll.interval.ms=600000 # 从300秒调到600秒(10分钟) max.poll.records=100 # 每次拉取的消息数调小,减少单次处理时间 session.timeout.ms=45000 # 会话超时适当调长
六、第七天:性能衰减——第七天比第一天慢了很多
现象:第七天早上,我们对比了一下监控数据,发现了一个严重问题——第七天比第一天所用的时间更长。
具体来说:
| 指标 | 第一天(平均值) | 第七天(平均值) | 增幅 |
|---|---|---|---|
| SparkSQL作业平均耗时 | 2.4分钟 | 4.1分钟 | +70% |
| API平均响应时间 | 135ms | 289ms | +114% |
| 采集任务平均耗时 | 1.8秒/表 | 3.5秒/表 | +94% |
| 质量任务平均耗时 | 8.2秒/表 | 15.7秒/表 | +91% |
所有任务的耗时都在变长,而且越往后越慢。这是个典型的性能衰减问题。
排查过程:
第一步:看系统资源。
CPU还是75%左右,没变。内存使用率也没明显增长。磁盘I/O正常。
资源层面没问题,那就不是"系统被压垮了",而是"某些资源被消耗了但没释放"。
第二步:查数据库层面。
连上Oracle,查了一下临时表空间和UNDO表空间:
sql
-- 查看临时表空间使用 SELECT * FROM V$TEMP_SPACE_HEADER; -- 临时表空间用了80%,比第一天高了40% -- 查看UNDO表空间 SELECT tablespace_name, ROUND(used_space/1024/1024/1024, 2) as used_gb FROM DBA_TABLESPACE_USAGE_METRICS WHERE tablespace_name = 'UNDOTBS1'; -- UNDO用了120GB,第一天只有30GB
临时表空间和UNDO都在持续增长,说明有些事务一直没有提交,或者提交后空间没有被回收。
第三步:查长时间运行的事务。
sql
SELECT sid, serial#, username, status, ROUND((SYSDATE - start_time) * 24, 2) as hours_running, sql_text FROM V$SESSION JOIN V$SQL ON V$SESSION.sql_id = V$SQL.sql_id WHERE status = 'ACTIVE' AND start_time < SYSDATE - 1/24 -- 运行超过1小时 ORDER BY start_time;
发现有3个事务已经跑了6个小时还没结束,一直在执行大量的INSERT操作。这些长事务持有UNDO段不释放,导致UNDO表空间越涨越大,其他需要读取一致性视图的操作就变慢了。
第四步:找到长事务的源头。
顺着SID查了应用的日志,发现是质量任务里有一个脚本做了全量数据的行数校验,对一张30亿行的表执行了SELECT COUNT(*)。这个查询需要读取全表数据,同时为了保证读一致性,需要用到UNDO里的历史数据。UNDO被撑得越大,查询就越慢,形成了一个恶性循环。
问题根因:
质量任务的SQL写得有问题——对超大表执行全表COUNT(*),不仅自己慢,还撑爆了UNDO,拖累了整个系统的性能。
解决方案:
终止了那个跑了6小时的查询
sql
-- 终止长事务 ALTER SYSTEM KILL SESSION 'sid,serial#';
优化质量任务的SQL,用统计信息代替全表COUNT
sql
-- 原来的写法(30亿行全表扫描) SELECT COUNT(*) FROM huge_table WHERE dt = '2026-01-15'; -- 优化后的写法(查分区统计信息) SELECT num_rows FROM DBA_TAB_PARTITIONS WHERE table_name = 'HUGE_TABLE' AND partition_name = 'P_20260115';
清理UNDO表空间,回收空间
sql
-- 切换UNDO表空间,让旧的UNDO段释放 ALTER SYSTEM SET UNDO_TABLESPACE = UNDOTBS2; -- 等旧事务全部结束后,再切回去
最终效果:
优化之后,我们又跑了一轮7天稳定性测试,这次第七天的性能跟第一天基本持平:
| 指标 | 第一天 | 第七天 | 变化 |
|---|---|---|---|
| SparkSQL作业平均耗时 | 2.4分钟 | 2.6分钟 | +8.3% |
| API平均响应时间 | 135ms | 148ms | +9.6% |
| 采集任务平均耗时 | 1.8秒/表 | 1.9秒/表 | +5.6% |
| 质量任务平均耗时 | 8.2秒/表 | 8.8秒/表 | +7.3% |
在可接受范围内。
七、稳定性测试中压出的其他问题
除了上面这几个,我们在多轮稳定性测试中还压出了以下问题:
问题四:内存泄漏导致OOM
测试跑到第六天的时候,某个采集服务的JVM内存使用率从正常的40%涨到了90%,然后触发了Full GC,服务卡顿了5分钟,大量任务超时。
排查手段:用jmap导出了堆内存dump文件,用MAT(Memory Analyzer Tool)分析,发现某个缓存Map一直在膨胀,从来没清理过。
bash
# 导出堆dump jmap -dump:live,format=b,file=heap.hprof <pid> # 用MAT分析,找到最大的对象
解决方案:给缓存加了个大小限制,使用LRU(Least Recently Used)淘汰策略,超过上限就自动淘汰最久未使用的条目。
java
// 修改前 Map<String, Object> cache = new HashMap<>(); cache.put(key, value); // 一直往里塞,从来不删 // 修改后 Cache<String, Object> cache = Caffeine.newBuilder() .maximumSize(10000) // 最多缓存1万条 .expireAfterWrite(1, TimeUnit.HOURS) // 1小时后自动过期 .build();
问题五:HDFS小文件堆积
SparkSQL作业每跑一次就会在HDFS上生成一些临时文件。七天下来,临时文件累积了80多万个小文件,NameNode的内存被撑爆了,导致整个HDFS集群变慢。
解决方案:配置Spark作业自动清理临时目录。
sql
SET spark.sql.autoBroadcastJoinThreshold=104857600; -- 关键是下面这个配置:作业结束后自动删除临时文件 SET spark.sql.adaptive.coalescePartitions.enabled=true;
同时增加了一个每日定时任务,清理超过3天的临时文件:
bash
# 每天凌晨2点清理HDFS临时文件 hdfs dfs -rm -r -skipTrash /tmp/spark/*/ 2>/dev/null
八、稳定性测试结论
一轮7×24小时的稳定性测试跑下来,发现的主要问题:
| 问题 | 发现时间 | 严重程度 | 解决方案 |
|---|---|---|---|
| 日志写满磁盘 | 第4天 | 高(差点宕机) | 定时清理脚本 |
| 连接池泄漏 | 第5天 | 中(成功率下降) | 代码加finally释放 |
| Kafka频繁Rebalance | 第5天 | 中(短暂抖动) | 调大超时参数 |
| 长事务撑爆UNDO | 第7天 | 高(性能严重衰减) | 优化SQL+清理UNDO |
| 内存泄漏OOM | 第6天 | 高(服务卡顿) | 限制缓存大小+LRU |
| HDFS小文件堆积 | 第7天 | 中(影响集群性能) | 自动清理临时文件 |
几个核心经验:
性能测试看"快不快",稳定性测试看"多久还是这么快"。第七天比第一天慢了一倍多,这种衰减在短期的性能测试里是发现不了的。
75% CPU压力是稳定性测试的黄金标准。太低压不出问题,太高系统直接崩溃没法观察。75%刚好让所有组件都工作在接近极限的状态,长期运行各种隐藏问题都会暴露。
日志清理不是小事。日志写满磁盘是稳定性测试里最高频的问题之一,一定要有自动清理机制,而且要持续监控磁盘使用率。
长事务是性能衰减的最大杀手。一个跑了6小时的
COUNT(*),看起来只是"慢",但它会拖慢整个系统。对大表查询,能用统计信息就别用全表扫描。