暂无图片
暂无图片
2
暂无图片
暂无图片
暂无图片

从单机到两节点:达梦 DM9 9.1.0.26 读写分离集群搭建实录

原创 山佳 2026-09-23
130

 

从单机到两节点:达梦 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.comshanjiatst6.sjtest.com
IP192.168.40.154192.168.40.155
OSRocky Linux 8.10Rocky Linux 8.10
内核4.18.0-553.el8_10.x86_644.18.0-553.el8_10.x86_64
CPU / 内存4 核 / 7.5Gi8 核 / 7.5Gi
磁盘60G,可用 49G120G,可用 111G
初始时区CSTEDT(要改)

软件版本两节点一致:安装介质 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_NUM52365236
MAL 通信端口6114161142
守护进程监听 MAL_DW_PORT5214152142
实例本地守护 MAL_INST_DW_PORT3314133142
组名 / OGUIDGRP1 / 453332GRP1 / 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_*.logGRP1 / 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 DmWatcherServiceGRP1active (running)

终态:两节点守护进程由 systemd 托管并开机自启,155 上运行确认监视器,主备 LSN 对齐,服务名 dmrw 能把写路由到主库、把读路由到备库。

附录:完整安装配置复现手册(Runbook)

R0 前置条件

项目要求
节点2 台,192.168.40.154(主)、192.168.40.155(备)
OSRocky 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_*.logGROUP GRP1 / OGUID 453332 / MON_CONFIRM TRUE
看守护状态systemctl status DmWatcherServiceGRP1active (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按策略清理
重启集群先停监视器与守护,再停实例;启动时反向角色保持不变

 

最后修改时间:2026-09-24 09:54:40
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论