dsh-plugin-dm-recovery-readiness
达梦 DM8 数据库可恢复能力评估插件。按 16 个恢复场景逐项对照「要求 vs 本库实际」,回答能恢复到哪一点、要丢什么,生成 HTML 报告。
单实例与共享存储集群(DMDSC 两节点)都已完成真机验证;文档里的地址与账号均做过替换,非真实环境。
目录
功能
- 16 个恢复场景,分三组覆盖:逻辑损坏 5 项(单条记录误删误改、整表误 DROP / TRUNCATE、整表批量误 UPDATE、schema 被 DROP、表结构误改)、物理损坏 8 项(单数据页、单数据文件、控制文件、联机日志、归档日志、备份集、整机与存储、表空间)、集群容灾 3 项(DMDSC 单节点故障、全集群故障、站点级灾难)
- 每个场景给出五档能力判定:可恢复 / 仅能粗粒度恢复 / 不可恢复 / 无法判定 / 不适用,并附一张「要求 vs 本库实际」条件表,只对未满足的条件给建议
- 回答「能恢复到哪一点」:归档链逐段核对(逐段验
next.ARCH_LSN === prev.CLSN + 1,再看并集有无洞)、未归档尾部、可回溯窗口的时间起点、时间 ↔ LSN 双向夹逼 - 备份可用性核对:库里有哪些备份集、能恢复到哪一点、备份本身可不可信,并核对调用方自行提供的备份位置。校验目标按「账本 → 磁盘上找到的备份集 → 调用方给的
backupPath」三档取;一个都取不到时也出结论(说明三个来源为何都不可用),不静默省略这一节 —— 备份集在 ASM 上时find本来就够不到,backupPath是账本为空时唯一的位置线索 - 支持 DMDSC 逐节点核对:节点间参数配置差异、坏页归属、归档目录自足性(实测交叉配置下两个归档目录各自都不自足)。集群口径上还有两点:①「本节点备份日志里没有
N corrupted pages during backup」属正常形态(全备只由其中一个节点发起),别的节点已给出裁决时判提示而非警告;②ARCH_HANG_FLAG的解读同时覆盖 LOCAL 与 REMOTE 目标 —— REMOTE 取 0 意味着对端不可达时静默放弃投递,那份归档冗余会悄悄失效 - 可选的主机侧只读核验(SSH):
dmrman CHECK BACKUPSET、备份日志的全库逐页裁决、备份文件查找 - 输出单文件 HTML 报告,同目录另存一份内容相同的
findings.json - 库内 SQL 均为查询语句,主机侧只执行 3 类只读命令。不扫描、不修复、不改配置
分级的依据是「哪个场景救不了」,不是「哪个参数没设」—— 参数齐全但救不了,仍然是严重问题。
环境要求
| 项 | 要求 |
|---|---|
| Node.js | ≥ 20 |
| Java | 8 及以上(JDK 或 JRE)。桥接器以源码方式运行,首次运行编译一次,结果缓存在工作目录 |
| 达梦 JDBC 驱动 | DmJdbcDriver18.jar 或 DmJdbcDriver8.jar,需自行提供,见安装第 2 步 |
| DeepSeek Harness | 0.1.x |
| plink | 仅主机侧核验需要,在 PATH 中即可,或用 plinkPath 指定 |
安装
1. 安装插件
dsh plugin --profile web add github:Shiyuedong-Jade/DM-recovery
从本地目录安装:
dsh plugin --profile web add file:/path/to/dsh-plugin-dm-recovery-readiness
file: 安装时绝对路径不能含空格:dsh plugin 会转发给 pnpm,参数在空格处断开。
2. 放置达梦 JDBC 驱动
驱动 jar 是达梦的商业组件,不随插件分发。把达梦安装目录下的
dmdbms/drivers/jdbc/DmJdbcDriver18.jar 复制到工作目录的 drivers/ 下:
| 平台 | 路径 |
|---|---|
| Windows | %USERPROFILE%\.dsh\dm-recovery-readiness\drivers\ |
| Linux | ~/.dsh/dm-recovery-readiness/drivers/ |
插件加载时会自动建好工作目录(drivers/、java/、runtime/)。工作目录可用插件配置 home 修改,见配置。
3. 重启
首次安装后重启 dsh web,并刷新已打开的页面。
使用
在会话里调用 dm_recovery_readiness。目标、账号、口令都能在设置卡片里预先填好,也可以每次调用时填。
工具有一个必填参数 action,共六个动作:
| action | 说明 |
|---|---|
report |
汇总成一份 HTML 报告与一份 findings.json 落盘(本插件的主产出) |
readiness |
处置能力体检:能否在线修、能恢复到什么程度、能否主动扫描 |
list |
坏页清单:按时间判断是否需再次修复、反复损坏检测、跳页证据 |
triage |
逐页分诊:把坏页定位到具体对象,给出处置路径 |
backup |
备份可用性:账本里有什么、能恢复到哪一点、备份可不可信、调用方给的备份位置核对 |
capacity |
归档空间参数检查 |
工具返回分级结论(严重 / 警告 / 提示 / 未取到)、稳定 ID 的结论项与报告路径。同一个结论跨次运行 ID 不变,可直接做对比。
调用参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
action |
string | 必填 | list / readiness / triage / capacity / backup / report |
host |
string | — | 数据库服务器 IP 或主机名,支持 host:port 简写(如 10.0.0.61:7236)。不给则用插件配置里的默认目标 |
port |
integer | 5236 |
数据库端口(DMDSC 常见 7236 / 7237)。写了它即压过 host 里带的端口 |
user |
string | SYSDBA |
数据库账号 |
password |
string | — | 数据库口令。会写入会话日志,建议改用设置卡片或 credentialRef |
targets |
string | — | DMDSC 各节点,host:port 以逗号、空格或换行分隔。实测 V$DSC_EP_INFO 没有地址列,插件无法自动发现对端节点,必须由调用方给;不给也能用,但会明确告知「本库是 N 节点、只检查了 1 个」 |
outDir |
string | <插件工作目录>/dm-recovery-reports |
report 的报告目录,可用 ~ 开头 |
retentionHours |
integer | 24 |
capacity 的回溯窗口假设(小时)。这是业务决策:出事后需要能恢复到多久之前 |
backupPath |
string | — | backup:调用方自行提供的备份位置(备份集路径或备份名,ASM 路径以 + 开头同样可以)。插件拿它去 V$BACKUPSET 核对,并给出唯一能真正校验备份集的 dmrman 命令 |
sampleSec |
integer | — | 已废弃、不再使用,保留仅为兼容旧调用 |
sshHost |
string | — | 主机侧核验的数据库主机。不给则不做任何 OS 级检查,插件保持纯 JDBC 只读 |
sshPort |
integer | 22 |
SSH 端口 |
sshUser |
string | — | SSH 账号(通常 dmdba 或 root)。多节点用逗号分隔且与 targets 同序,只给一个值时所有节点共用 |
sshPassword |
string | — | SSH 口令。会写入会话日志,建议改用凭据库。多节点用逗号分隔且与 targets 同序;这一项不按空格拆分(口令里可以有空格) |
sshHostKey |
string | — | 主机密钥指纹(形如 ssh-ed25519 255 SHA256:...)。plink 在 -batch 模式下要求密钥已知,不给会在确认处中止 |
plinkPath |
string | plink |
plink 可执行文件路径 |
dmrmanPath |
string | dmrman |
主机上 dmrman 的路径,默认走 PATH |
dmrmanDcrIni |
string | — | 主机上 dmdcr.ini 的路径(DMDSC 上必需)。备份集在 ASM 上时不加它,CHECK BACKUPSET 会报「无效的备份集目录」—— 那不是备份坏了,是 dmrman 够不到 ASM |
sshLogDir |
string | — | 主机上 DM 日志目录。给了它插件才会跑日志扫描,取回「全库逐页裁决」 |
sshScriptPath |
string | <sshLogDir>/scan-backup-log.sh |
scan-backup-log.sh 在数据库主机上的路径。插件没有上传通道,需先手工拷过去 |
sshBackupDir |
string | — | 在主机文件系统上找备份文件的目录。只在备份路径不是 ASM 时才有意义(ASM 不是文件系统,find 本来就看不到) |
取值优先级:本次调用参数 > 插件配置。
配置
写在 profile 的 cordis.patch.yml 中该插件那一行上,也可以在设置界面里修改:
- insert:
- id: dm-recovery-readiness
name: 'dsh-plugin-dm-recovery-readiness'
config:
host: '10.0.0.61' # 默认目标;多节点用调用参数 targets
port: 7236
user: 'SYSDBA'
home: 'D:\dm-recovery-home' # 工作目录,默认 ~/.dsh/dm-recovery-readiness
credentialRef: 'DM_RECOVERY_PASSWORD'
driverJar: '' # 直接指定驱动 jar 时填
javaHome: '' # 需要 javac 编译桥时填
timeoutSec: 120
sshUser: 'dmdba' # 以下全部为空时不做任何主机侧命令
plinkPath: 'plink'
dmrmanPath: 'dmrman'
| 配置项 | 默认值 | 说明 |
|---|---|---|
home |
~/.dsh/dm-recovery-readiness |
工作目录,其下有 drivers/、java/、runtime/ |
host / port / user |
— / 5236 / SYSDBA |
默认目标与账号 |
password |
— | 数据库口令。会进入会话日志,不推荐写在配置里 |
credentialRef |
— | 数据库口令的凭据名;password 为空时才会去凭据库取 |
driverJar |
— | 直接指定 JDBC 驱动 jar 的路径 |
javaHome |
— | JDK 路径(需要 javac 编译桥时) |
timeoutSec |
120 |
单次采集超时(秒) |
sshHost / sshPort / sshUser / sshPassword / sshHostKey / plinkPath / dmrmanPath |
— | 主机侧核验的默认值。全部为空时插件保持纯 JDBC 只读,不执行任何主机命令 |
口令的另外两种来源
本项目的约定是每次运行在设置卡片里手输、用完即弃 —— 插件不保存、不写配置文件。要用凭据库则设置 credentialRef:
export DM_RECOVERY_PASSWORD='数据库口令'
或写入 ~/.dsh/.credentials.yaml 的 refs,键名与 credentialRef 一致。
取值顺序:调用参数 password > 配置里的 password > credentialRef 指向的凭据库。
报告
report 动作落两份同名不同后缀的文件:
dm-recovery-<库名>-<时间戳>.html ← 给人看:正文按恢复场景组织
dm-recovery-<库名>-<时间戳>.findings.json ← 给机器用:信封去掉正文
报告按「恢复场景」组织,而不是按「插件能查到什么」分章:
- 封面信息带:评估对象、数据库与实例、形态与版本、采集账号、生成时间、报告编号与总体等级色标
- 目录:封面之后、第 1 节之前。点条目跳到对应章节,每节末尾可跳回目录;打印时隐藏
- 评估概况:定义式面板,以及版本与实例信息(库与实例、服务版本、形态与节点、归档模式、归档链、未归档尾部、可回溯起点、备份账本、坏页记录、时间 ↔ LSN 精度、OS 级实测)
- 结论摘要:KPI、分布条、「等级 / 条数 / 判定含义」表、分级明细、场景能力分布
- 最要紧的几件事:按严重度排序,指向下方建议编号
- 该改的 N 件事:建议按要改的东西去重,每件事只说一次,并列出它影响哪些场景
- 逐场景条件对照:16 个场景 ×「要求 vs 本库实际」,建议只给指针
- 场景能力汇总、场景判定的实测依据
- OS 级实测:按本次实际跑到的条数连续编号
- 附录 A~F:处置能力体检、坏页清单、归档空间、备份可用性、部署与节点核对、输出词表
版式与同族的《达梦数据库安全评估报告》一致:深蓝封面信息带、编号小节徽章、定义式信息面板、结论看板;计数为 0 的档位照常显示、只是变淡;带 A4 打印样式,导出 PDF 时表格与卡片不被切断。
DMDSC 多节点时报告里会多出节点核对的内容;报告默认输出到插件工作目录下的 dm-recovery-reports,不是 dsh 进程的启动目录。
README 与插件市场里展示的报告截图(assets/screenshot-*.png)由 node tools/shots.mjs 从一份真实报告生成:先脱敏、再出图;脱敏后若还残留真实 IP、账号或内部文档引用,工具会拒绝出图。截图清单见 screenshots.json。
OS 访问(可选):三条只读命令
不给 sshHost 时插件是纯 JDBC 只读的,一次主机命令都不执行。给了之后,它会在数据库主机上执行三条固定形态的只读命令,把库内查不到的东西取回来:
| 命令 | 取回什么 |
|---|---|
dmrman CTLSTMT="CHECK BACKUPSET '<备份集>'" |
备份集到底能不能读。备份集在 ASM 上时需另给 dmrmanDcrIni,否则报「无效的备份集目录」—— 那不是备份坏了,是 dmrman 够不到 ASM;加上 DCR_INI 后同一条命令返回 check backupset successfully |
sh scan-backup-log.sh '<日志目录>' |
备份日志里的全库逐页裁决(N corrupted pages during backup)与实例日志里的页校验失败、实例中止事件 —— 这是 V$ERROR_PAGE 永远给不了的东西 |
find / ls |
文件系统上实际存在的备份文件。只在备份路径不是 ASM 时才有意义(ASM 不是文件系统,find 本来就看不到) |
边界写死在代码里,不靠自觉:
- 命令只能由
lib/ossh.js的三个构造函数产生,没有任何接受任意命令字符串的入口。 - 路径参数走
safeArg逐字符校验,允许的字符集故意很窄;引号、分号、管道、重定向、$、反引号、空格、换行、括号一律抛错。所以「把用户输入拼进命令」这条路在类型上就不存在。 - 三条命令全是只读,没有
RESTORE/RECOVER/ 任何写命令,也不进dmrman交互模式(一律用CTLSTMT=一次性执行)。
scan-backup-log.sh 要先手工拷到数据库主机(插件没有上传通道),路径由 sshScriptPath 指定,默认 <sshLogDir>/scan-backup-log.sh。
注意事项
- 所有库内 SQL 都是查询语句,主机侧只执行 3 类只读命令(
dmrman CHECK BACKUPSET、sh scan-backup-log.sh、find找备份文件),不修改数据库与主机配置。 - 不主动扫描坏页,这是配置语义决定的:达梦
PAGE_CHECK_POLICY=1下读到坏页会让实例 HALT。想发现未被碰到的坏页就得去读那些页,读到坏页库就 HALT —— 所以「扫描去发现坏页」在PAGE_CHECK_POLICY=1的环境上不该存在,本插件在实现层面就不提供。 - 不执行修复:
SF_PRIMARY_PAGE_REPAIR是拿备库的干净页覆盖主库坏页。备库不同步时,覆盖上去的可能是旧页 —— 把「一个坏页」换成「一个陈旧但校验通过」的页,更隐蔽。 V$ERROR_PAGE只记录被访问到的坏页,「未发现」不等于「没有坏页」。真机模拟实验(2026-10-13)实测:零化一页后读取,实例报page check error与SYSTEM SHUTDOWN ABORT,而V$ERROR_PAGE始终 0 行;全备照样成功、日志里写0 corrupted pages during backup。真正抓到它的是dmdbchk。- 对象定位依赖
DBA_EXTENTS。同一次实验里它在该页上返回 0 行(该页DBA_EXTENTS只报 1 个区 / 5 块,DBA_SEGMENTS是 197 区 / 3152 块),所以定位结果一律按「可能没定位到」看待。 - 备份历史来自
V$BACKUPSET,只反映当前控制文件记得的备份集。账本为空不等于没有备份 —— 这一态判「无法判定」,不判「没有备份」。 - 「时间点 → LSN」的映射只能借归档文件的
CREATE_TIME,精度等于一个归档文件的跨度(低负载库一个文件能横跨几十小时),因此不承诺精确到秒的 PITR。夹逼区间跨过判定线时结论是「无法判定」,不猜一个。 - 没有 RTO 判定:恢复耗时取决于磁盘吞吐、并行度、是否异机,库内查不出来。插件不会拿假设的吞吐量估一个「预计分钟数」。
- 主机侧核验需要先把
assets/shell/scan-backup-log.sh手工拷到数据库主机上;插件没有上传通道。 - 非交互地向 plink 传口令时,口令会出现在 plink 的进程参数里 —— 这是已知弱点。要规避就改用密钥登录(
ssh -i),那样插件配置里根本不需要 SSH 口令。 - 数据守护(主备)形态尚未在真机验证。
- 以下环境未验证:DM9、Windows 上的达梦、ARM 架构(鲲鹏 / 飞腾)。
- 报告包含主机名、账号、目录路径与实测发现,不要提交到公开仓库或公共渠道。
- 口令在调用卡片中填写,插件不保存、不落盘;宿主的会话日志会记录调用参数,这一点插件无法避免。
- 开发时注意:宿主加载的是 profile 目录下的安装副本(
$DSH_HOME/profiles/web/node_modules/dsh-plugin-dm-recovery-readiness),仓库源码改了不会自动生效。流程是先推送,再dsh plugin --profile web update dsh-plugin-dm-recovery-readiness更新副本,最后重启dsh web。这个 profile 虽然声明了patchReload: live,但那只覆盖补丁文件,插件代码没有热重载通道,原因见 DESIGN §48。 - Java 8 下首次运行会编译桥接器,产物在
home/runtime/classes;删除home/runtime/可清空,下次运行自动重建。
卸载
dsh plugin --profile web remove dsh-plugin-dm-recovery-readiness
再删除工作目录(其中包含驱动 jar):
| 平台 | 命令 |
|---|---|
| Windows | Remove-Item "$env:USERPROFILE\.dsh\dm-recovery-readiness" -Recurse -Force |
| Linux | rm -rf ~/.dsh/dm-recovery-readiness |
目录结构
├── package.json 插件清单
├── cordis.patch.yml 作为 bundle 的插入清单
├── screenshots.json 报告截图清单(插件市场详情页用)
├── lib/
│ ├── index.js 插件入口:注册 dm_recovery_readiness 工具、解析参数与口令
│ ├── bridge.js Node 与 Java 之间的只读桥
│ ├── queries.js 全部只读 SQL,跨版本差异都带降级链
│ ├── analyze.js 判定逻辑(纯函数,可离线单测)
│ ├── archive.js 归档空间参数检查
│ ├── backup.js 备份可用性:账本解析、覆盖度、归档链逐段核对、时间 ↔ LSN 夹逼
│ ├── dsc.js DMDSC 多节点:目标解析、节点一致性、覆盖度、坏页归属
│ ├── scenarios.js 恢复场景引擎:16 个场景判定、条件表、建议
│ ├── vocab.js 唯一词表:分级、场景能力、条件满足
│ ├── format.js 四层输出骨架
│ ├── result.js 结果信封与稳定 ID
│ ├── report.js HTML 报告渲染
│ └── ossh.js 可选的主机侧只读采集,命令只能由本模块的构造函数产生
├── assets/
│ ├── java/DmQuery.java 只读查询桥源码
│ ├── screenshot-*.png 报告截图,由 tools/shots.mjs 生成
│ └── shell/
│ ├── scan-backup-log.sh 日志扫描:备份证据与全库逐页裁决(插件会调用)
│ └── find-dm-logs.sh 日志发现(人工执行;插件当前不用它)
├── .github/workflows/
│ ├── ci.yml 离线自检(不连库、不装依赖)
│ └── peer-range.yml 每周核对 peer 范围是否落后于 registry(需联网)
└── tools/ 开发与自检脚本
开发
node tools/smoke.mjs # 离线自检:契约形状、安全边界、报告版式;不连库、不需要驱动(条数自己打印)
node tools/triage.mjs selftest # 判定逻辑自检(含时间比对、反复损坏检测)
node tools/peer-range.mjs # 核对 peer 范围是否还覆盖 registry 上已发布的版本(需联网)
node tools/check-backup.mjs # 连真实库跑一次 backup 动作
node tools/run-action.mjs # 任意 action 的真机运行器,支持 --out-dir、--dmrman-dcr-ini
node tools/live-check.mjs report # 连真实库打印信封本体与正文
node tools/live-smoke.mjs # 真机回归:六个 action × 信封不变量 × 交付物形状 × 口令不落盘
node tools/page-chain-check.mjs # 坏页链路真机半验证,18 项断言(注入页 × 真机数据)
node tools/shots.mjs # 从 reports/ 下的报告生成 assets/screenshot-*.png(本机需 Edge / Chrome)
真机脚本的口令只从环境变量读,不进命令行:DM_HOST、DM_USER、DM_PASSWORD,DMDSC 另给 DM_TARGETS;主机侧核验另给 SSH_HOST、SSH_USER、SSH_PASSWORD、SSH_HOST_KEY、DMRMAN_DCR_INI、SSH_LOG_DIR。live-smoke.mjs 的退出码:0 通过、1 断言失败、2 环境不可用。
改 peerDependencies 时留意 @deepseek-ai/* 的预发布版本:node-semver 只在范围里存在与该版本同元组、且自身带预发布标签的比较符时才放行,写得太宽或太窄都会静默漏掉版本,用户装的时候才会看到 peer 告警。范围里是一条条枚举(覆盖 0.1.0~0.1.7 每个元组),宿主发新元组就会静默失效 —— 所以改完跑一次 node tools/peer-range.mjs(它把 registry 上已发布的版本逐个比一遍);.github/workflows/peer-range.yml 每周也会自动跑一次。
No comments yet. Be the first to write one.