上个月凌晨两点,值班电话把我从床上拽起来:生产环境的DM主库进程没了,应用连接池瞬间被打满,报错短信刷了几百条。我打开监视器一看,备库状态还是“Normal”,数据也没断,但就是没人敢点那个“切换”按钮——因为之前从没在真实环境里验证过这套主备到底能不能自动顶上。等我们人工走完确认、切换、拉起应用的流程,时间已经过去了二十多分钟,业务投诉早就飞过来了。
那晚之后我做了个决定:不能等故障真来了才练,必须在测试环境里把DM数据库的实时主备故障模拟成固定科目,反复打、反复看切换过程到底靠不靠谱。这篇文章就是我从环境搭建、故障注入、切换观察到数据校验的完整记录。我用的是DM8一主一备一监视器的经典部署,把主库进程杀掉、断网隔离、整机宕机三类典型故障都模拟了一遍,还做了原主库回归后的回切测试。如果你也在维护达梦数据库,或者正准备给核心系统做高可用验证,这套演练思路可以直接抄作业。
先说清楚,不同版本、不同操作系统环境下的命令和参数名可能有细微差异,本文以DM8为基准,你操作前最好对着自己环境的版本手册核对一下。另外文中的IP、目录、实例名都是测试环境的示例,别直接往生产上套。
1. 先把主备切换的“决策机制”弄明白,否则演练就是瞎按
做故障模拟之前,我建议你先花点时间把DM数据守护系统的角色关系搞清楚。很多同学把主备搭起来、看到数据能同步就觉得万事大吉,真到切换的时候才发现整个决策链路上有几个关键节点,任何一个配置不对,自动切换都起不来。
1.1 主库、备库、守护进程、监视器,这四类角色谁说了算
一套正经的DM实时主备环境里,至少有四类角色在协同工作:
- 主库(Primary):对外提供读写服务的数据库实例,业务请求都打在它身上。
- 备库(Standby):实时接收主库的Redo日志并持续应用,正常情况下只读,不对外提供写入。
- 守护进程(dmwatcher):跑在主备库所在机器上的常驻进程,负责监控本机实例的健康状态,并向监视器上报心跳。
- 监视器(dmmonitor):整个集群的“决策中枢”,收集所有守护进程上报的状态,判断是否有实例故障,并在确认条件满足后自动发起切换。
打个比方,dmserver是被守护的对象,相当于运动员;dmwatcher是队医,跟着运动员实时摸脉搏;dmmonitor是总教练,看到运动员倒地了,喊替补上场。这里面最容易被人忽略的角色是守护进程,很多人只在主备库机器上装了dmserver,没装dmwatcher,结果监视器根本收不到状态,自动切换自然无从谈起。
1.2 监视器靠什么判断“主库真的挂了”
达梦的自动切换不是主库一没心跳就立刻切,而是要通过多重判定来避免误判。整个判断链路主要落在三个参数上:
- MAL_CONN_FAIL_INTERVAL:MAL系统连接故障判定时间,默认通常是10秒左右。如果超过这个时间还无法和某个节点建立MAL连接,就认为连接异常。
- MAL_INST_FAIL_INTERVAL:MAL实例故障判定时间,默认15秒左右。连接异常持续累计达到这个阈值,才判定对应实例不可用。
- DW_ERROR_TIME:守护进程的错误容忍时间,常见配置为30秒。守护进程发现本机实例异常后,会持续观察这么久,如果还不能恢复,才向监视器上报故障。
这三个时间叠加起来,基本决定了故障切换的“感知速度”。比如默认配置下,从主库真挂到监视器发起切换,往往要三十秒到一分钟量级。如果你的业务对RTO要求特别高,这几项就得根据实际硬件能力和业务容忍度谨慎调小,但也不能调得太激进,否则网络抖动就可能触发误切换。
这里还牵扯到一个容易被忽视的点:监视器分为确认监视器和普通监视器。确认监视器是集群里唯一有权发起自动切换的节点,它一挂,集群就失去自动决策能力;所以生产环境至少要有两个监视器,或者把确认监视器单独放在一台与主备库网络都连通、但又不承担数据库业务的机器上。我后面的演练就是按“确认监视器独立部署”的架构来做的。
1.3 一个很关键的认知:进程挂、断网、宕机,是三种完全不同的故障
为什么我要把故障分门别类去模拟?因为它们的表现形态完全不同:
- 主库进程崩溃:dmserver没了,但操作系统还活着,网卡还通。这时主库机器上的dmwatcher能立刻发现实例异常,监视器也还能和守护进程通信,整个链路的信息传递是畅通的。
- 主库网络被隔离:主库其实还活着,dmserver还在正常服务,但是和备库、监视器之间的链路断了。这时候主库侧和备库侧会形成“信息孤岛”,最容易出现误判或者脑裂风险。
- 整机宕机:进程和网络同时消失,故障最彻底,但也最“干净”,反而最不容易出现双主问题。
不同故障形态对切换策略的要求完全不同,这也是后面所有演练动作的设计基础。
2. 演练前环境搭建:最小可用的DM一主一备一监视器集群
这一节我尽量把搭建过程压缩着讲,重点放在影响后续故障模拟的配置项上。如果你已经有一套能正常同步的主备环境,可以跳过直接看第3章。
2.1 环境规划:三台机器、一张端口规划表
我用的测试环境是三台Linux虚拟机,网络互通,时间通过chrony做了同步。在实际操作中,监视器机器可以复用备库机器,但为了模拟“管理节点独立存活”的真实场景,我还是建议单独拆一台出来。
| 角色 | 实例名 | IP | 数据库端口 | MAL端口 | 守护端口 | 说明 |
|---|---|---|---|---|---|---|
| 主库 | GRP1_PRIMARY | 192.168.56.101 | 5236 | 5269 | 5270 | 对外提供读写 |
| 备库 | GRP1_STANDBY | 192.168.56.102 | 5236 | 5269 | 5270 | 实时同步 |
| 监视器 | - | 192.168.56.103 | - | - | - | 只装dmmonitor |
如果只是临时做演练,主备两台机器也够用。但我要强调一点:监视器不要和主库部署在同一台机器上,否则主库宕机时,你的“决策中枢”也跟着没了,自动切换一样起不来。这是我最早踩过的一个坑,后面会细说。
2.2 主库初始化与核心配置
如果你是从零开始,先完成DM数据库安装(这篇文章不展开安装步骤),然后用dminit初始化一个实例:
dminit PATH=/dm/data PAGE_SIZE=32 LOG_SIZE=2048 CHARSET=1 DB_NAME=DMDB INSTANCE_NAME=GRP1_PRIMARY PORT_NUM=5236接下来要做三件关键的事:
第一,修改dm.ini,打开MAL和归档开关:
MAL_INI = 1 ARCH_INI = 1 ALTER_MODE_STATUS = 0 ENABLE_OFFLINE_TS = 2ALTER_MODE_STATUS=0的意思是不允许通过命令随意修改数据库模式,避免误操作把主备搞乱。正式部署时这个值一定要设成0,演练结束时我也建议保持这个配置回切。
第二,配置dmarch.ini,同时做本地归档和实时归档:
[ARCHIVE_LOCAL1] ARCH_TYPE = LOCAL ARCH_DEST = /dm/arch ARCH_FILE_SIZE = 2048 ARCH_SPACE_LIMIT = 10240 ARCH_FLUSH_TIMING = 1 [ARCHIVE_REALTIME1] ARCH_TYPE = REALTIME ARCH_DEST = GRP1_STANDBY注意ARCH_DEST = GRP1_STANDBY这里填的是备库在dmmal.ini里定义的MAL实例名,不是随便写的。很多新手在这里填成备库IP或者备库实例名,导致实时归档始终起不来。
第三,配置dmmal.ini,主备库的内容要完全一致:
MAL_CHECK_INTERVAL = 5 MAL_CONN_FAIL_INTERVAL = 10 MAL_INST_FAIL_INTERVAL = 15 MAL_BUF_SIZE = 100 MAL_BUF_MAX_SIZE = 200 MAL_LOGIN_TIMEOUT = 15 [MAL_INST1] MAL_INST_NAME = GRP1_PRIMARY MAL_HOST = 192.168.56.101 MAL_PORT = 5269 MAL_INST_HOST = 192.168.56.101 MAL_INST_PORT = 5236 MAL_DW_PORT = 5270 [MAL_INST2] MAL_INST_NAME = GRP1_STANDBY MAL_HOST = 192.168.56.102 MAL_PORT = 5269 MAL_INST_HOST = 192.168.56.102 MAL_INST_PORT = 5236 MAL_DW_PORT = 52702.3 备库克隆:备份、RESTORE、RECOVER三步走
主库配置好后,要做一次全量备份,然后拿这份备份去搭建备库。我是直接在disql里执行的:
backup database full backupset '/dm/backup/full_bk';把主库的dm.ini、dmmal.ini、dmarch.ini以及这份备份集copy到备库机器后,用dmrman执行恢复:
dmrman CTLSTMT="RESTORE DATABASE '/dm/data/DMDB/dm.ini' FROM BACKUPSET '/dm/backup/full_bk'" dmrman CTLSTMT="RECOVER DATABASE '/dm/data/DMDB/dm.ini' UPDATE DB_MAGIC"这段命令里有个特别容易漏的细节:备库的dm.ini里INSTANCE_NAME一定要改成GRP1_STANDBY,同时ALTER_MODE_STATUS也要设成0。改完之后,主备库在mount状态下分别执行模式设置:
-- 主库执行 ALTER DATABASE PRIMARY; -- 备库执行 ALTER DATABASE STANDBY;然后配置dmwatcher.ini(主备库一致,INST_INI路径各自指向本机),再通过DM提供的脚本注册服务并启动:
DW_MODE = AUTO DW_ERROR_TIME = 30 INST_RECOVER_TIME = 60 INST_ERROR_TIME = 30 INST_OGUID = 453331 INST_INI = /dm/data/DMDB/dm.ini INST_AUTO_RESTART = 1 INST_STARTUP_CMD = /dm/bin/dmserver RLOG_APPLY_THRESHOLD = 0启动顺序有讲究:先主库,再备库,然后主备库的守护进程,最后启动监视器。全部起来后在监视器交互窗口执行show health,看到主备状态都是“Normal”,说明集群已经就绪。这时候我习惯导入一份测试用的dmp数据到库里面,备库上应声可见——顺便说一句,达梦导入dmp文件用dimp工具就能完成,命令路径通常是/dm/bin/dimp,演练前备好一份有代表性的数据,后面做数据一致性校验会方便很多。
3. 演练一:直接杀掉主库dmserver进程,观察自动切换能否“无人驾驶”
这个场景模拟的是最典型的故障:进程崩溃,操作系统还活着。这是成本最低、最容易复现的演练方式,也是我建议所有人第一次做故障模拟时先打的科目。
3.1 注入故障前,先把“现场证据”记录下来
任何一场正经的故障演练,都不能上来就杀进程。先把当时的集群快照记录清楚,后面做数据比对才有依据。我在disql里执行的是这几个查询:
-- 查看主库当前日志信息 SELECT INSTANCE_NAME, STATUS$, MODE$ FROM V$INSTANCE; -- 查看主备库LSN位置 SELECT FILE_LSN, CUR_LSN FROM V$RLOG; -- 查看归档同步情况 SELECT ARCH_DEST, ARCH_LSN, ARCH_STATE FROM V$ARCH_STATUS;同时记录下监视器窗口当前的show health输出,保持两个终端的日志滚动。接下来才真正注入故障:
# 在主库机器上执行,模拟主库进程被外部强制杀掉 kill -9 $(pidof dmserver)这里我刻意用kill -9而不是systemctl stop,目的是模拟最极端的突发崩溃。kill -9之后,dmserver没有机会做任何优雅退出动作,Redo日志就停留在崩溃那一刻,这对备库的日志追平能力是一个真实的考验。
3.2 故障注入后的时间线:从kill到业务恢复
kill执行后,我紧盯着监视器窗口和业务模拟端的日志。整个自动切换过程大概是这个节奏:
| 时间点 | 事件 |
|---|---|
| 0秒 | 执行kill -9,主库dmserver进程消失 |
| 约8秒 | 备库侧守护进程检测到与主库MAL通信异常 |
| 约18秒 | 监视器日志出现实例故障判定信息 |
| 约35秒 | 确认监视器判定主库不可用,开始自动切换 |
| 约40秒 | 原备库被提升为新主库,开始对外服务 |
这个时间线是在我上面的参数配置下得到的。我把业务模拟端的Java应用连接串指向守护环境的VIP,切换完成后新连接自动落到新主库上,应用侧无感知,只有少数几个正在执行的长事务被断开重连。
切换完成后,在监视器窗口执行show database命令,可以看到原备库的角色已经变成了Primary,原主库显示为故障状态。这个过程中最让我紧张的是日志追平环节——主库崩溃前可能还有一部分Redo日志没来得及传给备库,备库要接管成为新主库,必须先从本地归档把日志补齐到至少和主库崩溃点一致的位置。好在实时归档模式下达梦基本能做到“零丢失”,这也是实时主备和异步主备最大的区别。
3.3 切换逻辑复盘:为什么备库要等“日志追平”才肯上场
自动切换不是“备库投票通过就立刻顶上”,而是有一个重要的前提条件:备库必须确认自己拿到了主库崩溃前的全部日志。如果备库还差一段日志就强行接管,那部分数据就丢了。
那么怎么判断“追平”了?答案是看归档序列号和LSN。备库的V$ARCH_STATUS里,ARCH_DEST对应主库的归档源,ARCH_LSN如果能追到和主库崩溃时一致的LSN,说明已经具备接管条件。RLOG_APPLY_THRESHOLD用来控制备库与主库日志差异的容忍阈值,默认0表示不允许有差异,必须严格追平才能接管。如果你的业务可以容忍少量丢失以换取更快的切换速度,这个参数可以适当调大,但绝大多数核心系统不建议调,因为“丢数据”这个代价远比“多等十几秒”严重。
这个环节还暴露了一个容易被忽略的问题:备库机器磁盘IO能力如果太差,日志应用速度跟不上主库的写入速度,平时看不出问题,一旦发生故障切换,备库要花很长时间才能追平日志,RTO就被无限拉长了。所以做实时主备,备库的硬件规格不能比主库低太多,尤其是磁盘和网络,这是我从几次演练里总结出来的硬道理。
4. 演练二:模拟网络隔离与整机宕机,把“脑裂”和“回切”一次讲透
主库进程被杀,只是故障模拟的入门科目。真正让人头疼的是网络层面的隔离,因为这种故障下主库还活着,它并不知道自己已经被“孤立”了。
4.1 用iptables制造“主库单边失联”,看看监视器会不会出错
我当时的操作是在主库机器上模拟“入站连接全部丢弃”,相当于把主库的网络对外一刀切:
# 在主库机器上执行,把入站ICMP和数据库相关端口全部丢弃 iptables -A INPUT -s 192.168.56.102 -j DROP iptables -A INPUT -s 192.168.56.103 -j DROP执行完这条命令后,主库的dmserver还在跑,本地连接还能正常查询,但对备库和监视器来说,主库已经“失联”了。这个场景比kill -9更危险,因为主库侧并没有感知到故障,它还在继续接收业务写入。
在我这套带独立确认监视器的架构里,监视器在持续一段时间收不到主库守护进程的心跳后,会判定主库故障,然后和备库侧的守护进程完成确认,把备库提升为新主库。整个切换过程大约在一分钟以内完成,原主库由于网络被隔离,并不知道自己已经被“下课”,但它也接不到业务流量了,所以并没有出现两个主库同时对外提供服务的情况。
反过来,如果监视器本身没有独立部署,或者MAL判定时间配置得过短,网络抖动就可能触发“脑裂”——两边都认为自己才是主库,两边都在接受写入。这种事故的恢复成本非常高,轻则数据不一致,重则整个集群都要重建。所以关于脑裂的防范,我的建议是三句话:确认监视器务必独立部署;网络参数的调整要小步慢跑,不要一次调得过于激进;故障判定参数需要结合真实网络环境做记录和复盘,而不是拍脑袋定一个值。
4.2 整机宕机模拟:直接把虚拟机断电
断电的模拟更简单,直接在虚拟化平台上对主库执行强制关机,或者如果条件允许,直接poweroff。这种故障形态下,主库进程、守护进程、网络全部消失,信息孤岛效应反而不存在,监视器和备库很容易就达成一致,完成切换。
我用这个方式验证的是另一个问题:原主库重新开机后,能不能自己乖乖回到集群里。整机宕机恢复后,原主库的dmwatcher会自动拉起dmserver,它会发现集群里已经有了新的主库,然后自动进入“备库重建”的流程。这里有个细节:如果你用的版本支持自动重建,原主库会自动从新主库拉取基线备份和归档日志,重建自己的数据文件;如果不支持,就需要手工把原主库重新RESTORE一遍,把它变成新主库的备库。
我在演练中发现,自动重建虽然方便,但耗时取决于数据量和网络带宽。数据量大的时候,建议在业务低峰期操作,同时监控新主库的归档空间,防止被重建请求把磁盘打满。
4.3 回切:让原主库重新拿回“话语权”
演练的最后一环,是把集群状态恢复到初始状态,也就是让原主库重新变回主库。这个操作叫回切,也常被称为switchover。回切的前提是:原主库已经作为备库成功追平了新主库的日志,并且集群整体处于健康状态。
在监视器交互窗口执行切换命令:
switchover GRP1_PRIMARY;执行完这条命令后,监视器会协调两边:原主库先从备库角色切换为主库,原备库(也就是当前主库)再从主库切换为备库。整个过程是平滑的,两端的数据会在切换前做最后一次日志追平,然后完成角色互换。
回切之后,务必再做一遍数据校验:新主库上跑的测试表数据量、主备两边的归档序列、关键表的checksum是否一致。我校验的方式是:
-- 在主备分别执行,对比结果 SELECT COUNT(*), SUM(CAST(MD5(COL_A||COL_B) AS VARBINARY)) FROM TEST_TABLE;两边结果完全一致,才说明整个故障模拟和回切流程没有丢数。
5. 演练中的翻车记录与参数调整心得
再完美的演练方案,实操起来也免不了踩坑。这一节我专门记下我重复遇到过的几个典型问题,每一个都是真金白银换来的教训。
5.1 我记得最深的五个坑
坑一:备库忘记改INSTANCE_NAME。复制主库配置文件到备库时,dm.ini里还写着GRP1_PRIMARY,启动守护进程时两边实例名重复,监视器里看到两个“主库”,日志刷屏。这个错误很低级,但特别容易在熬夜操作时犯。建议备库的配置文件在copy完成后,第一步就改INSTANCE_NAME,不要等后面启动失败了才想起来。
坑二:系统时间没同步。DM主备同步对时间很敏感,两台机器时间偏差超过几十毫秒,同步延迟的监控曲线就会异常。第一次演练时我用了虚拟机默认时间,主备时间差了整整两分钟,Redo日志传输永久落后,看得我差点排查到天亮。现在我把chrony同步做成了环境初始化的固定步骤。
坑三:归档目录空间规划不足。故障演练和自动重建需要消耗大量归档空间,如果ARCH_SPACE_LIMIT设置过小,备库在追日志时直接报“归档空间不足”,被迫中断恢复。这个参数要按照“能容纳至少两轮全量备份体积的归档日志”来规划,不要撑得刚刚好。
坑四:监视器放到了主库机器上。第一次做架构设计时,图省事把确认监视器装在了主库机器上,觉得“反正平时不用它”。结果模拟主库宕机时,监视器也跟着不可用,自动切换根本没触发,被我手动用takeover命令接管才完成。这个教训让我彻底改了架构——监视器必须独立。
坑五:切换后没有及时更新应用连接串。用的是VIP才能规避这个坑。如果应用连接串直接指向实例IP,主备切换后应用怎么都连不上库。生产环境建议用守护进程的VIP或者服务名来做连接入口,而不是裸IP。
5.2 把故障演练变成定期机制,而不是一次性的“表演”
一次演练成功不等于系统永远可靠。我现在的做法是每季度做一轮完整的主备故障模拟,并且每次都换一种故障注入方式:第一季度杀进程,第二季度断网,第三季度整机强制关机,第四季度做磁盘满、归档中断这类“慢病型”故障。每一次演练都要形成记录,包括故障注入时间、切换完成时间、数据校验结果、出现的问题和处理动作。
我还做了一张简版检查清单,每次演练前逐项打勾:
| 检查项 | 操作 | 预期 |
|---|---|---|
| 主备状态 | show health | 双Normal |
| 归档连通性 | V$ARCH_STATUS | ARCH_STATE正常 |
| 时间同步 | chronyc tracking | 偏差小于10ms |
| 磁盘空间 | df -h | 剩余空间充足 |
| 应用连接入口 | 确认VIP/服务名 | 指向守护环境 |
| 数据基线 | 记录LSN/归档序列 | 事后可比对 |
这套机制运行了三个季度之后,我再也没在深夜值班电话里手忙脚乱过。不是因为它能预测故障,而是因为每一次切换动作都已经被反复验证过,信息是确定的,预案是走通的。
最后再分享一个小技巧:演练过程中监视器窗口的日志,我都会原样保存到文件里,命名带上日期和故障类型。一段时间之后再翻,能清楚看到每次优化参数后切换时间的变化曲线,这对调优来说价值极大。如果你刚准备做DM主备故障演练,不妨也从这个习惯开始。