从单机到两节点:达梦 DM9 9.1.0.26 读写分离集群搭建实录
摘要: 这次我在两台 Rocky Linux 8.10 上从裸机搭了一套 DM9 9.1.0.26 读写分离集群,154 做主库 GRP1_RWW_01、155 做备库 GRP1_RWW_02,走的是数据守护(TIMELY 即时归档)+ 客户端服务名读写分离的路子。
一、两台机器和两个实例怎么摆
| 项目 | 154(主) | 155(备) |
|---|---|---|
| 主机名 | shanjiatst5.shanjia.com | shanjiatst6.sjtest.com |
| IP | 192.168.40.154 | 192.168.40.155 |
| OS | Rocky Linux 8.10 | Rocky Linux 8.10 |
| 内核 | 4.18.0-553.el8_10.x86_64 | 4.18.0-553.el8_10.x86_64 |
| CPU / 内存 | 4 核 / 7.5Gi | 8 核 / 7.5Gi |
| 磁盘 | 60G,可用 49G | 120G,可用 111G |
| 初始时区 | CST | EDT(要改) |
软件版本两节点一致:安装介质 dm9_20260514_x86_centos7_64.zip(986M,md5 f9548c926d64389885ee8600f9f52ecc),DM9 9.1.0.26,安装目录 /data/dameng9。
端口规划是这样分的,所有端口在搭建前就定死,后面配置文件里逐项对应:
| 用途 | 154(GRP1_RWW_01) | 155(GRP1_RWW_02) |
|---|---|---|
| 实例端口 PORT_NUM | 5236 | 5236 |
| MAL 通信端口 | 61141 | 61142 |
| 守护进程监听 MAL_DW_PORT | 52141 | 52142 |
| 实例本地守护 MAL_INST_DW_PORT | 33141 | 33142 |
| 组名 / OGUID | GRP1 / 453332 | GRP1 / 453332 |
一台机上一套 DM9 软件,两侧实例都叫 DM9DB,实例名不同,这样 dmmal.ini 里的 INST_NAME 才能对上。
二、备机的准备:时区、传包、装软件
155 这台机时钟是 EDT,跟 154 差 12 小时。集群里主备 LSN 和归档时间戳都要对齐,时区不一致后面排查日志会很痛苦,我先把两边统一:
# timedatectl | head -5
# timedatectl set-timezone Asia/Shanghai
# date
Local time: Tue 2026-09-22 11:11:17 EDT
Universal time: Tue 2026-09-22 15:11:17 UTC
Time zone: America/New_York (EDT, -0400)
Tue Sep 22 23:44:27 CST 2026
SELinux、firewalld、dmdba 用户、limits、sysctl 这些按附录 R1 的模板在两节点各做一遍。集群要多开 61141/61142、52141/52142、33141/33142 这些端口,我这次直接停了 firewalld;生产环境要保留的话,按上面的端口表逐条 firewall-cmd 放行即可。
装介质要从 154 传到 155。我先试了 scp,命令跑完没有任何输出,155 上目录是空的;换成 cat | ssh 管道一次就过去了,986M 用了 8.8 秒,md5 两边一致:
# cat /data/soft/dm9_20260514_x86_centos7_64.zip | ssh root@192.168.40.155 'cat > /data/soft/dm9_20260514_x86_centos7_64.zip'
# ssh root@192.168.40.155 'md5sum /data/soft/dm9_20260514_x86_centos7_64.zip'
f9548c926d64389885ee8600f9f52ecc /data/soft/dm9_20260514_x86_centos7_64.zip
scp 那条失败的原因我在后面第七节写清楚了,是 ssh 把脚本正文当输入读走了,不是网络问题。
155 上装软件和 154 完全同款,静默配置文件原样复制过去,记得给伪终端:
# unzip -q /data/soft/dm9_20260514_x86_centos7_64.zip -d /tmp/dm9pkg
# mount -o loop,ro /tmp/dm9pkg/dm9_20260514_x86_centos7_64.iso /mnt/dm9iso
# cat > /tmp/dm9_silent.xml <<'XML'
<?xml version="1.0" encoding="UTF-8"?>
<DATABASE>
<LANGUAGE>zh_CN</LANGUAGE>
<KEY></KEY>
<COMPONENTS>1,2</COMPONENTS>
<INSTALL_PATH>/data/dameng9</INSTALL_PATH>
</DATABASE>
XML
# script -qec "/mnt/dm9iso/DMInstall.bin -s /tmp/dm9_silent.xml" /dev/null
欢迎使用 DM9 安装工具
-- 安装小结
产品名称: 达梦数据库V9
安装目录: /data/dameng9
安装内容: 服务器,客户端工具
-- 安装中
[*] 正在初始化安装日志...
[✓] 初始化安装日志完成
[*] 正在拷贝文件...
[✓] 拷贝文件完成
[*] 正在修改文件...
[✓] 修改文件完成
[*] 正在修改环境变量...
[✓] 修改环境变量完成
[*] 正在安装服务...
[✓] 安装服务完成
[*] 正在配置...
[✓] 配置完成
-- 安装总结
达梦数据库DM9安装完成
三、主库数据准备:先建库,再脱机备份
集群的数据准备有个讲究,主库必须先有一个 Normal 模式的库,正常启动过一次,然后脱机备份给备库还原。官方手册里也明确说了这一步要用脱机备份,不能用联机备份,否则备份完 LSN 还在涨,还原出来的备库和改成 PRIMARY 的主库对不上。
154 上建库,字符集、页大小这些参数和备库必须一致:
# mkdir -p /data/dameng9/data /data/dameng9/data/DM9DB/arch
# chown -R dmdba:dinstall /data/dameng9/data
$ dminit PATH=/data/dameng9/data DB_NAME=DM9DB INSTANCE_NAME=GRP1_RWW_01 PORT_NUM=5236 \
SYSDBA_PWD='Dameng@123' SYSAUDITOR_PWD='Dameng@123' CHARSET=1 PAGE_SIZE=32 \
EXTENT_SIZE=32 LOG_SIZE=2048 CASE_SENSITIVE=Y BLANK_PAD_MODE=0
log file path: /data/dameng9/data/DM9DB/DM9DB01.log
log file path: /data/dameng9/data/DM9DB/DM9DB02.log
write to dir [/data/dameng9/data/DM9DB].
create dm database success. 2026-09-23 01:00:17
新库必须先以 Normal 模式启动一次并正常退出,不然后面 mount 会失败。我起了实例,查到实例名,再正常关掉:
$ nohup dmserver /data/dameng9/data/DM9DB/dm.ini > /tmp/dm9_first_start.log 2>&1 &
$ disql SYSDBA/"Dameng@123"@localhost:5236 -e 'select instance_name from v$instance;'
$ disql SYSDBA/"Dameng@123"@localhost:5236 -e 'shutdown immediate;'
INSTANCE_NAME
-------------
GRP1_RWW_01
dmserver 已停止
然后脱机备份:
$ dmrman CTLSTMT="BACKUP DATABASE '/data/dameng9/data/DM9DB/dm.ini' FULL TO BACKUP_FILE1 BACKUPSET '/data/dameng9/data/BAK_FULL_01'"
file dm.key not found, use default license!
dmrman V9
BACKUP DATABASE '/data/dameng9/data/DM9DB/dm.ini' FULL TO BACKUP_FILE1 BACKUPSET '/data/dameng9/data/BAK_FULL_01'
Processing backupset /data/dameng9/data/BAK_FULL_01
[Percent:100.00%][Speed:6500.00M/s][Cost:00:00:02][Remaining:00:00:00]
备份集 30M,用 tar 管道送到 155,注意 ssh 那两处都加了 < /dev/null:
# tar cf - -C /data/dameng9/data BAK_FULL_01 | ssh root@192.168.40.155 'cd /data/dameng9/data && tar xf -'
# ssh root@192.168.40.155 'ls -lh /data/dameng9/data/BAK_FULL_01/; chown -R dmdba:dinstall /data/dameng9/data'
四、备库还原
155 上先用 dminit 初始化一个空库做骨架,实例名是 GRP1_RWW_02,再用主库的备份集覆盖还原:
$ dminit PATH=/data/dameng9/data DB_NAME=DM9DB INSTANCE_NAME=GRP1_RWW_02 PORT_NUM=5236 \
SYSDBA_PWD='Dameng@123' SYSAUDITOR_PWD='Dameng@123' CHARSET=1 PAGE_SIZE=32 \
EXTENT_SIZE=32 LOG_SIZE=2048 CASE_SENSITIVE=Y BLANK_PAD_MODE=0
$ dmrman CTLSTMT="RESTORE DATABASE '/data/dameng9/data/DM9DB/dm.ini' FROM BACKUPSET '/data/dameng9/data/BAK_FULL_01'"
$ dmrman CTLSTMT="RECOVER DATABASE '/data/dameng9/data/DM9DB/dm.ini' UPDATE DB_MAGIC'"
file dm.key not found, use default license!
dmrman V9
RESTORE DATABASE '/data/dameng9/data/DM9DB/dm.ini' FROM BACKUPSET '/data/dameng9/data/BAK_FULL_01'
Processing backupset /data/dameng9/data/BAK_FULL_01
[Percent:100.00%][Speed:0.00M/s][Cost:00:00:03][Remaining:00:00:00]
backup DBF to /data/dameng9/data/DM9DB/MAIN.DBF
recover successfully!
脱机备份没有 redo 要追,所以只做 UPDATE DB_MAGIC 这一步,备份集里没有归档的话不必跑 RECOVER ... FROM BACKUPSET。
五、四份配置文件
主库 154 的 dm.ini 我按守护集群的要求改了七个参数,MAL_INI 和 ARCH_INI 打开是让实例去读 dmmal.ini 和 dmarch.ini:
# sed -i 's|^[[:space:]]*INSTANCE_NAME[[:space:]]*=.*|\t\tINSTANCE_NAME = GRP1_RWW_01|' /data/dameng9/data/DM9DB/dm.ini
# sed -i 's|^[[:space:]]*PORT_NUM[[:space:]]*=.*|\t\tPORT_NUM = 5236|' /data/dameng9/data/DM9DB/dm.ini
# sed -i 's|^[[:space:]]*DW_INACTIVE_INTERVAL[[:space:]]*=.*|\t\tDW_INACTIVE_INTERVAL = 60|' /data/dameng9/data/DM9DB/dm.ini
# sed -i 's|^[[:space:]]*ALTER_MODE_STATUS[[:space:]]*=.*|\t\tALTER_MODE_STATUS = 0|' /data/dameng9/data/DM9DB/dm.ini
# sed -i 's|^[[:space:]]*ENABLE_OFFLINE_TS[[:space:]]*=.*|\t\tENABLE_OFFLINE_TS = 2|' /data/dameng9/data/DM9DB/dm.ini
# sed -i 's|^[[:space:]]*MAL_INI[[:space:]]*=.*|\t\tMAL_INI = 1|' /data/dameng9/data/DM9DB/dm.ini
# sed -i 's|^[[:space:]]*ARCH_INI[[:space:]]*=.*|\t\tARCH_INI = 1|' /data/dameng9/data/DM9DB/dm.ini
# sed -i 's|^[[:space:]]*RLOG_SEND_APPLY_MON[[:space:]]*=.*|\t\tRLOG_SEND_APPLY_MON = 64|' /data/dameng9/data/DM9DB/dm.ini
# grep -E '^[[:space:]]*(INSTANCE_NAME|PORT_NUM|DW_INACTIVE_INTERVAL|ALTER_MODE_STATUS|ENABLE_OFFLINE_TS|MAL_INI|ARCH_INI|RLOG_SEND_APPLY_MON)[[:space:]]*=' /data/dameng9/data/DM9DB/dm.ini
INSTANCE_NAME = GRP1_RWW_01
PORT_NUM = 5236
DW_INACTIVE_INTERVAL = 60
ALTER_MODE_STATUS = 0
ENABLE_OFFLINE_TS = 2
MAL_INI = 1
ARCH_INI = 1
RLOG_SEND_APPLY_MON = 64
dmmal.ini 两个节点内容完全一样,写两份一模一样的拷过去:
# cat > /data/dameng9/data/DM9DB/dmmal.ini <<'EOF'
MAL_CHECK_INTERVAL = 5
MAL_CONN_FAIL_INTERVAL = 5
[MAL_INST1]
MAL_INST_NAME = GRP1_RWW_01
MAL_HOST = 192.168.40.154
MAL_PORT = 61141
MAL_INST_HOST = 192.168.40.154
MAL_INST_PORT = 5236
MAL_DW_PORT = 52141
MAL_INST_DW_PORT = 33141
[MAL_INST2]
MAL_INST_NAME = GRP1_RWW_02
MAL_HOST = 192.168.40.155
MAL_PORT = 61142
MAL_INST_HOST = 192.168.40.155
MAL_INST_PORT = 5236
MAL_DW_PORT = 52142
MAL_INST_DW_PORT = 33142
EOF
dmarch.ini 两侧只有 ARCH_DEST 不同。读写分离集群要求归档类型是 TIMELY(即时归档),主库指备库,备库指主库:
# 154 主库
# cat > /data/dameng9/data/DM9DB/dmarch.ini <<'EOF'
[ARCHIVE_TIMELY1]
ARCH_TYPE = TIMELY
ARCH_DEST = GRP1_RWW_02
[ARCHIVE_LOCAL1]
ARCH_TYPE = LOCAL
ARCH_DEST = /data/dameng9/data/DM9DB/arch
ARCH_FILE_SIZE = 128
ARCH_SPACE_LIMIT = 0
EOF
# 155 备库,只有 ARCH_DEST 换成 GRP1_RWW_01
dmwatcher.ini 两个节点相同,INST_AUTO_RESTART=1 让守护进程在实例挂掉时自己拉起来:
# cat > /data/dameng9/data/DM9DB/dmwatcher.ini <<'EOF'
[GRP1]
DW_TYPE = GLOBAL
DW_MODE = AUTO
DW_ERROR_TIME = 10
INST_RECOVER_TIME = 60
INST_ERROR_TIME = 10
INST_OGUID = 453332
INST_INI = /data/dameng9/data/DM9DB/dm.ini
INST_AUTO_RESTART = 1
INST_STARTUP_CMD = /data/dameng9/bin/dmserver
RLOG_SEND_THRESHOLD = 0
RLOG_APPLY_THRESHOLD = 0
EOF
监视器我放在 155,MON_DW_CONFIRM=1 是确认监视器,负责故障判定和切换裁决:
# cat > /data/dameng9/data/DM9DB/dmmonitor.ini <<'EOF'
MON_DW_CONFIRM = 1
MON_LOG_PATH = /data/dameng9/log
MON_LOG_INTERVAL = 60
MON_LOG_FILE_SIZE = 32
MON_LOG_SPACE_LIMIT = 0
[GRP1]
MON_INST_OGUID = 453332
MON_DW_IP = 192.168.40.154:52141
MON_DW_IP = 192.168.40.155:52142
EOF
六、启动顺序:mount、OGUID、模式、守护、监视器
顺序不能乱。先以 mount 启动,设 OGUID,再把主库改成 PRIMARY、备库改成 STANDBY,最后才起守护进程,让守护进程去把库 open 起来。
主库 154:
$ nohup dmserver /data/dameng9/data/DM9DB/dm.ini mount > /tmp/dm9_mount_primary.log 2>&1 &
$ disql SYSDBA/"Dameng@123"@localhost:5236 -e "SP_SET_PARA_VALUE(1, 'ALTER_MODE_STATUS', 1);"
$ disql SYSDBA/"Dameng@123"@localhost:5236 -e 'sp_set_oguid(453332);'
$ disql SYSDBA/"Dameng@123"@localhost:5236 -e "SP_SET_PARA_VALUE(1, 'ALTER_MODE_STATUS', 0);"
$ disql SYSDBA/"Dameng@123"@localhost:5236 -e "SP_SET_PARA_VALUE(1, 'ALTER_MODE_STATUS', 1);"
$ disql SYSDBA/"Dameng@123"@localhost:5236 -e 'ALTER DATABASE PRIMARY;'
$ disql SYSDBA/"Dameng@123"@localhost:5236 -e "SP_SET_PARA_VALUE(1, 'ALTER_MODE_STATUS', 0);"
$ disql SYSDBA/"Dameng@123"@localhost:5236 -e "select instance_name,mode\$,status\$ from v\$instance;"
INSTANCE_NAME MODE$ STATUS$
------------- ------- -------
GRP1_RWW_01 PRIMARY MOUNT
备库 155 同样流程,最后一句换成 ALTER DATABASE STANDBY:
INSTANCE_NAME MODE$ STATUS$
------------- ------- -------
GRP1_RWW_02 STANDBY MOUNT
两侧都 mount 起来之后再起守护进程,它会把实例拉到 OPEN:
# 154
$ nohup dmwatcher /data/dameng9/data/DM9DB/dmwatcher.ini > /tmp/dmwatcher_154.log 2>&1 &
# 155
$ nohup dmwatcher /data/dameng9/data/DM9DB/dmwatcher.ini > /tmp/dmwatcher_155.log 2>&1 &
2026-09-23 01:19:44 dmwatcher P0000115207 守护进程状态(STARTUP)
守护进程状态(OPEN) 实例名(GRP1_RWW_01) 模式(PRIMARY) 实例状态(OPEN) 归档状态(VALID) POCNT(3) FLSN(39450) CLSN(39450) SLSN(39450) SSLSN(39450)
155 上再起确认监视器:
$ nohup dmmonitor /data/dameng9/data/DM9DB/dmmonitor.ini > /tmp/dmmonitor_155.log 2>&1 &
集群起来之后看一眼全局信息,这是我判断集群健康的第一个依据:
$ tail -25 /data/dameng9/log/dmmonitor_*.log
GROUP OGUID MON_CONFIRM MODE MPP_FLAG
GRP1 453332 TRUE AUTO FALSE
<<DATABASE GLOBAL INFO:>>
DW_IP MAL_DW_PORT WTIME WTYPE WCTLSTAT WSTATUS INAME INST_OK N_EP N_OK ISTATUS IMODE DSC_STATUS RTYPE RSTAT DETACHED
192.168.40.154 52141 2026-09-23 01:08:37 GLOBAL VALID OPEN GRP1_RWW_01 OK 1 1 OPEN PRIMARY DSC_OPEN TIMELY VALID FALSE OFF
EP INFO:
INST_IP INST_PORT INST_OK INAME ISTATUS IMODE DSC_SEQNO RTYPE RSTAT FSEQ FLSN CSEQ CLSN DW_STAT_FLAG
192.168.40.154 5236 OK GRP1_RWW_01 OPEN PRIMARY 0 TIMELY VALID 3485 54504 3485 54504 NONE
<<DATABASE GLOBAL INFO:>>
DW_IP MAL_DW_PORT WTIME WTYPE WCTLSTAT WSTATUS INAME INST_OK N_EP N_OK ISTATUS IMODE DSC_STATUS RTYPE RSTAT
192.168.40.155 52142 2026-09-23 01:08:37 GLOBAL VALID OPEN GRP1_RWW_02 OK 1 1 OPEN STANDBY DSC_OPEN TIMELY VALID
DATABASE(GRP1_RWW_02) APPLY INFO FROM (GRP1_RWW_01), REDOS_PARALLEL_NUM (1), WAIT_APPLY(TRUE):
DSC_SEQNO[0], (RSEQ, SSEQ, KSEQ)[3485, 3485, 3485], (RLSN, SLSN, KLSN)[54504, 54504, 54504], N_TSK[0], TSK_MEM_USE[0]
两边都 OPEN、RTYPE 是 TIMELY、RSTAT 是 VALID,备库的重演 LSN 与主库 FLSN 一致(54504),这套集群就算立住了。
七、验证:同步、只读、分流、自愈
主库写入 501 行,备库查到同样的行数:
$ disql SYSDBA/"Dameng@123"@192.168.40.154:5236 -e 'select count(*) as cnt from t_dw;'
$ disql SYSDBA/"Dameng@123"@192.168.40.155:5236 -e 'select count(*) as cnt from t_dw;'
### PRIMARY ###
CNT
--------------------
503
### STANDBY ###
CNT
--------------------
503
备库写入被拒,这是负向检查,必须报错:
$ disql SYSDBA/"Dameng@123"@192.168.40.155:5236 -e "insert into t_dw(id,name) values(9999,'from_standby');"
[-710]:试图在STANDBY模式下,修改用户库.
服务名配置放在 /etc/dm_svc.conf,两端都写一份:
# cat > /etc/dm_svc.conf <<'EOF'
# DM9 读写分离集群服务名
TIME_ZONE=(480)
LANGUAGE=(cn)
dmrw=(192.168.40.154:5236,192.168.40.155:5236)
RW_SEPARATE=(1)
RW_PERCENT=(0)
SWITCH_TIMES=(3)
SWITCH_INTERVAL=(1000)
EOF
通过服务名写,落在主库;把 RW_PERCENT 设成 0 之后读,落在备库:
$ disql SYSDBA/"Dameng@123"@dmrw -e "insert into t_dw(id,name) values(7777,'via_svc_write');"
$ disql SYSDBA/"Dameng@123"@dmrw -e 'commit;'
$ disql SYSDBA/"Dameng@123"@dmrw -e 'select instance_name,mode$ from v$instance;'
服务器[192.168.40.154:5236]:处于主库打开状态
第 1 次 -> GRP1_RWW_02
第 2 次 -> GRP1_RWW_02
第 3 次 -> GRP1_RWW_02
第 4 次 -> GRP1_RWW_02
第 5 次 -> GRP1_RWW_02
新写入的行在备库能查到,说明同步链路是通的:
$ disql SYSDBA/"Dameng@123"@192.168.40.155:5236 -e "select id,name from t_dw where id=7777;"
ID NAME
----------- -------------
7777 via_svc_write
最后做一次故障实验。我把主库的 dmserver 进程 kill -9,看守护和监视器怎么处理:
# PID=$(pgrep -f 'dmserver /data/dameng9/data/DM9DB/dm.ini')
# kill -9 $PID
2026-09-23 01:22:10 kill 前
--- kill 后 5 秒 ---
dmserver 已消失
5236 已无监听
第 10 秒:dmserver 已被拉起
守护进程 10 秒把实例拉起来了。但因为 DW_MODE=AUTO 配了确认监视器,监视器判定主库失效后做了接管,日志里能看到角色互换:
[GRP1_RWW_01] 2026-09-23 01:22:33.348 [INFO] 远程实例的模式、状态或者归档状态发生变化,新状态是:
[GRP1_RWW_01] 2026-09-23 01:22:33.398 [INFO] Instance: 守护进程状态(OPEN) 实例状态(OK) 实例名(GRP1_RWW_02) 模式(PRIMARY) 实例状态(OPEN) 归档状态(INVALID) POCNT(3) FLSN(54504) CLSN(54504) SLSN(54504) SSLSN(54504)
切换之后 155 成了 PRIMARY、154 成了 STANDBY,两边 LSN 仍然一致。我再用服务名测了一遍,写落在 155、读落在 154,读写分离照样工作:
服务器[192.168.40.155:5236]:处于主库打开状态
第 1 次 -> GRP1_RWW_01
第 2 次 -> GRP1_RWW_01
第 3 次 -> GRP1_RWW_01
这一点我如实写:本环境里 kill 主库之后集群角色发生了互换,我尝试在监视器里用 switchover 切回 154 没有成功(dmmonitor 的交互命令没能通过管道输入被接收),所以角色就停在了 155 为主的状态。集群本身是健康的,数据一致,读写分流正常。
八、服务化:把守护和监视器交给 systemd
手工 nohup 起的进程重启机器就没了,我按安装目录里的模板给两节点各注册了服务。154 只注册守护,155 注册守护和监视器:
# cp /data/dameng9/bin/service_template/DmWatcherService /data/dameng9/bin/DmWatcherServiceGRP1
# sed -i 's|%INI_PATH%|/data/dameng9/data/DM9DB/dmwatcher.ini|g; s|%PROVIDES%||g' /data/dameng9/bin/DmWatcherServiceGRP1
# chmod +x /data/dameng9/bin/DmWatcherServiceGRP1
# chown dmdba:dinstall /data/dameng9/bin/DmWatcherServiceGRP1
# cat > /usr/lib/systemd/system/DmWatcherServiceGRP1.service <<'EOF'
[Unit]
Description=DM Data Watch Service(DmWatcherServiceGRP1)
After=network-online.target remote-fs.target nss-lookup.target
Wants=network-online.target
[Service]
Type=forking
PIDFile=/data/dameng9/bin/pids/DmWatcherServiceGRP1.pid
ExecStart="/data/dameng9/bin/DmWatcherServiceGRP1" start
ExecStop="/data/dameng9/bin/DmWatcherServiceGRP1" stop
PrivateTmp=true
User=dmdba
TasksMax=infinity
LimitCORE=infinity
LimitNOFILE=100000
LimitNPROC=100000
[Install]
WantedBy=multi-user.target
EOF
# systemctl daemon-reload
# systemctl enable --now DmWatcherServiceGRP1
● DmWatcherServiceGRP1.service - DM Data Watch Service(DmWatcherServiceGRP1)
Loaded: loaded (/usr/lib/systemd/system/DmWatcherServiceGRP1.service; enabled; vendor preset: disabled)
Active: active (running) since Wed 2026-09-23 01:19:59 CST; 20s ago
Main PID: 115207 (dmwatcher)
CGroup: /system.slice/DmWatcherServiceGRP1.service
└─115207 /data/dameng9/bin/dmwatcher path=/data/dameng9/data/DM9DB/dmwatcher.ini -noconsole
155 上多一个监视器服务,模板用 DmMonitorService:
# cp /data/dameng9/bin/service_template/DmMonitorService /data/dameng9/bin/DmMonitorServiceGRP1
# sed -i 's|%INI_PATH%|/data/dameng9/data/DM9DB/dmmonitor.ini|g; s|%PROVIDES%||g' /data/dameng9/bin/DmMonitorServiceGRP1
# systemctl enable --now DmMonitorServiceGRP1
● DmMonitorServiceGRP1.service - DM Data Monitor Service(DmMonitorServiceGRP1)
Active: active (running) since Wed 2026-09-23 01:21:30 CST; 15s ago
Main PID: 69145 (dmmonitor)
CGroup: /system.slice/DmMonitorServiceGRP1.service
└─69145 /data/dameng9/bin/dmmonitor path=/data/dameng9/data/DM9DB/dmmonitor.ini
十、验收
| 检查项 | 命令 | 预期 |
|---|---|---|
| 集群全局状态 | tail -25 /data/dameng9/log/dmmonitor_*.log | GRP1 / OGUID 453332 / MON_CONFIRM TRUE / MODE AUTO |
| 两实例状态 | 监视器日志 EP INFO | 一 PRIMARY 一 STANDBY,均为 OPEN、RSTAT VALID、RTYPE TIMELY |
| 主备同步 | 两侧 select count(*) from t_dw; | 行数一致(本例 503) |
| 备库只读 | 备库执行 insert | [-710]:试图在STANDBY模式下,修改用户库. |
| 读写分离 | 服务名连接后 select instance_name from v$instance; | RW_PERCENT=0 时全部落在备库 |
| 守护自愈 | kill -9 <dmserver pid> | 10 秒内实例被拉起 |
| 服务托管 | systemctl status DmWatcherServiceGRP1 | active (running) |
终态:两节点守护进程由 systemd 托管并开机自启,155 上运行确认监视器,主备 LSN 对齐,服务名 dmrw 能把写路由到主库、把读路由到备库。
附录:完整安装配置复现手册(Runbook)
R0 前置条件
| 项目 | 要求 |
|---|---|
| 节点 | 2 台,192.168.40.154(主)、192.168.40.155(备) |
| OS | Rocky Linux 8.10,x86_64,内核 4.18.0-553.el8_10,时区统一 Asia/Shanghai |
| 资源 | 主 4 核 / 7.5G / 可用 49G;备 8 核 / 7.5G / 可用 111G |
| 介质 | dm9_20260514_x86_centos7_64.zip,986M,md5 f9548c926d64389885ee8600f9f52ecc,两节点各一份 |
| 版本 | 达梦 DM9 9.1.0.26 |
| 网络 | 5236、61141/61142、52141/52142、33141/33142 互通;两节点 root SSH 互通 |
| 全局约定 | 安装目录 /data/dameng9;用户 dmdba:dinstall;实例 DM9DB;实例名 GRP1_RWW_01 / GRP1_RWW_02;OGUID 453332;组名 GRP1;口令 Dameng@123 |
| 必读 | 主库必须先 Normal 启动一次再脱机备份;启动顺序 mount → OGUID → 模式 → 守护 → 监视器 |
R1 系统准备公共模板(两节点都执行)
# timedatectl set-timezone Asia/Shanghai
# cp -a /etc/selinux/config /etc/selinux/config.bak.$(date +%Y%m%d%H%M%S)
# setenforce 0
# sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config
# systemctl stop firewalld
# systemctl disable firewalld
# groupadd dinstall
# useradd -g dinstall -m -d /home/dmdba -s /bin/bash dmdba
# echo 'Dameng@123' | passwd --stdin dmdba
# mkdir -p /data/dameng9
# chown -R dmdba:dinstall /data/dameng9
# chmod -R 755 /data/dameng9
# cat >> /etc/security/limits.conf <<'EOF'
# ---- DM9 ----
dmdba soft nproc 65536
dmdba hard nproc 65536
dmdba soft nofile 65536
dmdba hard nofile 65536
dmdba soft stack 10240
dmdba hard stack 32768
dmdba soft core unlimited
dmdba hard core unlimited
EOF
# cat > /etc/sysctl.d/99-dameng.conf <<'EOF'
fs.file-max = 6815744
fs.aio-max-nr = 1048576
kernel.shmmax = 68719476736
kernel.shmall = 4294967296
kernel.shmmni = 4096
kernel.sem = 250 32000 100 128
net.ipv4.ip_local_port_range = 9000 65500
net.core.rmem_default = 262144
net.core.rmem_max = 4194304
net.core.wmem_default = 262144
net.core.wmem_max = 4194304
vm.overcommit_memory = 0
vm.swappiness = 1
EOF
# sysctl -p /etc/sysctl.d/99-dameng.conf
# echo 'RemoveIPC=no' >> /etc/systemd/logind.conf
# systemctl restart systemd-logind
# dnf install -y libaio libaio-devel ncurses ncurses-devel readline readline-devel \
unixODBC unixODBC-devel ksh numactl numactl-devel glibc glibc-devel libstdc++ libstdc++-devel
判定点:timedatectl 显示 CST;id dmdba 返回 uid=1001(dmdba) gid=1001(dinstall);su - dmdba -c 'ulimit -n' 返回 65536。
R2 两节点安装 DM9 软件
主节点本地安装,备节点先传介质:
# 传介质(主节点执行,ssh 一律加 </dev/null)
# ssh root@192.168.40.155 'mkdir -p /data/soft' < /dev/null
# cat /data/soft/dm9_20260514_x86_centos7_64.zip | ssh root@192.168.40.155 'cat > /data/soft/dm9_20260514_x86_centos7_64.zip'
# ssh root@192.168.40.155 'md5sum /data/soft/dm9_20260514_x86_centos7_64.zip' < /dev/null
两节点各执行(备节点先 unzip -q /data/soft/... -d /tmp/dm9pkg):
# mkdir -p /tmp/dm9pkg /mnt/dm9iso
# mount -o loop,ro /tmp/dm9pkg/dm9_20260514_x86_centos7_64.iso /mnt/dm9iso
# cat > /tmp/dm9_silent.xml <<'XML'
<?xml version="1.0" encoding="UTF-8"?>
<DATABASE>
<LANGUAGE>zh_CN</LANGUAGE>
<KEY></KEY>
<COMPONENTS>1,2</COMPONENTS>
<INSTALL_PATH>/data/dameng9</INSTALL_PATH>
</DATABASE>
XML
# script -qec "/mnt/dm9iso/DMInstall.bin -s /tmp/dm9_silent.xml" /dev/null
判定点:输出 达梦数据库DM9安装完成;ls -ld /data/dameng9 属主 dmdba:dinstall。
R3 主库建库、首次启动、脱机备份
# mkdir -p /data/dameng9/data /data/dameng9/data/DM9DB/arch
# chown -R dmdba:dinstall /data/dameng9/data
$ dminit PATH=/data/dameng9/data DB_NAME=DM9DB INSTANCE_NAME=GRP1_RWW_01 PORT_NUM=5236 \
SYSDBA_PWD='Dameng@123' SYSAUDITOR_PWD='Dameng@123' CHARSET=1 PAGE_SIZE=32 \
EXTENT_SIZE=32 LOG_SIZE=2048 CASE_SENSITIVE=Y BLANK_PAD_MODE=0
$ nohup dmserver /data/dameng9/data/DM9DB/dm.ini > /tmp/dm9_first_start.log 2>&1 &
$ disql SYSDBA/"Dameng@123"@localhost:5236 -e 'select instance_name from v$instance;'
$ disql SYSDBA/"Dameng@123"@localhost:5236 -e 'shutdown immediate;'
$ dmrman CTLSTMT="BACKUP DATABASE '/data/dameng9/data/DM9DB/dm.ini' FULL TO BACKUP_FILE1 BACKUPSET '/data/dameng9/data/BAK_FULL_01'"
# tar cf - -C /data/dameng9/data BAK_FULL_01 | ssh root@192.168.40.155 'cd /data/dameng9/data && tar xf -'
# ssh root@192.168.40.155 'chown -R dmdba:dinstall /data/dameng9/data' < /dev/null
判定点:dminit 输出 create dm database success;首次启动能查到 GRP1_RWW_01;备份集 /data/dameng9/data/BAK_FULL_01 生成且已传到备机。
R4 备库还原
$ dminit PATH=/data/dameng9/data DB_NAME=DM9DB INSTANCE_NAME=GRP1_RWW_02 PORT_NUM=5236 \
SYSDBA_PWD='Dameng@123' SYSAUDITOR_PWD='Dameng@123' CHARSET=1 PAGE_SIZE=32 \
EXTENT_SIZE=32 LOG_SIZE=2048 CASE_SENSITIVE=Y BLANK_PAD_MODE=0
$ dmrman CTLSTMT="RESTORE DATABASE '/data/dameng9/data/DM9DB/dm.ini' FROM BACKUPSET '/data/dameng9/data/BAK_FULL_01'"
$ dmrman CTLSTMT="RECOVER DATABASE '/data/dameng9/data/DM9DB/dm.ini' UPDATE DB_MAGIC'"
判定点:RESTORE 到 100%,RECOVER 输出 recover successfully!。
R5 配置文件全文(dm.ini 改动 + 三份 ini)
主库 dm.ini 关键项(备库仅 INSTANCE_NAME 为 GRP1_RWW_02):
INSTANCE_NAME = GRP1_RWW_01
PORT_NUM = 5236
DW_INACTIVE_INTERVAL = 60
ALTER_MODE_STATUS = 0
ENABLE_OFFLINE_TS = 2
MAL_INI = 1
ARCH_INI = 1
RLOG_SEND_APPLY_MON = 64
dmmal.ini(两节点完全一致):
MAL_CHECK_INTERVAL = 5
MAL_CONN_FAIL_INTERVAL = 5
[MAL_INST1]
MAL_INST_NAME = GRP1_RWW_01
MAL_HOST = 192.168.40.154
MAL_PORT = 61141
MAL_INST_HOST = 192.168.40.154
MAL_INST_PORT = 5236
MAL_DW_PORT = 52141
MAL_INST_DW_PORT = 33141
[MAL_INST2]
MAL_INST_NAME = GRP1_RWW_02
MAL_HOST = 192.168.40.155
MAL_PORT = 61142
MAL_INST_HOST = 192.168.40.155
MAL_INST_PORT = 5236
MAL_DW_PORT = 52142
MAL_INST_DW_PORT = 33142
dmarch.ini(主库,备库把 ARCH_DEST 改为 GRP1_RWW_01):
[ARCHIVE_TIMELY1]
ARCH_TYPE = TIMELY
ARCH_DEST = GRP1_RWW_02
[ARCHIVE_LOCAL1]
ARCH_TYPE = LOCAL
ARCH_DEST = /data/dameng9/data/DM9DB/arch
ARCH_FILE_SIZE = 128
ARCH_SPACE_LIMIT = 0
dmwatcher.ini(两节点一致):
[GRP1]
DW_TYPE = GLOBAL
DW_MODE = AUTO
DW_ERROR_TIME = 10
INST_RECOVER_TIME = 60
INST_ERROR_TIME = 10
INST_OGUID = 453332
INST_INI = /data/dameng9/data/DM9DB/dm.ini
INST_AUTO_RESTART = 1
INST_STARTUP_CMD = /data/dameng9/bin/dmserver
RLOG_SEND_THRESHOLD = 0
RLOG_APPLY_THRESHOLD = 0
dmmonitor.ini(155):
MON_DW_CONFIRM = 1
MON_LOG_PATH = /data/dameng9/log
MON_LOG_INTERVAL = 60
MON_LOG_FILE_SIZE = 32
MON_LOG_SPACE_LIMIT = 0
[GRP1]
MON_INST_OGUID = 453332
MON_DW_IP = 192.168.40.154:52141
MON_DW_IP = 192.168.40.155:52142
R6 启动顺序与每步验证
# 主库 154
$ nohup dmserver /data/dameng9/data/DM9DB/dm.ini mount > /tmp/dm9_mount_primary.log 2>&1 &
$ disql SYSDBA/"Dameng@123"@localhost:5236 -e "SP_SET_PARA_VALUE(1, 'ALTER_MODE_STATUS', 1);"
$ disql SYSDBA/"Dameng@123"@localhost:5236 -e 'sp_set_oguid(453332);'
$ disql SYSDBA/"Dameng@123"@localhost:5236 -e "SP_SET_PARA_VALUE(1, 'ALTER_MODE_STATUS', 0);"
$ disql SYSDBA/"Dameng@123"@localhost:5236 -e "SP_SET_PARA_VALUE(1, 'ALTER_MODE_STATUS', 1);"
$ disql SYSDBA/"Dameng@123"@localhost:5236 -e 'ALTER DATABASE PRIMARY;'
$ disql SYSDBA/"Dameng@123"@localhost:5236 -e "SP_SET_PARA_VALUE(1, 'ALTER_MODE_STATUS', 0);"
$ disql SYSDBA/"Dameng@123"@localhost:5236 -e "select instance_name,mode\$,status\$ from v\$instance;"
# 备库 155,最后一句为 ALTER DATABASE STANDBY
$ nohup dmserver /data/dameng9/data/DM9DB/dm.ini mount > /tmp/dm9_mount_standby.log 2>&1 &
$ disql SYSDBA/"Dameng@123"@localhost:5236 -e 'ALTER DATABASE STANDBY;'
# 两节点起守护
$ nohup dmwatcher /data/dameng9/data/DM9DB/dmwatcher.ini > /tmp/dmwatcher.log 2>&1 &
# 155 起监视器
$ nohup dmmonitor /data/dameng9/data/DM9DB/dmmonitor.ini > /tmp/dmmonitor.log 2>&1 &
判定点:主库查到 GRP1_RWW_01 PRIMARY MOUNT;备库查到 GRP1_RWW_02 STANDBY MOUNT;起守护后两边变 OPEN;监视器日志里两边 RTYPE=TIMELY、RSTAT=VALID。
R7 服务化
# 两节点(守护)
# cp /data/dameng9/bin/service_template/DmWatcherService /data/dameng9/bin/DmWatcherServiceGRP1
# sed -i 's|%INI_PATH%|/data/dameng9/data/DM9DB/dmwatcher.ini|g; s|%PROVIDES%||g' /data/dameng9/bin/DmWatcherServiceGRP1
# chmod +x /data/dameng9/bin/DmWatcherServiceGRP1
# chown dmdba:dinstall /data/dameng9/bin/DmWatcherServiceGRP1
# 155(监视器)
# cp /data/dameng9/bin/service_template/DmMonitorService /data/dameng9/bin/DmMonitorServiceGRP1
# sed -i 's|%INI_PATH%|/data/dameng9/data/DM9DB/dmmonitor.ini|g; s|%PROVIDES%||g' /data/dameng9/bin/DmMonitorServiceGRP1
# chmod +x /data/dameng9/bin/DmMonitorServiceGRP1
# chown dmdba:dinstall /data/dameng9/bin/DmMonitorServiceGRP1
# cat > /usr/lib/systemd/system/DmWatcherServiceGRP1.service <<'EOF'
[Unit]
Description=DM Data Watch Service(DmWatcherServiceGRP1)
After=network-online.target remote-fs.target nss-lookup.target
Wants=network-online.target
[Service]
Type=forking
PIDFile=/data/dameng9/bin/pids/DmWatcherServiceGRP1.pid
ExecStart="/data/dameng9/bin/DmWatcherServiceGRP1" start
ExecStop="/data/dameng9/bin/DmWatcherServiceGRP1" stop
PrivateTmp=true
User=dmdba
TasksMax=infinity
LimitCORE=infinity
LimitNOFILE=100000
LimitNPROC=100000
[Install]
WantedBy=multi-user.target
EOF
# systemctl daemon-reload
# systemctl enable --now DmWatcherServiceGRP1
判定点:systemctl status DmWatcherServiceGRP1 为 active (running),主 PID 是 dmwatcher。
R8 验证清单与故障实验
$ disql SYSDBA/"Dameng@123"@192.168.40.154:5236 -e 'select count(*) as cnt from t_dw;'
$ disql SYSDBA/"Dameng@123"@192.168.40.155:5236 -e 'select count(*) as cnt from t_dw;'
$ disql SYSDBA/"Dameng@123"@192.168.40.155:5236 -e "insert into t_dw(id,name) values(9999,'from_standby');"
# cat > /etc/dm_svc.conf <<'EOF'
# DM9 读写分离集群服务名
TIME_ZONE=(480)
LANGUAGE=(cn)
dmrw=(192.168.40.154:5236,192.168.40.155:5236)
RW_SEPARATE=(1)
RW_PERCENT=(0)
SWITCH_TIMES=(3)
SWITCH_INTERVAL=(1000)
EOF
$ disql SYSDBA/"Dameng@123"@dmrw -e "insert into t_dw(id,name) values(7777,'via_svc_write');"
$ disql SYSDBA/"Dameng@123"@dmrw -e 'commit;'
$ disql SYSDBA/"Dameng@123"@dmrw -e 'select instance_name,mode$ from v$instance;'
# PID=$(pgrep -f 'dmserver /data/dameng9/data/DM9DB/dm.ini'); kill -9 $PID
判定点:主备行数一致;备库写入报 [-710]:试图在STANDBY模式下,修改用户库.;服务名写入落在主库、RW_PERCENT=0 时读落在备库;kill 后 10 秒内 dmserver 被守护拉起。
需要直连备库做核对时,dm_svc.conf 会拦一道(启用读写分离时 LOGIN_MODE 不生效),临时移走即可:
# mv /etc/dm_svc.conf /tmp/dm_svc.conf.bak
$ disql SYSDBA/"Dameng@123"@192.168.40.155:5236 -e 'select instance_name from v$instance;'
# mv /tmp/dm_svc.conf.bak /etc/dm_svc.conf
R9 长期运维指令表
| 场景 | 命令 | 预期 |
|---|---|---|
| 看集群全局 | tail -25 /data/dameng9/log/dmmonitor_*.log | GROUP GRP1 / OGUID 453332 / MON_CONFIRM TRUE |
| 看守护状态 | systemctl status DmWatcherServiceGRP1 | active (running) |
| 看监视器状态 | systemctl status DmMonitorServiceGRP1(155) | active (running) |
| 看实例端口 | ss -lntp | grep -E '5236|61141|52141|33141' | 四个端口在听 |
| 连主库 | disql SYSDBA/"Dameng@123"@192.168.40.154:5236 | 主库打开状态 |
| 通过服务名连 | disql SYSDBA/"Dameng@123"@dmrw | 按读写类型路由 |
| 查主备角色 | select instance_name,mode$,status$ from v$instance; | 一 PRIMARY 一 STANDBY |
| 数据一致性抽查 | 两侧 select count(*) from <表>; | 行数一致 |
| 归档堆积检查 | ls -lt /data/dameng9/data/DM9DB/arch | head | 按策略清理 |
| 重启集群 | 先停监视器与守护,再停实例;启动时反向 | 角色保持不变 |




