文档目录

5.4 环境隔离清单

上一节:5.3 重复、抖动与异常值 | 下一节:5.5 数据集设计 配套代码:05-experiment-design/04-environment-isolation


一句话结论

实验环境里任何「你没关掉的东西」,都会变成数据里的噪声。 而与性能测试不同,噪声不会报错——它只会让你的结论变得不可信,而你往往察觉不到。


一、用「化学实验的洁净室」理解环境隔离

化学实验里,一点灰尘就可能让结果完全偏差。所以:

化学实验室 性能实验
无尘环境 独占的测试机器
恒温恒湿 固定的 CPU 频率、无降频
纯净试剂 真实但固定的数据集
空白对照 基线组
记录环境参数 采集环境元数据

关键差异:化学实验的污染通常是可见的(溶液变色),性能实验的污染是不可见的——你只会看到一个稍微高一点的 P99,然后开始怀疑代码。


二、五类污染源与处理方式

❶ 同一台机器上的其他负载

污染源 影响 处理
压测客户端与服务同机 互相抢 CPU,两个方向都失真 必须分离(最低要求:不同容器 + CPU 限制)
IDE / 浏览器 / 本地开发 抢占 CPU 与内存 关掉
其他服务实例 争抢资源 独占环境
监控 agent 持续采集消耗资源 记录其开销,或降频采样

❷ 机器自身的周期性任务

污染源 频率 表现 处理
数据库 autovacuum 不定 磁盘 IO 尖刺 实验前手动 VACUUM ANALYZE,期间可临时调整 autovacuum 参数
数据库 checkpoint 不定 IO 抖动 观察 checkpoint 配置与日志时间戳
日志轮转 按大小/时间 磁盘写尖刺 避开轮转时刻,或临时调大阈值
备份任务 通常夜间 全盘 IO 与网络 实验前确认已关闭
cron / 定时任务 各种 周期性 CPU/IO pgrep -a -f cron 确认
系统更新 不定 突发 CPU/IO 关闭自动更新

❸ CPU 频率与调度

污染源 影响 处理
CPU governor(节能模式) 频率波动 → 延迟抖动 设为 performance
Turbo Boost 前后跑的基准频率不同 要么固定频率,要么全程接受 turbo(关键是一致)
未绑核 线程在不同核间迁移,缓存失效 taskset 绑定
超线程干扰 同一物理核上的两个逻辑核互相影响 明确是否启用,并保持一致
NUMA 跨节点 内存访问变慢 numactl 绑定

❹ 云环境的不可控因素

污染源 表现 处理
邻居抢占(steal) st 指标高,延迟抖动 换独占型实例;或多次实验取可比时段
网络路径变化 跨 AZ 调用延迟变化 固定可用区;记录网络拓扑
存储 IOPS 配额 IO 突发受限 确认卷类型与 IOPS 上限
宿主机维护 突发迁移 记录实验期间的事件

❺ 实验自身的干扰

污染源 影响 处理
高开销剖析(profiler) 占用 CPU,抬高 P99 基线组和实验组必须用同样的观测配置
详细日志(DEBUG) 同步 IO,可能占 10%+ 统一日志级别
/metrics 高频抓取 序列化开销 统一 scrape interval
压测机自身瓶颈 实际到达率低于设定值 每次压测先验证到达率(第 4 章 4.1)

三、实验前的检查清单(脚本化)

## 环境检查(实验前逐项确认)

### 机器
- [ ] CPU governor = performance
- [ ] 已绑核(或明确不绑核,并保持一致)
- [ ] 关闭了自动更新
- [ ] 无其他无关进程(`ps aux --sort=-%cpu | head`)
- [ ] `st`(steal)< 1%(多次采样确认)

### 容器
- [ ] CPU limit 与生产一致
- [ ] 内存 limit 与生产一致
- [ ] 无 sidecar 干扰(或已计入)
- [ ] 节流比例 < 1%(`/sys/fs/cgroup/cpu.stat`)

### 数据库
- [ ] autovacuum 状态已知(关闭或已记录)
- [ ] 无长事务(`pg_stat_activity`)
- [ ] 统计信息已更新(`ANALYZE`)
- [ ] 连接数无人抢占

### 服务
- [ ] JVM 参数与生产一致
- [ ] 日志级别一致
- [ ] 观测配置一致(JFR/profiler)
- [ ] 服务已重启(避免上一轮热状态污染)

### 压测
- [ ] 客户端独立机器/容器
- [ ] 脚本已冻结快照
- [ ] 已预热(或明确预热阶段)
- [ ] 到达率可验证

### 记录
- [ ] 环境元数据已采集(第 1 章 1.7)
- [ ] 环境事件时间线已开启记录

四、绑核与频率固定的具体命令

# ① 固定 CPU 频率(Linux)
sudo cpupower frequency-set -g performance
# 验证
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor

# ② 绑定到指定核(服务用 2-5,压测客户端用 6-7,互不干扰)
taskset -c 2-5 java -jar app.jar
taskset -c 6-7 k6 run loadtest.js

# ③ NUMA 绑定
numactl --cpunodebind=0 --membind=0 java -jar app.jar

# ④ 查看 NUMA 拓扑
numactl --hardware

# ⑤ 容器内限制 JVM 看到的核数(避免线程池过大)
java -XX:ActiveProcessorCount=2 -jar app.jar

# ⑥ 关闭 swap(避免内存压力导致的抖动)
sudo swapoff -a

注意:绑核能降低噪声,但也可能降低吞吐(因为无法利用其他核)。如果实验目的是测「最大吞吐」,绑核会让结果偏低。关键是「一致性」而不是「绝对最优」——基线组和实验组用同样的配置即可。


五、验证环境是否「干净」的三个方法

方法一:空跑基线(最有说服力)

在没有任何改动的条件下,连续跑 5 次实验,看结果的波动范围。

如果 5 次结果几乎一致(CV < 3%)→ 环境干净
如果波动很大(CV > 10%)→ 环境有问题,先修环境

这就是第 7 节的「噪声底线」——它是判断"环境干不干净"的量化标准。

方法二:观测 st 与上下文切换

# 连续采样 10 次
for i in $(seq 1 10); do
  vmstat 1 1 | tail -1 | awk '{print "st=" $17 "% cs=" $12}'
  sleep 1
done

判读:st 持续 > 3% → 环境不可控;cs 波动极大 → 有其他负载。

方法三:检查「实验期间的意外事件」

# 记录实验期间的系统日志(内核、OOM、CPU 限流)
dmesg -T | tail -50
journalctl --since "1 hour ago" | grep -iE "oom|throttl|error" | head -20

特别注意 dmesg 里的 OOM 与 TCP 丢包记录——它们能解释很多"莫名奇妙"的异常。


六、本节小结

  1. 环境里的任何"没关掉的东西"都会变成噪声,而噪声不报错——它只让结论不可信。
  2. 五类污染源:机器上的其他负载、周期性任务、CPU 频率与调度、云环境不可控因素、实验自身的干扰。
  3. 最后一项最容易被忽略:剖析工具、详细日志、指标抓取都会影响结果,基线组和实验组必须配置一致。
  4. 环境检查要清单化 + 脚本化,否则一定会漏。
  5. 验证环境是否干净的方法:空跑基线看 CV(最有力)、看 st 与 cs、查系统日志。
  6. 绑核与固定频率的目标是一致性,不是绝对最优。

七、自测

  1. 你发现每次压测的前 2 分钟 P99 都偏高,之后下降。这可能是什么原因?请列出至少三种可能,以及各自的验证方法。
  2. 为什么「观测工具的变化」属于环境隔离问题?如果基线组开了 JFR 而实验组没开,你会看到什么假象?
  3. 一个团队在云主机上做实验,结果波动很大(P99 在 80–300 ms 之间跳)。请给出三个排查方向,并说明哪个最可能是根因。
  1. 可能原因与验证:① JVM 预热不足——验证:用滚动 CV 观察是否收敛(第 2 章 2.4);看 -XX:+PrintCompilation 的编译事件时间分布。② 连接池/缓存冷启动——验证:观察 db_pool_active 是否在前 2 分钟逐渐上升至稳态;观察缓存命中率曲线。③ 数据库缓存未热——验证:看 PG 的 pg_stat_database.blks_hit / blks_read 比例在前 2 分钟的变化;或看查询耗时的 P50 曲线形状。④ 还有可能是上一个实验的残留影响(如果没重启服务)——验证:确认实验前是否重启了服务。⑤ 压测机预热(k6 自身也需要 GC/JIT 预热,如果是 JVM 系工具)。结论:无论哪种,处理方式都是把前 2 分钟作为预热阶段,不计入统计——但前提是确认它确实会收敛。
  2. 因为观测工具会消耗被测系统的资源,所以它和"机器上跑着别的程序"在本质上是一类污染(第 0 章 0.9 节:观测改变被观测对象)。如果基线组开了 JFR(约 1–2% 开销)而实验组没开,你会看到实验组"变快了"1–2%——但这个差异完全来自观测开销,不是代码优化。更严重的情况:如果基线组用了 -e cpu -i 1ms 的高频采样(开销可能 5–15%),那么实验组会显得"提升 10%",而实际上什么都没优化。这就是为什么观测配置必须冻结。
  3. 三个排查方向与优先级:① 云主机邻居抢占(steal)——最可能的根因。验证:vmstat 看 st 列是否持续 > 3%;如果是,说明宿主机的其他租户在抢占 CPU,你的应用什么都没做也会抖动。处理:换独占型实例、调整部署位置,或接受这个噪声并把它写进噪声底线。② CPU 频率波动或降频——验证:grep MHz /proc/cpuinfo 多次采样看频率是否变化;检查 governor。③ 周期性任务或自动扩缩容——验证:记录实验期间的环境事件时间线(cron、备份、其他服务部署),看抖动是否与某些时刻对齐。为什么 steal 最可能:因为它的典型特征就是"随机的大幅抖动"(应用侧所有指标正常,但延迟周期性飙升),而后两者通常有更规律的模式。验证顺序:先看 st(一条命令),再查频率,最后查事件时间线。