前段时间我在一套达梦数据库集群上做了一次“在线剔除异步备库”的实验。简单说,就是把一台仍然在运行、仍然承担同步任务、但已经不需要继续留在集群里的异步备库节点,在不重启主库、不中断主库业务的前提下安全摘掉。很多人听到“在线剔除”第一反应是:直接把备库停掉不就行了?实际操作下来真没这么简单,如果顺序错了、命令先后反了,备库会被守护进程分分钟自动拉起来,甚至可能把集群状态搞乱,吓得人一身冷汗。
这篇文章就把整个实验过程完整复盘一遍,从“为什么需要在线剔除异步备库”到环境准备、操作步骤、配置文件清理、踩坑记录全部写出来。如果你正在用达梦数据库,做过主备集群,或者马上要处理类似“机房节点回收”“备库报废”“一个多余的异步备库要下线”这类事情,这篇内容应该能帮你少走不少弯路。我这里用的是达梦8(DM8)环境,配置方式和命令在DM7上略有差异,但只要理解了思路,换版本也不难套用。
1. 先弄清楚:为什么要单选“异步备库”剔除
1.1 异步备库在集群里到底是个什么角色
达梦数据库集群的主备架构里,主库负责业务读写,备库负责实时或者准实时拿日志做冗余。通常大家说的“同步备库”,主要是实时归档模式,主库事务提交前日志至少要送到备库侧,主备之间数据一致性非常强,主库故障时数据不丢。而“异步备库”就不一样,它允许主备之间有一定延迟,备库可以定时去拿主库的归档日志,或者主库发送日志后不强制等待备库确认,主库该提交提交、该返回返回,哪怕备库网络抖动都不会拖累主库性能。
正因为它“不拖累主库”,很多团队会把异步备库放在跨机房、边缘机房或者一些性能一般的老机器上,用来做异地容灾、报表数据源、数据演练副本这类要求不高的场景。但运行时间长了问题就来了:
- 异步备库硬件老旧,磁盘、内存已经跟不上新版本的集群要求。
- 该节点所在机房要网络整改或上收,这台机器要么报废,要么挪作他用。
- 节点已经长期失联或同步延迟越来越大,监控里一直告警,与其留着还不如彻底踢出去。
- 集群里实际保留的备库数量已经足够,冗余度过高,继续维护纯粹是负担。
这时候就出现了一个问题:怎么把备库摘掉?粗暴做法是什么?有人直接在备库上把服务停了,有人干脆把备库网线拔了。但达梦的高可用集群不是这么玩的,后面我会详细讲为什么这些做法会踩坑。
1.2 在线剔除的真正难点在哪里
我一开始也以为“在线剔除备库”就是把备库相关进程停掉、配置文件删一删就完事。真动手做实验才发现,难点有两层。
第一层是达梦的守护机制。达梦主备集群中,每个节点上都有数据库实例 dmserver,同时还有一个守护进程 dmwatcher 在看着它,核心监控进程则是确认监视器 dmmonitor。如果备库的 dmserver 异常退出,dmwatcher 会在很短的时间内自动把它重新拉起来,甚至尝试重新加入集群。所以你要是直接 kill 备库实例,不出十几秒它自己又启动并恢复 standby 状态了,根除不了。
第二层是配置链路上的关系。主库、备库之间通过 dmmal.ini 里的 MAL 条目通信,主库的 dmarch.ini 里还保留着向这个备库发送日志的归档配置,监视器一侧也记录了这个节点的信息。光把备库停了没有用,主库侧还在继续往这个节点发送日志,只是发不出去,时间一长本地待发送日志越堆越多,最终受影响的反而是主库。
所以真正干净利落的剔除,必须分两个层面来做:先把节点从集群的“运行态”里摘掉,让守护进程不再管它、监视器不再盯着它;再处理配置文件,让这个节点从“配置态”里也消失。这两个层面拆开想通了,后面的操作就不会乱。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 实验环境与开始之前的准备工作
2.1 实验集群的基本构成
我这边的实验环境比较简单,一主一备,外加一个独立部署的确认监视器。主备库的同步方式配置成了异步归档,也就是说备库是一台典型的异步备库。
| 角色 | 主机IP | 实例名 | 实例服务 | 数据库模式 | OGUID |
|---|---|---|---|---|---|
| 主库 | 192.168.10.11 | GRP1_01 | DmServiceGRP1_01 | PRIMARY | 45331 |
| 待剔除异步备库 | 192.168.10.12 | GRP1_02 | DmServiceGRP1_02 | STANDBY | 45331 |
| 确认监视器 | 192.168.10.10 | dmmonitor(独立进程) | - | - | - |
OS 是麒麟V10,达梦版本是 DM8,补丁相对较新。主备库的安装路径默认在 /opt/dmdbms,数据目录为 /opt/dmdbms/data,守护进程和数据库实例都配置成了系统服务,方便用 systemctl 管理。如果你那边是手工方式启动的,操作逻辑一样,但进程管理命令要换成直接 ps 找进程再 kill。
2.2 剔除前必须做的健康检查
不管是什么集群变更,我习惯先把现状摸一遍再动手,避免改完出了问题连“改之前是什么样”都说不清楚。这次剔除异步备库之前,我做的事情主要有这几项:
- 在确认监视器里执行
show database,查看主备两个节点的状态。 - 执行
show health看守护进程报不报错。 - 查看备库已经追到哪个归档日志,确认同步延迟不高,或者说心里有数它落后了多少。
- 检查待剔除备库上有没有还在跑的应用任务、备份脚本、监控采集程序。
- 把当前 dmwatcher.ini、dmmal.ini、dmarch.ini、dm.ini 等关键配置文件全部备份一次。
进入监视器的方式通常是这样的:
bash复制cd /opt/dmdbms/bin
./dmmonitor /opt/dmdbms/data/dmmonitor.ini
进入后先看一轮状态:
text复制show database
show health
我这边显示的是主库 GRP1_01 状态 OPEN,GRP1_02 状态 OPEN 但也正常同步,没有报警。同步延迟这一项要单独看,异步备库本身就允许落后,但因为实验前已经确保没有大量写入,落得并不多。
这里建议大家一定检查“有没有业务连着这个备库”。我的实验里还专门查了一遍应用白名单和调度平台配置,确认这台备库没有被报表任务或跑批任务引用。异步备库通常也是只读库,如果还有人在上面跑查询,你把库一停,应用就立刻报错。见过太多因为“以为没业务实际上有定时任务”导致凌晨故障的案例,这一步真不能省。
为了验证整个剔除过程不中断主库业务,我在主库上建了一张心跳表,用一个循环脚本持续插入数据,每两秒一条,实验结束后数总行数就能知道有没有断点:
sql复制CREATE TABLE KEEPALIVE_CHECK(
ID NUMBER,
NOTE VARCHAR2(100),
CREATE_TIME TIMESTAMP DEFAULT SYSDATE
);
INSERT INTO KEEPALIVE_CHECK(ID, NOTE) VALUES(1, 'before');
COMMIT;
主库持续写入的手法是这套实验结论可信的关键。如果没有这层验证,你很难说自己到底是不是“在线”完成的剔除。
3. 在线剔除的完整操作步骤
3.1 第一步:在监视器里把目标备库“临时请出组”
这一步是整个实验里最容易忽略、但也最重要的一步。很多人直接去停备库的数据库实例,结果发现被守护进程反复拉起,原因就是没提前让监控层放弃对这个节点的管理。
我登录确认监视器后,先执行了 exclude 操作,把 GRP1_02 从当前的监视守护范围内剔除:
text复制exclude database GRP1_02
show database
执行后从监视器视角看,GRP1_02 已经不再作为正常集群成员参与心跳检测和状态收集了。这时候它的状态在我的监视器输出里会变成类似“无效”“不在组内”的标识,具体文案不同版本略有差异,但核心点就是监视器已经不管它了。
为什么这一步要在停止备库实例之前做?因为如果你先把备库实例停了,监视器和守护进程会判定这个节点异常,然后自动触发拉起或重连流程。虽然异步备库一般不会触发主库切换,但整个集群的心跳、守护日志会出现大量异常报错,甚至会影响主库侧对自身健康状态的判断。先让监视器“看不见”这个节点,后面的停库操作才不会引发连锁反应。
需要注意,exclude 命令的准确拼写在你使用的版本里可能会有细微差别,实操前可以先在监视器里敲 help 查看命令帮助。如果这个实验做完还要恢复备库,对应的 include 命令可以把节点加回来,所以不用太担心做错。
3.2 第二步:停掉备库的守护进程,防止自动拉起
监视器已经不管这个节点,但备库本机上的守护进程还在,它会继续守护本机的 dmserver。如果我现在直接停备库的 dmserver,dmwatcher 依然会尝试把它拉起来。所以下一步要先把备库的 dmwatcher 停掉。
我这边的系统服务名是 DmWatcherServiceGRP1_02,执行:
bash复制systemctl stop DmWatcherServiceGRP1_02
systemctl status DmWatcherServiceGRP1_02
确认守护进程已经退出后,再用 ps 检查一下:
bash复制ps -ef | grep dmwatcher
我执行完这条命令后,输出里已经看不到 GRP1_02 对应的 dmwatcher 进程了。如果是手工部署没有注册成服务的环境,就需要先找到进程号再 kill,操作前看清楚进程路径,别杀错主库的守护进程。
顺序为什么必须是“先停 watcher,再停 dmserver”?你可以把 dmwatcher 理解成健身房里盯着学员做动作的教练,学员一偷懒教练就会吼他继续练。你直接让学员停下来,教练马上会干预,让他重新站起来。想让学员真正休息,得先把教练请走。守护进程就是那个教练,不先把它停掉,后面的所有操作都会被打断。
3.3 第三步:把备库实例切回普通模式并停掉
守护进程退出后,备库的 dmserver 还在运行,而且此时它的数据库模式大概率还是 STANDBY。如果不改变模式就直接把实例停掉,下次如果误启动了服务,它可能还会尝试以备库身份连接主库,或者因为找不到守护配置而启动异常。
所以这一阶段要登录备库实例,把它从 STANDBY 模式切成普通模式。我用 disql 连接备库:
bash复制/opt/dmdbms/bin/disql SYSDBA/SYSDBA_PASSWORD@192.168.10.12:5237
登录后的切换命令如下:
sql复制ALTER DATABASE MOUNT;
ALTER DATABASE NORMAL;
第一次执行 ALTER DATABASE MOUNT 时,如果备库当前处于 OPEN 状态,命令会正常完成;但如果库本身不是 OPEN 状态,可能提示当前状态不允许操作。遇到这种情况,第一步就换成先确认当前状态,不要反复执行同一句命令。进入 MOUNT 状态后,执行 ALTER DATABASE NORMAL,把实例的守护模式改成普通模式。
模式切换完成后,数据库状态会在 MOUNT 下,这时可以直接把实例服务停掉:
bash复制systemctl stop DmServiceGRP1_02
ps -ef | grep dmserver
确认进程退干净。到了这一步,备库已经从运行状态上彻底退出了整个集群。
这里想多说一句:ALTER DATABASE NORMAL 这个操作不会删除数据文件,不会清除归档,也不会把数据目录弄坏。它只是把数据库角色从“备库”变成“普通库”。也就是说,这台机器后面如果想当成一个独立的普通数据库来用,数据都还在,启动实例后它甚至可以作为一个单独的单机库对外服务。只不过它停止在主库故障前的某一时间点,业务要用之前一定要知道数据可能不是最新的。
3.4 第四步:配置文件清理与两阶段取舍
运行态的剔除做完以后,集群已经可以正常工作了,主库不受影响。但此时的集群配置在“配置文件层面”还是留有这个备库的痕迹,主要体现在几个文件里:
- 目标备库本机的 dmwatcher.ini、dmmal.ini、dmarch.ini。
- 主库和监视器侧配置中与该备库相关的条目。
先说目标备库本机的配置。既然这台机器已经决定退出集群,我会把它本机的 dmwatcher.ini 改名留档,再把 dmarch.ini 里带有归档发送和集群同步意义的配置段清理掉,保留一份本地归档配置即可。如果这台机器以后彻底不再参与达梦集群了,甚至可以连 MAL_INI 参数一起在 dm.ini 里关掉,但这个改动需要重启实例才生效,实际操作中要确认这台机器是否还要做单机使用。
真正要谨慎的是主库侧配置的清理。如果确定这台备库永久下线,那主库的 dmmal.ini 里对应的节点条目、主库 dmarch.ini 里指向这台备库的归档配置也应该删掉。但这两个文件是数据库启动时加载的,不重启实例不会重新读取。在线状态下重启主库实例显然违背了“在线”的原则,所以我把这套处理拆成了两阶段。
第一阶段就是前面几步,先把节点从运行态摘除,让主库业务无感,这一步当前已经完成。第二阶段才是彻底清理配置,需要申请一个业务维护窗口,重启一次主库实例让主库侧配置干净。如果只是临时把一个不想要的备库踢出去,后面还要加回来,我会建议第一阶段做完就停手,保留主库侧配置不动,这样以后加节点省事很多。
我在实验里实际验证了第一阶段结束后,主库业务依然正常。KEEPALIVE_CHECK 表里的插入没有中断,主库节点没有出现任何切换或异常状态。持久化的配置文件我是在后续维护窗口里才清理的,这样既不影响“在线剔除”的实验目标,也不会因为改 dmmal.ini 引入主库重启风险。
3.5 第五步:验证剔除结果和业务影响
剔除实验做完后,验证环节我分了三块并行做。
第一块是验证主库业务。回主库查询 KEEPALIVE_CHECK 表,看插入时间戳是否连续。我这边整个操作加验证大约花了二十分钟,心跳表里数据插入无断点,说明主库业务全程没受影响,这才是“在线”的真正证明。
第二块是验证集群状态。回到确认监视器,再次执行 show database 和 show health,输出里已经看不到 GRP1_02 这个节点了,至少不再把它作为存活备库显示,告警也没有了。主库 GRP1_01 依然健康,状态为 OPEN,守护进程连接正常。
第三块是验证数据同步边界。由于备库已经被摘出,理论上主库新增的数据不会再自动同步到这台机器上。我特意在主库上再建了一张表并插入几行数据,然后去原备库的数据目录里查这张表,结果确实查不到。这说明复制链路已经断开,备库停留在了剔除操作那个时间点的数据状态上。
如果你的场景要求“剔除后这台备库要挪作其他用途”,还需要额外做一遍库的完整性检查,因为异步备库本身就是有时间点落后状态的。比较稳妥的做法是拿它还完全正常时做过的一次全备作为基础,或者从主库拉一份最新的备份过来在新节点上恢复,才能得到一个干净可用的数据副本。我把这一步理解为“削完苹果要把果核也清理掉”,运行态剔除只是削皮,配置和数据边界确认才是收尾。
4. 几个容易踩的坑与排查速查
4.1 坑一:没先排除节点就停实例,备库被守护进程强拉
我第一次做这个实验时,直接登到备库机器上执行了 systemctl stop DmServiceGRP1_02,结果没等操作提示完全结束,进程又自动起来了。当时我第一个念头是“坏了,命令没生效”,后来 ps 一看,进程确实是从旧 PID 变成新 PID 重新启动的,典型的守护进程自动拉起行为。
排查过程很简单,先查看 dmwatcher 进程:
bash复制ps -ef | grep dmwatcher
发现 dmwatcher 还在,而且日志里有大量的“DB RESTART”类记录。解决办法就是回到监视器先把这个节点 exclude 掉,再停 dmwatcher,最后才停 dmserver。这个顺序问题如果反复踩,很容易把备库搞成一直重启但又起不来的状态,反而更难看。
4.2 坑二:模式切换不先 MOUNT,直接 NORMAL 被拒绝
还有一个常见问题是在执行 ALTER DATABASE NORMAL 时,报“当前状态不允许执行该操作”。达梦的模式切换是有状态要求的,从 STANDBY 模式转成 NORMAL 前,一定要先把数据库状态切到 MOUNT。有人图省事,直接在 OPEN 状态下执行 ALTER DATABASE NORMAL,库会直接拒绝。
这里也提醒一下,模式切换命令要以 SYSDBA 身份登录,普通 DBA 账号没有权限。而且实际数据库如果处于“备库还连在复制链路上”的状态,即使命令能执行也可能产生诡异行为。最稳的做法是先把 watcher 停掉,通过 disql 连接备库,执行:
sql复制ALTER DATABASE MOUNT;
ALTER DATABASE NORMAL;
执行完最好再重启一次备库实例,确认它以普通模式正常起来。我实验时第一次切换成功后没有重启实例,直接停了服务,后面要复用时发现实例以旧参数启动又尝试走 standby 路径,折腾了一下。所以如果需要这台机器继续以普通单机库使用,别怕麻烦,执行完模式切换后重启一次实例。
4.3 坑三:只改配置不重启,或者反过来改完就重启
配置清理阶段最容易出现两种极端。一种是改完 dmmal.ini、dmarch.ini 之后不重启任何东西,结果配置文件永远不生效,节点其实还残留在主库的归档发送列表里;另一种是主库侧的配置一改,马上就重启主库实例,结果业务中断,把“在线剔除”变成了“离线变更”。
我的经验是先把“改配置”和“重启进程”两者拆开考虑。目标节点已经摘除后,主库侧配置文件即使不改,业务也不会受影响,最多是日志发送目录里会保留一些发不出去的状态。真正需要改主库配置并重启的,只有永久彻底下线这一个目的。对于只是“移除一个活跃备库”的操作,第一阶段的运行时剔除已经足够。别为了追求配置文件完美而牺牲业务连续性。
4.4 常见问题速查
| 现象 | 最可能原因 | 处理方法 |
|---|---|---|
| 停掉备库实例后很快又自动启动 | 守护进程 dmwatcher 还在运行,自动拉起实例 | 先在监视器 exclude 节点,再停 dmwatcher,最后停 dmserver |
| 执行 ALTER DATABASE NORMAL 报错 | 数据库当前状态不是 MOUNT | 先执行 ALTER DATABASE MOUNT,再切换 NORMAL |
| 监视器一直显示备库异常、反复告警 | 没有提前把节点从监视守护范围剔除 | 在监视器执行 exclude 操作后再停库 |
| 主库目录下待发送日志不断增长 | 主库 dmarch.ini 仍配置向该备库发送日志 | 确认该节点已下线后,在维护窗口清理主库归档配置 |
| 切换普通模式后启动实例仍然尝试备库角色 | 模式切换后没有重启,或 dm.ini 中相关参数未同步 | 重启实例,检查 dm.ini 中的守护相关配置 |
| 剔除节点后原备库不能作为单机库查询 | 备库数据停留在剔除前时间点,不是实时数据 | 明确剔除时间点,必要时从主库重新备份恢复 |
这张表是我整理给自己看的,后来做类似操作时基本就按这个套路排查。核心思路就是:先搞清是哪个层出了问题,运行态、守护态还是配置态,三个层面各自单独排查,基本不会有大问题。
5. 这次实验后我留下的几个实操习惯
整个实验做完,我心里最深刻的一条体会是:达梦集群的在线变更,永远要把“运行时操作”和“配置文件操作”当成两件事。监视器里执行 exclude、停守护进程、切普通模式、停实例,这一串动作解决的是“让它立刻不干活”的问题;改 dmmal.ini、dmarch.ini、dmwatcher.ini,解决的是“以后重启也不希望它回来”的问题。这两件事可以分开做,不必绑定,知道什么时候该做哪件事,操作风险就能降一大半。
另外两个习惯也很值得分享。第一,做任何剔除、下线、踢节点操作前,所有的配置文件先备份一遍,哪怕只是 cp xxx.ini xxx.ini.bak_20250112,一旦后续发现需要回滚,省下的是重新回忆和配置的时间。第二,在线变更一定要配一个持续心跳,不管是业务表插入也好,还是一个简单的 job 轮询也好,没有它你永远无法证明业务真的无感。实验中的 KEEPALIVE_CHECK 表让我后来写变更报告的时候非常从容,每一分钟数据都在,不需要靠“感觉没断”来交差。
这个实验方案其实还能扩展。比如把“先运行时摘除、再维护窗口清理配置”的思路用在备库替换场景:先加一个新的异步备库,等追平主库后,再按这套流程把旧备库删掉,整个过程对业务几乎不可见。如果你正在规划达梦集群节点搬迁、机房收缩,或者单纯只是想清理一个冗余备库,完全可以拿这套流程去练手,先在测试环境跑一遍,再上生产就稳了。
