简介:dgreadiness_v3.6.zip 面向在 Windows 10 上使用 VMware Workstation 运行 Ubuntu 等虚拟机的开发与运维人员,针对关闭 Hyper-V 后仍提示 VMware 与 Device/Credential Guard 不兼容、虚拟机无法启动的问题,提供基于 Windows Defender Device Guard 与 Credential Guard 硬件就绪工具的禁用方案。压缩包共 6 个文件,约 32KB,包含 PowerShell 工具脚本、p7b 策略文件、xml 配置模板和 txt 说明文档,分别用于执行禁用配置、承载策略设置、提供审计/强制模式模板及使用说明,体量轻便、结构明确。已有 3189 人学习下载,说明该问题在 Win10 虚拟机场景中较常见。读者可借助其中的脚本与策略模板快速完成 Credential Guard 关闭,减少手动修改组策略和注册表的试错成本,也可据说明文档理解各配置项作用,为同类兼容性排错提供参考。
1. dgreadiness_v3.6.zip 不是安装包,是切换前的一张体检单
dgreadiness_v3.6.zip 这个名字本身就说明了它的形态:一个压缩包、带版本号、没有安装向导,解开来是一堆 SQL 和 shell。它的作用不是部署容灾,而是在你已经有一套主备库(主库 + 一个或多个备库)之后,回答一个很具体的问题——现在这一刻,我敢不敢做切换演练?很多团队的做法是切之前拉个会,几个人分别敲几条命令看看,凭感觉拍板;这个包要做的事,是把这些零散判断固化成一份可重复、可对比、可交给别人的报告。它适合两类人:一类是要定期做容灾演练的 DBA,另一类是接手了一套没人维护的备库、想先摸清底细的运维。读完你能自己解包、配连接、改检查项、定阈值,也知道哪些绿灯其实是假的。
2. 就绪度先立标准:六类检查项和它们的权重怎么定
打开一个 readiness 包,最忌讳的动作是直接bash run.sh然后盯着输出里红色的那几行看。红色代表什么、多红才算不能切、这次红下次绿是环境变了还是脚本抽风——这些问题在脚本里没有答案,得你自己先定。所以第一章不做操作,先把“ready”这个词拆成可判定的指标。
2.1 传输层、应用层、结构层:就绪度的三个观察面
我把所有检查项归到三个观察面,这个分法决定了后面报告的排版,也决定了排错时的查找顺序。
传输层看的是日志能不能从主库顺利落到备库:归档目的地状态是不是 VALID、有没有 ERROR 文本、传输延迟多少、归档目的地所在文件系统的剩余空间、网络往返时延和带宽是否够峰值 redo 量。这一层出问题,表现是备库长时间收不到新日志,但主库业务一切正常,最容易被忽略。
应用层看的是日志到了之后有没有被真正应用:MRP 进程在不在、状态是 APPLYING_LOG 还是 WAITING_FOR_LOG、应用延迟多少、备库已应用的最大序列号和主库当前序列号差多少、有没有归档缺口(GAP)。这一层最容易出“假绿灯”,后面避坑那章会专门讲。
结构层看的是两边长得像不像:db_unique_name 和 log_archive_config 是否互认、standby_file_management 是否 AUTO、保护模式和 redo 传输是 SYNC 还是 ASYNC、standby redo log 的组数和单组大小、临时文件和数据文件路径、密码文件、以及多租户下每个 PDB 的状态。结构层的问题平时完全不疼,一切换就卡死,是典型的“平时不查、演练翻车”。
三个面里,传输层和应用层是动态指标,数值会随业务波动;结构层是静态指标,除非有人改配置,否则长期不变。这个差异直接影响采集频率:动态指标要频繁采、看趋势,静态指标一次采完存成基线就行,不必每次全量跑。
提示:如果包里的检查项命名和上面不一样,不要急着改脚本,先按这三个面把现有项归类,缺哪一类再补,比重写省事得多。
2.2 权重怎么定:按「切换后多久能对外服务」倒推
权重不是技术参数,是业务参数。我的做法是问一句话:这一项不达标,切换后要多花多久才能对外服务?答案越长,权重越高。按这个口径,归档缺口和结构层错配是最贵的——缺口意味着要补日志甚至重建,结构错配意味着切换根本起不来,这两类给高权重;传输延迟和应用延迟影响的是切换瞬间丢多少数据、追平要多久,中等权重;目的地状态、SRL 配置这类属于“不达标立刻知道、修起来也快”的,低权重。
| 检查项 | 观察面 | 权重 | 不达标后果 |
|---|---|---|---|
| 归档序列缺口 | 应用层 | 25 | 需补日志或重建备库 |
| 参数与结构一致性 | 结构层 | 20 | 切换可能直接失败 |
| 应用延迟 | 应用层 | 20 | 切换后追平日志耗时长 |
| 传输延迟 | 传输层 | 15 | 数据丢失窗口变大 |
| 目的地状态与错误文本 | 传输层 | 10 | 日志传输中断 |
| SRL 组数与大小 | 结构层 | 10 | 切换时日志写不下 |
这张表不需要照抄,权重之和凑够 100 就行,关键是每一项的权重你能对着业务说清楚理由。说不清理由的项,要么删掉,要么降权,别放在那里制造噪音。
2.3 判定口径要先写死,否则报告没法比较
分数之外还要有档位,否则你只知道“78 分”,不知道“78 分能不能切”。我一般分三档:PASS、WARN、FAIL。PASS 拿满分,WARN 拿一半,FAIL 拿零分。但光有分数不够——结构层错配这种项,哪怕总分 95 也不能切,所以还要引入“否决项”概念:归档缺口、SRL 配置、参数一致性这三项只要有一个 FAIL,无论总分多少都判定为 NOT READY。
这样做的好处是报告有两层结论:分数用来横向比较(这次 92、上次 88,是在变好还是变差),否决项用来纵向决策(能不能现在切)。只给分数,人会拿分数当挡箭牌;只给否决项,又看不出趋势。两者都要。
还有一条容易被忽略:口径要写进配置文件,不要写死在脚本里。阈值、权重、否决项名单都放conf/env.conf,这样半年后别人接手,改一个数字就能适配另一套库,不用读懂 SQL。
3. 解包与首轮巡检:三条命令跑出第一份报告
标准立完,动手。这一章的目标不是跑出完美报告,是在 20 分钟内跑出第一份“能看”的报告,哪怕里面一半是 FAIL——先有基线,再谈优化。
3.1 解包与目录约定:先固定路径再谈自动化
不要在原目录里直接解压。压缩包解开后通常有bin/、sql/、conf/、out/这几类目录,如果每次巡检都解到不同路径,报告会散落在各处,后面想做趋势对比就得靠人肉找文件。我固定用一个版本化路径:
# 固定安装路径,按版本号分目录,升级时并存而不是覆盖 sudo mkdir -p /opt/dgcheck/3.6 sudo unzip -q dgreadiness_v3.6.zip -d /opt/dgcheck/3.6 # 看一眼结构,确认入口脚本和 SQL 目录在哪 find /opt/dgcheck/3.6 -maxdepth 2 -type d | sort ls -l /opt/dgcheck/3.6/bin/ 2>/dev/null || ls -l /opt/dgcheck/3.6 # 脚本给执行权限,但不要给全局可写 sudo chown -R dbcheck:dba /opt/dgcheck/3.6 sudo chmod -R 750 /opt/dgcheck/3.6 sudo chmod 640 /opt/dgcheck/3.6/conf/*.conf 2>/dev/null这段的关键点在三处。-d指定目标目录,避免解压出来的文件平铺在当前目录,把家目录搞乱。find -maxdepth 2是先摸清结构再决定怎么调,因为不同打包习惯里入口脚本可能在bin/也可能在根目录,ls一次就知道,比自己猜快。chmod 640给配置文件是因为里面通常有连接串和口令,权限放开等于把库的钥匙挂在墙上。
专用运行账号也是必要的一步。我一般建一个只读账号dbcheck跑巡检,让它属于dba组以便读v$视图,但不给它sysdba。巡检脚本只需要看状态,不需要改任何东西——一个能改东西的巡检脚本,某天就会改错东西,这是血泪经验。
注意:如果包里带
.sh之外的可执行文件(比如编译好的采集二进制),先用file看一眼架构和依赖,别在 x86 机器上跑 arm 的产物,报错信息会误导你以为连不上库。
3.2 连接配置与最小巡检命令
配置文件只放连接信息和阈值,不放任何逻辑。常见做法是conf/env.conf这种以 shell 变量形式被source进来的文件:
# conf/env.conf —— 只放连接与阈值,逻辑一律进脚本 PRIMARY_TNS=dg_primary # 主库连接串,走 tnsnames 或等价命名 STANDBY_TNS=dg_standby # 备库连接串 RUN_USER=dbcheck # 巡检专用账号,非 sysdba THRESH_TRANSPORT_LAG=60 # 传输延迟告警线,单位秒 THRESH_APPLY_LAG=120 # 应用延迟告警线,单位秒 THRESH_ARCHIVE_GAP=0 # 允许的序列缺口,通常为 0 THRESH_FRA_PCT=85 # 归档区使用率告警线,百分比 DENY_ITEMS="archive_gap,srl_config,param_diff" # 否决项名单四个阈值里,前三个是硬指标,最后一个(归档区使用率)是很多人不做但极容易翻车的项——归档区满的那一刻,主库会挂起,备库也跟着停,属于“平时不查、爆起来要命”的类型。DENY_ITEMS是把第 2 章的口径落地的字段,后面折算报告时直接读这个变量。
配好之后,首轮就跑最小集合:一个主库侧、一个备库侧,别一上来跑全套,不然输出里几百行,你不知道哪行是关键的。
#!/usr/bin/env bash # bin/first_run.sh —— 首轮最小巡检,只采集传输与应用两个面 set -uo pipefail BASE=/opt/dgcheck/3.6 source "$BASE/conf/env.conf" TS=$(date +%Y%m%d_%H%M%S) OUT="$BASE/out/$TS" mkdir -p "$OUT" # 主库:归档目的地状态 + 当前最大序列 sqlplus -S "$RUN_USER"@"$PRIMARY_TNS" @"$BASE/sql/10_primary.sql" \ > "$OUT/primary.txt" 2>&1 # 备库:应用进程状态 + 已应用序列 sqlplus -S "$RUN_USER"@"$STANDBY_TNS" @"$BASE/sql/20_standby.sql" \ > "$OUT/standby.txt" 2>&1 echo "report dir: $OUT" grep -iE "error|ORA-|SP2-" "$OUT"/*.txt && echo "存在错误,先看上面几行" || echo "采集无报错"set -uo pipefail没有加-e,这是有意的:首轮巡检最怕的是某项失败就整体退出,导致你只看到第一个问题、看不到后面的问题。所以让每个采集动作各自失败、各自留痕,最后统一 grep 一遍错误关键字。-S是静默模式,去掉客户端横幅和提示符,输出更干净,后面好解析。时间戳精确到秒,是为了同一天多次演练时不覆盖文件。
grep -iE "error|ORA-|SP2-"这行是一个廉价但有效的兜底。数据库客户端的报错文本格式很好认,宽泛匹配一下就能把“没连上库”和“连上了但查询出错”区分开——这两种情况后面的处理完全不一样。
3.3 输出目录与失败兜底
out/目录要有轮转策略,别等它撑满。一次全量巡检的输出不大,但每天跑、跑半年,加上有人手动重跑,很容易堆到几百个目录。我一般保留最近 30 次全量 + 所有“变更前后”的对比目录,其余删除:
# 只保留最近 30 个时间戳目录,其余清理;命名规则必须严格按时间戳 cd /opt/dgcheck/3.6/out || exit 0 ls -1d 2*/ | sort | head -n -30 | xargs -r rm -rf前提是目录名严格是YYYYMMDD_HHMMSS,sort的字典序才等于时间序。如果包自带的输出目录用了别的命名,比如带主机名,就先改命名规则再用这条命令,否则head -n -30删掉的可能是昨天刚跑的基线,这种翻车比不清理更糟。
失败兜底还有一个细节:退出码。巡检脚本的退出码要能被上层调度感知——0 表示全 PASS,1 表示有 WARN,2 表示有 FAIL 或否决项。这样挂到定时任务或流水线上,不用解析文本就知道结果。很多包默认只 echo 不 return,接进自动化就得自己补这几行。
4. 改 SQL 与定阈值:把通用检查项调成你库的形状
一份通用脚本跑在你自己的库上,一定会有一半检查项“看起来对但不准”。这不是脚本的问题,是每套库的形态差异太大:有的单实例、有的多租户;有的保护模式要求零丢失、有的能接受几分钟延迟;有的 redo 峰值每秒几十兆、有的几百 K。所以第三章的核心动作是改,改 SQL 的取数口径、改阈值的红线位置。
4.1 三类必改的 SQL:序列缺口、备库日志、参数一致性
序列缺口是最该改的一条。很多脚本只查应用延迟,但延迟是个瞬时值——刚刚应用完一批日志,延迟会显示接近 0,哪怕中间其实缺了一段日志没传过来。真正的缺口要看序列号连续性:
-- sql/30_gap.sql:主库侧取每个线程的当前序列 set lines 200 pages 100 feedback off heading on col thread# format 999 col cur_seq format 9999999 select thread#, max(sequence#) as cur_seq from v$thread group by thread# order by thread#;-- sql/31_applied.sql:备库侧取每个线程已应用的序列 col thread# format 999 col applied_seq format 9999999 select thread#, max(sequence#) as applied_seq from v$archived_log where applied = 'YES' group by thread# order by thread#;逻辑说明:主库的v$thread给出每个线程当前写到哪,备库的v$archived_log里applied='YES'的最大值给出应用到哪。两者相减就是缺口。多线程(RAC 形态)下必须按 thread 分别算,只取全局最大值会漏掉某个线程掉队的场景——这种场景在小库上不常见,在大库上就是演练失败的常见原因。
参数说明:applied='YES'这个过滤条件是关键,去掉它拿到的是“收到了多少”而不是“应用了多少”,这两者在备库开启实时应用时差别很大。如果你的库用的是备库实时应用,还可以补一条查v$standby_apply_progress,但对缺口判断来说上面两条已经够用。
**备库日志(SRL)**是最容易被跳过的一条,因为它不影响日常运行,只影响切换:
-- sql/40_srl.sql:两边分别跑,比对组数与单组大小 col group# format 999 col mb format 9999 select group#, thread#, bytes/1024/1024 as mb, status from v$standby_log order by thread#, group#; select group#, thread#, bytes/1024/1024 as mb from v$log order by thread#, group#;判据是经验值:备库 SRL 的组数不少于主库在线 redo 组数 + 每线程 1 组,单组大小与主库一致(不能小于)。组数不够,切换瞬间会因为无处写日志而卡住;大小不一致,切换后可能出现频繁切换(log switch)拖慢性能。这两条都属于“不改也没事、一切换就死”的类型。
参数一致性要挑真正影响切换的那几个,别把全部参数拉出来比对,噪音太大:
-- sql/50_param.sql:只比对与容灾强相关的参数 col name format a32 col value format a48 select name, value from v$parameter where name in ( 'db_unique_name','log_archive_config','log_archive_dest_state_2', 'standby_file_management','protection_mode','archive_log_format', 'db_recovery_file_dest_size','remote_login_passwordfile' ) order by name;这十个参数里,log_archive_config少写一个备库名、standby_file_management是 MANUAL、remote_login_passwordfile不是 EXCLUSIVE,任意一条都能让切换直接失败。db_recovery_file_dest_size放在这里是很多人的疑问——它影响的是归档空间,不在切换时立刻报错,但会在切换后补日志阶段把空间撑爆,属于第二波翻车。
提示:参数比对一定要在主备两边都跑同一条 SQL 再 diff,不要拿主库的输出去和心里记着的备库配置比,记忆比脚本更容易错。
4.2 阈值不是拍脑袋:用历史延迟分布定红线
THRESH_APPLY_LAG=120这个数字从哪来?很多人是从别人的脚本里抄的。抄来的数字有两个问题:一是和自己的业务量不匹配,二是没人知道为什么是 120。
我的做法是先裸跑两周,只采集不判定,把每次的传输延迟、应用延迟记下来,然后看分布:
| 分位 | 传输延迟 | 应用延迟 | 用途 |
|---|---|---|---|
| p50 | 2 秒 | 15 秒 | 常态参考,不必告警 |
| p95 | 40 秒 | 90 秒 | WARN 线,留出批量作业的余量 |
| p99 | 75 秒 | 150 秒 | FAIL 线,超过说明真的出问题了 |
规律很朴素:WARN 线设在 p95 附近,FAIL 线设在 p99 或 p95 的两倍。这样日常波动不会天天报警,真出事的时候一定越线。设置完还要回头看一次——如果一周内 WARN 触发了十几次,说明线画低了,或者这条链路的容量本来就不够,该扩容而不是调阈值。
批量作业窗口要单独对待。夜间跑大批量的时候,apply lag 从 15 秒冲到 300 秒是正常的,如果这时 FAIL 了,你会收到一堆无效告警,然后开始忽略告警——这是运维里最危险的状态。解决办法是给批量窗口单独一组阈值,按时间切换配置,而不是把全局阈值放宽到 300 秒(那样等于白天也不告警了)。
4.3 把文本输出收敛成一张就绪度表
采集出来是一堆文本,人看得懂、脚本看不懂。中间加一层折算,把关键指标压成key=value,再交给一个小脚本算分:
# 采集脚本末尾:把关键指标压成一行一个键值,供折算脚本消费 { echo "archive_gap=$ARCHIVE_GAP" echo "apply_lag=$APPLY_LAG" echo "transport_lag=$TRANSPORT_LAG" echo "dest_status=$DEST_STATUS" echo "srl_ok=$SRL_OK" echo "param_diff=$PARAM_DIFF" } > "$OUT/metrics.kv"#!/usr/bin/env python3 # bin/score.py —— 把 metrics.kv 折算成就绪度分数与结论 import sys, os WEIGHTS = {"archive_gap": 25, "param_diff": 20, "apply_lag": 20, "transport_lag": 15, "dest_status": 10, "srl_ok": 10} DENY = set(os.environ.get("DENY_ITEMS", "").split(",")) def load(path): kv = {} with open(path, encoding="utf-8") as f: for line in f: line = line.strip() if not line or "=" not in line: continue k, v = line.split("=", 1) kv[k.strip()] = v.strip() return kv def judge(key, raw, kv): # 数值型指标与阈值比较,非数值型按 1/0 判定 if key == "archive_gap": return "PASS" if int(raw) <= 0 else ("WARN" if int(raw) <= 3 else "FAIL") if key in ("apply_lag", "transport_lag"): warn = float(os.environ.get("THRESH_" + key.upper(), 120)) v = float(raw) return "PASS" if v <= warn else ("WARN" if v <= warn * 2 else "FAIL") return "PASS" if raw in ("1", "VALID", "TRUE") else "FAIL" def main(path): kv = load(path) score, denies, rows = 0.0, [], [] for key, w in WEIGHTS.items(): if key not in kv: rows.append((key, "MISSING", 0)) continue res = judge(key, kv[key], kv) got = w if res == "PASS" else (w * 0.5 if res == "WARN" else 0) score += got if res == "FAIL" and key in DENY: denies.append(key) rows.append((key, res, got)) for k, r, g in rows: print(f"{k:16s} {r:8s} {g:>5.1f}") verdict = "NOT READY" if denies else ("READY" if score >= 90 else "RISKY") print(f"\nscore={score:.1f} verdict={verdict} deny={','.join(denies) or '-'}") if __name__ == "__main__": main(sys.argv[1])逻辑上分三层。WEIGHTS是第 2 章那张表的代码化;judge是个小状态机,把原始数值翻译成 PASS/WARN/FAIL,注意 WARN 的判定用的是“阈值的两倍”,这样只改一个配置就能同时控制两档;最后主线按权重累加,denies收集否决项。指标缺失(MISSING)单独记一档而不是直接 FAIL,因为缺失往往是采集失败,不是环境坏,混在一起会误导排查方向。
参数说明:DENY_ITEMS从环境变量读,是为了让同一份脚本能跑“演练前严格模式”和“日常宽松模式”两套口径,不用改代码。阈值同样从环境变量兜底(os.environ.get(..., 120)),保证脚本单独运行也不崩。
跑起来验证一下:
# 用一次真实采集结果折算,先看分数再看否决项 cd /opt/dgcheck/3.6 DENY_ITEMS="archive_gap,srl_ok,param_diff" python3 bin/score.py out/20240101_020000/metrics.kv输出如果verdict=NOT READY但score=92.0,说明分数被某一两个高分项撑着,否决项里有 FAIL——这正是第 2 章说的“分数管趋势、否决项管决策”,别被 92 分骗了。
5. 排查记录:五个让报告「全绿」却切不过去的坑
5.1 延迟显示 0 秒,但备库落后两小时
现象:报告里 apply lag 一直是 0 到 2 秒,看起来健康;真做切换时发现丢了大量数据,业务对账对不上。
原因:apply lag 的语义是“最近一条已应用日志的应用时间与当前时间之差”,它衡量的是“追平速度”,不是“落后多少”。如果日志根本没传到备库,备库无日志可应用,这个值反而会很小甚至为 0——这是最典型的假绿灯。
解决:把归档缺口(序列号差值)作为主判据,延迟只作为辅判据。缺口不为 0 时,无论延迟多好看都判 FAIL。这条一定要进否决项名单。
5.2 SRL 组数和主库一样多,切换仍然卡住
现象:比对下来 SRL 组数“看起来够”,切换演练时仍然在 redo 写入阶段卡了几十秒。
原因:组数够但单组大小小于主库的在线 redo,切换后日志切换频率暴增;或者组数是照“主库组数”配的,没有算上每线程多一组的余量。多线程环境下,每个线程都需要独立的 SRL 组,共用一个组池也会卡。
解决:按“每线程主库在线组数 + 1”配,单组大小严格等于主库 redo 大小,字节数一致而不是四舍五入后的兆数一致。改完重跑 4.1 里那条 SRL SQL 确认。
5.3 巡检账号权限过高,审计上翻车
现象:脚本跑得好好的,某次安全审计里被指出巡检使用了高权限账号,且口令明文放在配置文件里。
原因:为了省事直接用了最高权限账号,顺手把口令写进env.conf,文件权限还是 644。
解决:换专用只读账号,只授查询v$视图所需的最小权限;配置文件权限收到 640 并限定属组;口令改走外部认证或凭据文件,不进版本库。这条不是技术问题但会真的让项目停掉,早改比晚改省事。
5.4 输出目录把磁盘撑满,反过来拖垮归档
现象:某天主库归档区写满,业务挂起;查下来是巡检报告占了同一个文件系统。
原因:out/目录没做轮转,加上有人为了排查重复手动跑全量,一年下来堆了上千个目录。巡检工具本身成了故障源,这个坑我见过不止一次。
解决:输出目录和归档区物理隔离,别放同一个挂载点;加轮转(3.3 那条),并给巡检脚本本身加一个“单次输出不超过 N 兆”的保护——如果超了说明采集范围写错了,应该报错而不是继续写。
5.5 多租户下 PDB 状态没纳入检查
现象:容器库层面全绿,切换后发现某个 PDB 处于 MOUNT 或者没开读写。
原因:检查项只查了容器级视图,没有遍历 PDB。多租户环境里 PDB 的状态是独立管理的,容器级看起来一切正常。
解决:在结构层加一条遍历 PDB 的查询,把每个 PDB 的打开模式和恢复状态列出来,任一 PDB 不是 READ WRITE 就判 WARN,若你的业务依赖该 PDB 则升级为 FAIL。这条属于“不查不知道、查了才发现”的类型,建议第一次就补上。
6. 把它钉进演练流程:基线对比与回归的两种做法
跑通一次巡检不算什么,能让它在半年后仍然是团队判断切换时机的依据,才算落地。我用两种做法把它钉住:定时留基线,变更前后做 diff。
定时巡检我挂在业务低峰,只跑全量并把结果目录按“日期_类型”命名,类型分daily和drill。daily每天一次,用来攒趋势数据;drill只在演练和变更前跑,用来做决策。两者阈值可以不同:daily用宽松口径只看异常,drill用严格口径加全部否决项。这样平时不会天天被报警吵,真要切换的时候有一个不会说谎的口径。
# 低峰定时:每日基线 + 目录按类型分,方便后面按类型做趋势 30 2 * * * /opt/dgcheck/3.6/bin/run_all.sh -t daily \ >> /opt/dgcheck/3.6/out/cron.log 2>&1 # 演练前手工:严格口径,带完整否决项 # /opt/dgcheck/3.6/bin/run_all.sh -t drill第二种做法更重要:变更前后做 diff,而不是看单次分数。因为分数会骗人——加了个索引、换了批处理时间,分数可能从 88 涨到 93,但某项具体指标其实是恶化的,只是被其他项盖过去了。所以我每次都把metrics.kv和上一次 drill 的做逐键对比:
# 变更前后逐键 diff,只关心变化方向和幅度 diff <(sort out/20240101_drill/metrics.kv) \ <(sort out/20240201_drill/metrics.kv) | grep -E '^[<>]'输出里>是变更后,<是变更前。如果archive_gap从 0 变成 1,哪怕分数还在 90 以上,也要停下来查——缺口从 0 到 1 往往意味着某个归档目的地开始间歇性失败,这种早期信号只有 diff 抓得到。
还有一个我踩过的细节:基线要跟着版本走。升级到 3.6 之后新增了检查项,旧基线里没有这些键,diff 出来一堆>,看着像恶化其实是新增。所以升级后第一件事是重跑一次 drill 生成新基线,并把旧基线的metrics.kv单独归档,不要混在同一个目录里比。
我自己的习惯是每次演练前打印一行结论贴在工单里:score / verdict / deny 列表 / 与上次 drill 的差异项。四个字段,谁看都清楚,也逼着自己别只看总分。这套东西的价值不在脚本多聪明,在于它把“我觉得可以切”变成“这份报告说可以切,而且和上次比没变差”——前者靠人,后者能交接。希望帮到你。
本文还有配套的精品资源,点击获取