注册
从零搭建达梦 DM8 读写分离主备 + 异步集群
专栏/技术分享/ 文章详情 /

从零搭建达梦 DM8 读写分离主备 + 异步集群

迷枫 2026/05/28 359 0 0
摘要

本文就是我从零搭建读写分离主备 + 异步集群的完整记录,希望对你也有帮助。


目录


一、整体架构,先看懂再动手

搭之前看看架构图,知道自己每一步在干嘛:

+-------------------------------------------------------------+
|                      DM8 数据守护集群                        |
|                                                             |
|  +-----------+   REDO 实时同步  +-----------+              |
|  |  主库      | --------------> |  实时备库   |              |
|  | 131 DW1_01| <-------------- | 132 DW1_02 |              |
|  | PRIMARY   |   故障时可切换    |  STANDBY   |              |
|  +-----+-----+                 +------------+              |
|        |                                                    |
|        |  REDO 异步定时发送(每1分钟批量推)                  |
|        v                                                    |
|  +-----------+                  +-----------+              |
|  |  异步备库  |                  |  确认监视器 |              |
|  | 133 DW1_03|                  | 134       |              |
|  |  STANDBY  |                  | dmmonitor |              |
|  +-----------+                  +-----------+              |
+-------------------------------------------------------------+

读写分离的核心逻辑与适用场景

  • 概念:达梦的读写分离集群底层依赖数据守护(Data
    Watch)机制。简单来说,主库(Primary)包揽所有的写入和修改操作,备库(Standby)在实时同步主库数据的同时,帮主库分担查询(SELECT)的压力。

  • 适用场景最适合“读多写少”的业务模型。以“全局通知发布系统”为例:**管理员在后台发布了一条全员通知(这是写入动作,只有 1 次并发),但瞬间会有成千上万的员工打开 APP 去查看这条通知(这是读取动作,可能有 10000 次并发)。在这种大家只能看、不能评论的场景下,所有的查询压力都被完美分流到了备库上,主库在海量并发下依然能稳如泰山。

三种角色,一句话区分:

  • 主库(PRIMARY):唯一可以读写的库,产生 REDO 日志并推给备库
  • 实时备库(STANDBY + GLOBAL 守护):实时接收 REDO、只读,主库挂了它能接管
  • 异步备库(STANDBY + LOCAL 守护):定时接收归档日志、只读,纯容灾用,不参与自动切换

集群规划:

1 机器 2 机器 3 机器(异步)
业务 IP 192.168.166.131 192.168.166.132 192.168.166.133
心跳 IP 192.168.166.131 192.168.166.132 192.168.166.133
实例名 DW1_01 DW1_02 DW1_03
实例端口 15236 15236 15236
MAL 端口 5336 5336 5336
MAL 守护进程端口 5436 5436 5436
守护进程端口 5536 5536 5536
INST_OGUID 45331 45331 45331
守护组 GDW1 GDW1 GDW1
安装路径 /dmdbms /dmdbms /dmdbms
实例路径 /dmdata/ /dmdata/ /dmdata/
归档路径 /dmarch /dmarch /dmarch
归档上限(MB) 51200 51200 51200
确认监视器 IP 192.168.166.134

关于业务 IP 和心跳 IP:生产环境这两个必须用不同网卡分开,心跳走专用内网,业务走外网,这样才能避免业务流量把心跳链路堵死导致误判故障。本实验是学习环境,只有一块网卡,所以两者填的是同一个 IP,知道这个差异就行。


二、基础准备:VMware + 克隆

2.1 虚拟机规格

(1)按照https://eco.dameng.com/community/post/202604061906375B122IUUKRZZT42M2B完成基础环境(至3.5)虚拟机规格如下:

项目 配置
内存 2.9 GB
处理器 2 核
硬盘 50 GB
网络模式 NAT

在这里插入图片描述
(2)root用户创建所需目录、赋予权限

mkdir /dmdbms mkdir /dmdata mkdir /dmarch chown -R dmdba:dinstall /dmdbms chown -R dmdba:dinstall /dmdata chown -R dmdba:dinstall /dmarch

(3)修改为静态ip,务必删除uuid

vi /etc/sysconfig/network-scripts/ifcfg-ens33 TYPE=Ethernet PROXY_METHOD=none BROWSER_ONLY=no # BOOTPROTO=dhcp 从dhcp改成static BOOTPROTO=static DEFROUTE=yes IPV4_FAILURE_FATAL=no IPV6INIT=yes IPV6_AUTOCONF=yes IPV6_DEFROUTE=yes IPV6_FAILURE_FATAL=no IPV6_ADDR_GEN_MODE=stable-privacy NAME=ens33 #UUID=f72480f8-57dc-4bcd-b5a4-01e056963051 DEVICE=ens33 # ONBOOT=no 从no改成yes ONBOOT=yes #IPADDR配置相关的IPV4地址 IPADDR=192.168.166.130 #NETMASK配置对应的子网掩码 NETMASK=255.255.255.0 #GATEWAY配置对应的网关 GATEWAY=192.168.166.1 #DNS1、DNS2配置对应的域名解析服务器 DNS1=8.8.8.8

2.2 克隆三份

(1)基础机器做好后直接克隆三份(完整克隆,快照克隆会共享磁盘,别用):

VMware → 右键虚拟机 → 管理 → 克隆 → 创建完整克隆
克隆三份,命名 dmdw02、dmdw03、dmdw04

(2)分配ip:
机器1、2、3、4执行

# 以 131 为例,修改 IP vi /etc/sysconfig/network-scripts/ifcfg-ens33 IPADDR=192.168.166.131 systemctl restart NetworkManager
VMware → 右键虚拟机 →设置 → 网络适配器 → 高级 → 生成MAC地址 →  确定 → 重启

在这里插入图片描述

# 改主机名 hostnamectl set-hostname dmdw01 # 131 机器 hostnamectl set-hostname dmdw02 # 132 机器 hostnamectl set-hostname dmdw03 # 133 机器 hostnamectl set-hostname dmdw04 # 134 机器

三、配置主库(1 机器)

所有操作以 dmdba 用户执行,注册服务切换 root。

3.1 初始化实例 + 开启集群相关参数

只在 1 机器执行 dminit,备库不要自己初始化:

[dmdba@~]$ /dmdbms/bin/dminit PATH=/dmdata/ INSTANCE_NAME=DW1_01 PORT_NUM=15236 PAGE_SIZE=32 EXTENT_SIZE=32 LOG_SIZE=2048 SYSDBA_PWD=Dameng@1234 SYSAUDITOR_PWD=Dameng@1234

PAGE_SIZE=32 这个参数在 init 之后就锁死了,后面改不了,一定要最开始就定好。

在这里插入图片描述

前台临时启动,用 disql 连上去改参数:

[dmdba@~]$ /dmdbms/bin/dmserver /dmdata/DAMENG/dm.ini # 新开一个终端连进去 [dmdba@~]$ /dmdbms/bin/disql SYSDBA/'"Dameng@1234"':15236
-- 先切到 MOUNT 模式,很多参数只有在 MOUNT 状态才能改 SQL> ALTER DATABASE MOUNT; SQL> SP_SET_PARA_VALUE (2,'PORT_NUM',15236); SQL> SP_SET_PARA_VALUE (2,'DW_INACTIVE_INTERVAL',60); -- 守护进程心跳间隔 SQL> SP_SET_PARA_VALUE (2,'ALTER_MODE_STATUS',0); -- 禁止手工切换主备角色,防止误操作 SQL> SP_SET_PARA_VALUE (2,'ENABLE_OFFLINE_TS',2); -- 允许备库表空间脱机 SQL> SP_SET_PARA_VALUE (2,'MAL_INI',1); -- 开启 MAL 通信,集群必须开 SQL> SP_SET_PARA_VALUE (2,'ARCH_INI',1); -- 开启归档,集群必须开 SQL> SP_SET_PARA_VALUE (2,'TIMER_INI',1); -- 开启定时器,给异步归档用 SQL> SP_SET_PARA_VALUE (2,'RLOG_SEND_APPLY_MON',64); -- 监控发送/应用日志的滑动窗口大小 SQL> ALTER DATABASE OPEN;

关掉前台实例,然后
做脱机备份:

[dmdba@~]$ /dmdbms/bin/dmrman CTLSTMT="BACKUP DATABASE '/dmdata/DAMENG/dm.ini' FULL BACKUPSET '/dmdata/DAMENG/bak/BACKUP_FILE' PARALLEL 4"

这里备份的意义不是日常备份,而是给备库提供"初始数据"。后面备库会用这份备份做还原,和主库保证数据一致性。


在这里插入图片描述

3.2 dmarch.ini — 归档配置,最容易搞混的一个文件

[dmdba@~]$ vi /dmdata/DAMENG/dmarch.ini
[ARCHIVE_LOCAL] ARCH_TYPE = LOCAL # 本地归档,必须有,是其他归档的"原材料" ARCH_DEST = /dmarch # 本地归档存放路径 ARCH_FILE_SIZE = 1024 # 单个归档文件大小,单位 MB ARCH_SPACE_LIMIT = 51200 # 归档总上限,超了会自动清理最老的,单位 MB [ARCHIVE_REALTIME] ARCH_TYPE = REALTIME # 实时归档,主库每次 COMMIT 都会实时推给备库 ARCH_DEST = DW1_02 # 目标是实时备库的实例名(不是 IP!) [ARCHIVE_ASYNC] ARCH_TYPE = ASYNC # 异步归档,按定时器批量推 ARCH_DEST = DW1_03 # 目标是异步备库的实例名 ARCH_TIMER_NAME = TIMER1 # 关联哪个定时器(在 dmtimer.ini 里定义)

重点ARCH_DEST 填的是实例名,不是 IP 或主机名。系统通过 dmmal.ini 里的实例名去查对应的 IP。这个设计的好处是切换后不用改归档配置,因为实例名不变。

在这里插入图片描述


3.3 dmmal.ini — 集群的"通讯录"

这个文件必须在所有节点上完全一致,少一个节点或者写错 IP 都会导致通信失败。

[dmdba@~]$ vi /dmdata/DAMENG/dmmal.ini
MAL_CHECK_INTERVAL = 10 # MAL 链路心跳检测间隔,秒 MAL_CONN_FAIL_INTERVAL = 10 # 超过这个时间收不到响应,判定链路断开 MAL_TEMP_PATH = /dmdata/malpath/ # 日志传输的临时缓冲目录,要提前建好 MAL_BUF_SIZE = 512 # 单个 MAL 连接的缓冲大小,单位 MB MAL_SYS_BUF_SIZE = 2048 # MAL 系统总缓冲上限,单位 MB MAL_COMPRESS_LEVEL = 0 # 日志压缩等级,0 不压缩,跨地域传输可以考虑开 [MAL_INST1] MAL_INST_NAME = DW1_01 # 实例名,与 dm.ini 中 INSTANCE_NAME 对应 MAL_HOST = 192.168.166.131 # 心跳 IP,用于日志传输和守护通信 MAL_PORT = 5336 # MAL 监听端口,用于实例间日志传输 MAL_INST_HOST = 172.16.166.131 # 业务 IP,应用程序连接数据库用 MAL_INST_PORT = 15236 # 业务端口,数据库对外服务端口 MAL_DW_PORT = 5436 # 守护进程对外监听端口(守护进程互联、监视器连接用) MAL_INST_DW_PORT = 5536 # 实例监听守护进程的端口(守护向实例发指令用) [MAL_INST2] MAL_INST_NAME = DW1_02 MAL_HOST = 192.168.166.132 MAL_PORT = 5336 MAL_INST_HOST = 172.16.166.132 MAL_INST_PORT = 15236 MAL_DW_PORT = 5436 MAL_INST_DW_PORT = 5536 [MAL_INST3] MAL_INST_NAME = DW1_03 MAL_HOST = 192.168.166.133 MAL_PORT = 5336 MAL_INST_HOST = 172.16.166.133 MAL_INST_PORT = 15236 MAL_DW_PORT = 5436 MAL_INST_DW_PORT = 5536

端口关系:5336 是节点间传日志的,5436 是守护进程之间、守护进程和监视器握手的,5536 是守护进程向实例下命令用的(比如"你给我切换成 STANDBY")。三个端口职责完全不同,不要混淆。


在这里插入图片描述

3.4 dmwatcher.ini — 守护进程配置

[dmdba@~]$ vi /dmdata/DAMENG/dmwatcher.ini
[GDW1] DW_TYPE = GLOBAL # 全局守护,主库和实时备库用这个;异步备库用 LOCAL DW_MODE = AUTO # AUTO=故障自动切换,MANUAL=故障手动切换 DW_ERROR_TIME = 20 # 多少秒收不到远端守护进程的心跳,认定对方故障,秒 INST_ERROR_TIME = 20 # 多少秒连不上本机实例,认定本机实例故障,秒 INST_RECOVER_TIME = 60 # 主库守护进程尝试恢复实例的间隔时间,秒 INST_OGUID = 45331 # 整个守护系统的唯一标识,所有节点必须相同! INST_INI = /dmdata/DAMENG/dm.ini # 本机 dm.ini 路径 INST_AUTO_RESTART = 1 # 实例崩溃后守护进程自动拉起,生产必开 INST_STARTUP_CMD = /dmdbms/bin/DmServiceDW start RLOG_SEND_THRESHOLD = 0 # 主库发送日志延迟告警阈值,0=关闭告警 RLOG_APPLY_THRESHOLD = 0 # 备库应用日志延迟告警阈值,0=关闭告警

OGUID 必须一致:它是整个守护组的组号,如果不一致,守护进程启动后会互相认不出对方。

在这里插入图片描述

3.5 dmtimer.ini — 控制异步归档的推送节奏

这个文件是异步备库的核心,主库靠它来决定什么时候、什么频率把归档推给异步备库。

[dmdba@~]$ vi /dmdata/DAMENG/dmtimer.ini

示例一:每隔 1 分钟推送一次(适合实验环境,实时性强)

[TIMER1] TYPE = 2 # 按日执行模式 FREQ_MONTH_WEEK_INTERVAL = 1 # 间隔周/月数 FREQ_SUB_INTERVAL = 1 # 间隔天数 FREQ_MINUTE_INTERVAL = 1 # 间隔分钟数,这里设 1 分钟推一次 START_TIME = 00:00:00 END_TIME = 00:00:00 # 开始和结束都是 00:00:00 代表全天有效 DURING_START_DATE = 2020-01-01 01:01:01 DURING_END_DATE = 9999-12-31 23:59:59 NO_END_DATE_FLAG = 1 DESCRIBE = RT TIMER IS_VALID = 1

示例二:每天凌晨 01:30~04:30 推送(适合生产,错开业务高峰)

[TIMER1] TYPE = 2 FREQ_MONTH_WEEK_INTERVAL = 1 FREQ_SUB_INTERVAL = 0 FREQ_MINUTE_INTERVAL = 0 START_TIME = 01:30:00 # 从凌晨 1:30 开始 END_TIME = 04:30:00 # 到凌晨 4:30 结束 DURING_START_DATE = 2020-01-01 01:01:01 DURING_END_DATE = 9999-12-31 23:59:59 NO_END_DATE_FLAG = 1 DESCRIBE = RT TIMER IS_VALID = 1

异步备库的数据延迟到底有多大,完全由这个定时器决定。1 分钟推一次,最大 RPO 就是 1 分钟的数据量。生产上具体设多少,要结合业务对数据损失的容忍度来定。


在这里插入图片描述

3.6 拷贝实例到 2 机器和 3 机器

[dmdba@~]$ scp -r /dmdata/DAMENG dmdba@192.168.166.132:/dmdata/ [dmdba@~]$ scp -r /dmdata/DAMENG dmdba@192.168.166.133:/dmdata/

为什么不在备库重新 dminit,而是要拷贝?

两个原因:

  1. dminit 会生成随机的加密密钥,备库如果用自己的密钥初始化,根本无法解析主库传来的数据。
  2. 每个实例初始化时会生成一个数据库 permanent_magic(永久魔数),主库传日志前会校验这个值是否匹配,不一致直接拒绝。

所以:只在主库 dminit,然后把整个实例目录拷过去,备库通过 dmrman RESTORE/RECOVER 来建立自己的数据版本,再用 UPDATE DB_MAGIC 更新"数据库魔数"(注意不是永久魔数)来标记这是一个独立的还原版本。


在这里插入图片描述

3.7 注册服务

# 注册数据库服务:-m mount 是关键!必须以 MOUNT 状态启动
# 如果以 OPEN 启动,数据库会开始产生 Redo 日志,破坏主备数据一致性
# 以 root 执行 [root@~]# /dmdbms/script/root/dm_service_installer.sh -t dmserver -p DW -dm_ini /dmdata/DAMENG/dm.ini -m mount # 注册守护进程服务 [root@~]# /dmdbms/script/root/dm_service_installer.sh -t dmwatcher -p Watcher -watcher_ini /dmdata/DAMENG/dmwatcher.ini

在这里插入图片描述

四、配置实时备库(2 机器)

4.1 修改 dm.ini

拷贝过来的实例名还是 DW1_01,要改成自己的:

[dmdba@~]$ vi /dmdata/DAMENG/dm.ini INSTANCE_NAME = DW1_02 # 改成自己的实例名,这是唯一需要改的 dm.ini 参数

4.2 替换 dmarch.ini

2 机器是实时备库,也配了 ARCHIVE_ASYNC,故障切换后,DW1_02 会变成新的主库,那时候它就需要负责向异步备库 DW1_03 推送日志。

[dmdba@~]$ vi /dmdata/DAMENG/dmarch.ini
[ARCHIVE_LOCAL] ARCH_TYPE = LOCAL ARCH_DEST = /dmarch ARCH_FILE_SIZE = 1024 ARCH_SPACE_LIMIT = 51200 [ARCHIVE_REALTIME] ARCH_TYPE = REALTIME ARCH_DEST = DW1_01 # 反向指向主库——切换后 DW1_02 变主库,DW1_01 变备库,要向它同步 [ARCHIVE_ASYNC] ARCH_TYPE = ASYNC ARCH_DEST = DW1_03 ARCH_TIMER_NAME = TIMER1

4.3 相同配置项

dmmal.inidmwatcher.inidmtimer.ini 与 1 机器完全相同,不需要改。

4.4 注册服务

[root@~]# /dmdbms/script/root/dm_service_installer.sh -t dmserver -p DW -dm_ini /dmdata/DAMENG/dm.ini -m mount [root@~]# /dmdbms/script/root/dm_service_installer.sh -t dmwatcher -p Watcher -watcher_ini /dmdata/DAMENG/dmwatcher.ini

在这里插入图片描述

4.5 恢复数据

用主库的备份在备库上做还原,建立一致的数据起点:

# 第一步:还原(用备份覆盖当前数据文件) [dmdba@~]$ /dmdbms/bin/dmrman CTLSTMT="RESTORE DATABASE '/dmdata/DAMENG/dm.ini' FROM BACKUPSET '/dmdata/DAMENG/bak/BACKUP_FILE'" # 第二步:恢复(应用备份集中的归档日志,把数据推进到备份结束时) [dmdba@~]$ /dmdbms/bin/dmrman CTLSTMT="RECOVER DATABASE '/dmdata/DAMENG/dm.ini' FROM BACKUPSET '/dmdata/DAMENG/bak/BACKUP_FILE'" # 第三步:更新数据库魔数(标记这是一个独立的数据库实体,区别于主库) [dmdba@~]$ /dmdbms/bin/dmrman CTLSTMT="RECOVER DATABASE '/dmdata/DAMENG/dm.ini' UPDATE DB_MAGIC"

在这里插入图片描述


五、配置异步备库(3 机器)

异步备库和实时备库的配置差异主要集中在三个地方:dm.ini 关闭定时器、dmarch.ini 只配本地归档、dmwatcher.ini 用 LOCAL 守护类型。

5.1 修改 dm.ini

[dmdba@~]$ vi /dmdata/DAMENG/dm.ini
INSTANCE_NAME = DW1_03 # 实例名 TIMER_INI = 0 # 关闭定时器!异步备库只接收日志,不主动推送,所以不需要定时器

5.2 替换 dmarch.ini

异步备库只配本地归档就够了,它的职责就是「被动接收」,不需要向其他节点推送什么:

[dmdba@~]$ vi /dmdata/DAMENG/dmarch.ini
[ARCHIVE_LOCAL] ARCH_TYPE = LOCAL ARCH_DEST = /dmarch ARCH_FILE_SIZE = 1024 ARCH_SPACE_LIMIT = 51200 # 没有 REALTIME 也没有 ASYNC,纯被动接收节点

5.3 创建 dmwatcher.ini

这里是异步备库和其他节点最关键的区别DW_TYPE = LOCAL,本地守护模式:

[dmdba@~]$ vi /dmdata/DAMENG/dmwatcher.ini
[GDW1] DW_TYPE = LOCAL # 本地守护!不参与全局切换决策,监视器不会选它当主库 DW_MODE = AUTO DW_ERROR_TIME = 20 INST_ERROR_TIME = 20 INST_OGUID = 45331 # OGUID 还是要相同 INST_INI = /dmdata/DAMENG/dm.ini INST_AUTO_RESTART = 1 INST_STARTUP_CMD = /dmdbms/bin/DmServiceDW start

LOCAL 守护的含义:这个守护进程只负责看着本机实例活没活,不参与主备切换的投票。就算它大声喊"主库挂了",监视器也不会理它。这设计很合理——异步备库数据本来就有延迟,当然不能让它自作主张接管。

5.4 相同配置项

dmmal.inidmtimer.ini 与 1 机器相同(拷贝过来的,不用改)。

5.5 注册服务

[root@~]# /dmdbms/script/root/dm_service_installer.sh -t dmserver -p DW -dm_ini /dmdata/DAMENG/dm.ini -m mount [root@~]# /dmdbms/script/root/dm_service_installer.sh -t dmwatcher -p Watcher -watcher_ini /dmdata/DAMENG/dmwatcher.ini

在这里插入图片描述

5.6 恢复数据

[dmdba@~]$ /dmdbms/bin/dmrman CTLSTMT="RESTORE DATABASE '/dmdata/DAMENG/dm.ini' FROM BACKUPSET '/dmdata/DAMENG/bak/BACKUP_FILE'" [dmdba@~]$ /dmdbms/bin/dmrman CTLSTMT="RECOVER DATABASE '/dmdata/DAMENG/dm.ini' FROM BACKUPSET '/dmdata/DAMENG/bak/BACKUP_FILE'" [dmdba@~]$ /dmdbms/bin/dmrman CTLSTMT="RECOVER DATABASE '/dmdata/DAMENG/dm.ini' UPDATE DB_MAGIC"

在这里插入图片描述


六、配置监视器(4 机器)

监视器是整个守护系统的"仲裁者",放在独立的第四台机器上(不能放在集群节点上,否则脑裂时仲裁失效)。

切换模式说明

dmwatcher 要求 dmmonitor 要求 说明
故障手动切换 DW_MODE = MANUAL MON_DW_CONFIRM = 0 非确认监视器
故障自动切换 DW_MODE = AUTO MON_DW_CONFIRM = 1 需要确认监视器,放在仲裁机

6.1 创建 dmmonitor.ini

[dmdba@~]$ vi /dmdbms/bin/dmmonitor.ini
MON_DW_CONFIRM = 1 # 1=确认监视器,发起切换需要多数票(防脑裂的关键) MON_LOG_PATH = ../log MON_LOG_INTERVAL = 60 # 每 60 秒记录一次系统状态到日志 MON_LOG_FILE_SIZE = 512 MON_LOG_SPACE_LIMIT = 2048 [GDW1] MON_INST_OGUID = 45331 MON_DW_IP = 192.168.166.131:5436 # IP 对应 MAL_HOST,端口对应 MAL_DW_PORT MON_DW_IP = 192.168.166.132:5436 MON_DW_IP = 192.168.166.133:5436 # 异步备库也要加进来,纳入监控范围

在这里插入图片描述

6.2 注册服务(选做)

[root@~]# /dmdbms/script/root/dm_service_installer.sh -t dmmonitor -p Monitor -monitor_ini /dmdbms/bin/dmmonitor.ini

在这里插入图片描述

6.3 监视器常用命令

命令 含义
list 查看守护进程的配置信息
show global info 查看所有实例组的信息
tip 快速查看当前运行状态
login 登录(执行切换操作前必须登录)
logout 退出登录
choose switchover GDW1 主库正常时,查看可切换为主库的备库列表
switchover GDW1.实例名 主库正常时,将指定实例切换为主库
choose takeover GDW1 主库故障时,查看可接管的备库列表
takeover GDW1.实例名 主库故障时,让指定备库接管
takeover force GDW1.实例名 强制接管,不管数据是否完整(慎用)

七、启动集群,声明角色

7.1 启动数据库,声明 PRIMARY 和 STANDBY

1 机器(主库)

[dmdba@~]$ /dmdbms/bin/DmServiceDW start [dmdba@~]$ /dmdbms/bin/disql SYSDBA/'"Dameng@1234"':15236 SQL> SP_SET_OGUID(45331); -- 先设 OGUID,告诉数据库自己属于哪个守护组 SQL> ALTER DATABASE PRIMARY; -- 声明为主库

在这里插入图片描述

2 机器(实时备库)

[dmdba@~]$ /dmdbms/bin/DmServiceDW start [dmdba@~]$ /dmdbms/bin/disql SYSDBA/'"Dameng@1234"':15236 SQL> SP_SET_OGUID(45331); SQL> ALTER DATABASE STANDBY;

在这里插入图片描述

3 机器(异步备库)

[dmdba@~]$ /dmdbms/bin/DmServiceDW start [dmdba@~]$ /dmdbms/bin/disql SYSDBA/'"Dameng@1234"':15236 SQL> SP_SET_OGUID(45331); SQL> ALTER DATABASE STANDBY; -- 跟实时备库一样,都是 STANDBY 模式

在这里插入图片描述

7.2 启动守护进程(1/2/3 机器)

[dmdba@~]$ /dmdbms/bin/DmWatcherServiceWatcher start

7.3 启动监视器(4 机器)

# 前台启动,能实时看到切换日志,调试时推荐 [dmdba@~]$ /dmdbms/bin/dmmonitor /dmdbms/bin/dmmonitor.ini # 后台启动 [dmdba@~]$ /dmdbms/bin/DmMonitorServiceMonitor start

验证集群状态,在监视器前台执行:

show

# 各节点 WSTATUS = OPEN、RSTAT = VALID 就说明集群正常

在这里插入图片描述

7.4 日常启停

# 启动:启守护进程就行,数据库会被自动拉起 1/2/3 机器:/dmdbms/bin/DmWatcherServiceWatcher start # 停止 1/2/3 机器:/dmdbms/bin/DmWatcherServiceWatcher stop 1/2/3 机器:/dmdbms/bin/DmServiceDW stop

八、配置应用连接

  • Linux 环境:dm_svc.conf/etc/
  • Win32 环境:放 System32
  • Win64 环境:放 System32SysWOW64

8.1 dm_svc.conf 配置

TIME_ZONE=(480)
LANGUAGE=(cn)

# 服务别名,列出集群所有节点
DW1=(192.168.166.131:15236, 192.168.166.132:15236)

[DW1]
CLUSTER=(DW)            # 守护组名,对应 dmwatcher.ini 里的 [GDW1]
SWITCH_TIMES=(300)      # 连接失败时重试次数
SWITCH_INTERVAL=(200)   # 重试间隔,毫秒
RW_SEPARATE=(1)         # 开启读写分离,写请求路由主库,读请求路由备库

8.2 应用连接示例(Spring Boot)

第 1 步:在 IDEA 中创建项目
Java: 选择 8
在依赖选择页面,勾选以下两项:

Web -> Spring Web
SQL -> JDBC API

第 2 步:安放并配置达梦驱动
在项目的根目录新建一个文件夹,命名为 lib。
达梦安装目录找到 DmJdbcDriver8.jar,放在lib文件夹里。

在这里插入图片描述

打开 pom.xml,添加

<dependency> <groupId>com.dameng</groupId> <artifactId>DmJdbcDriver18</artifactId> <version>8.1.2</version> <scope>system</scope> <systemPath>${project.basedir}/lib/DmJdbcDriver18.jar</systemPath> </dependency>

在这里插入图片描述

第 3 步:编写配置文件

server: port: 8080 spring: datasource: # 认准达梦驱动 driver-class-name: dm.jdbc.driver.DmDriver #不写 IP,只写服务名 DW1 url: jdbc:dm://DW1 # 你的数据库账号和密码 username: SYSDBA password: Dameng@1234

第 4 步:写入测试代码
在 src/main/java/com/example/dmclustertest/(你的基础包名路径)下,新建一个 Java 类,命名为 DmClusterTestController。

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController package org.example.demo418; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class DmClusterTestController { @Autowired private JdbcTemplate jdbcTemplate; @GetMapping("/test-dm") public String testCluster() { try { // 简单的查询测试,证明连接畅通 String instanceName = jdbcTemplate.queryForObject("SELECT INSTANCE_NAME FROM V$INSTANCE", String.class); String dbVersion = jdbcTemplate.queryForObject("SELECT * FROM V$VERSION WHERE ROWNUM = 1", String.class); return "<div style='font-family: Arial; padding: 20px; line-height: 1.6;'>" + "<h2 style='color: green;'>✅ 达梦数据库连接成功!</h2>" + "<b>当前连接的数据库实例名:</b> <span style='color: red; font-size: 20px;'>" + instanceName + "</span><br>" + "</div>"; } catch (Exception e) { return "<h2 style='color: red;'>❌ 连接失败</h2><br>错误信息: " + e.getMessage(); } } }

第 5 步:启动

在这里插入图片描述

应用代码不感知集群拓扑变化,切换后只需要驱动重新路由,应用代码一行不改。


8.3 读写分离机制解析与生效测试

(1)读写分离是怎么实现的?
读写分离的实现,依赖于驱动(如 DM JDBC、DPI 等)与 dm_svc.conf 文件的深度配合,整个过程对应用层代码透明。其核心路由逻辑如下:

拓扑感知与连接分配:
应用发起连接时,接口会根据 dm_svc.conf 中配置的 IP 列表进行**“裂变访问”,探测并获取当前集群的主、备机信息**。随后,严格根据配置的 LOGIN_MODE(登录模式)参数来返回相应的节点:

0(默认值):优先连主库(Primary) → 其次普通库(Normal) → 最后备库(Standby)

1:只连主库(Primary)

2:只连备库(Standby)

3:优先连备库(Standby) → 其次主库(Primary) → 最后普通库(Normal)

4:优先连普通库(Normal) → 其次主库(Primary) → 最后备库(Standby)

试探性路由分发:
在开启读写分离RW_SEPARATE=(1)后,接口在处理 SQL 时采用了试错策略

优先抛给备库:接口会优先把 SQL 语句发送到备库去执行。

成功即返回:如果是普通的 SELECT 查询,备库执行成功,结果就直接返回给用户,完美分担压力。

失败切主库:如果是 INSERT/UPDATE/DELETE 或是DDL 语句,备库(因为是只读状态)执行必然会报错。接口在底层瞬间捕获到这个失败后,会立刻将同一个事务里的所有操作,全部打包重新发送给主库去执行。只要发生了切主,该事务周期内的后续所有 SQL 都会死死锁定在主库上,保证数据绝对的一致性。

(2)测试读写分离是否真的生效了
我们可以利用刚才 Spring Boot 里的测试代码,做验证。

验证读请求路由(走备库):
直接在浏览器访问我们刚才写的 /test-dm 接口(里面只执行了 SELECT INSTANCE_NAME FROM V$INSTANCE)。
👉 预期结果:页面显示的当前连接实例名应该是 DW1_02(实时备库)。这说明纯查询操作被成功分发给了备库。

在这里插入图片描述

验证写请求路由(走主库):
覆盖原来的 DmClusterTestController.java:

package org.example.demo418; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class DmClusterTestController { @Autowired private JdbcTemplate jdbcTemplate; @GetMapping("/test-dm") public String testCluster() { try { // 1. 纯写操作:只要执行了写类型的 SQL(如 CREATE, INSERT, UPDATE, DELETE), // 达梦驱动就会立刻将当前事务/连接强制路由给主库。 jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS RW_TEST_ONLY_WRITE(ID INT)"); jdbcTemplate.execute("INSERT INTO RW_TEST_ONLY_WRITE SELECT 1"); // 2. 验证路由结果:此时查询实例名,应该一定是主库的实例名(例如 DW1_01) String instanceName = jdbcTemplate.queryForObject("SELECT INSTANCE_NAME FROM V$INSTANCE", String.class); String dbVersion = jdbcTemplate.queryForObject("SELECT * FROM V$VERSION WHERE ROWNUM = 1", String.class); // 3. 随手清理测试表,方便下次刷新页面继续测试 jdbcTemplate.execute("DROP TABLE RW_TEST_ONLY_WRITE"); return "<div style='font-family: Arial; padding: 20px; line-height: 1.6;'>" + "<h2 style='color: green;'>✅ 纯写操作路由测试成功!</h2>" + "<b>执行了 CREATE 和 INSERT 语句。</b><br>" + "<b>当前连接被强制路由到的实例名:</b> <span style='color: red; font-size: 20px;'>" + instanceName + "</span>" + " <span style='color: #666;'>(预期应为主库实例)</span><br>" + "</div>"; } catch (Exception e) { return "<h2 style='color: red;'>❌ 测试失败</h2><br>错误信息: " + e.getMessage(); } } }

👉 预期结果:页面显示的实例名会变成 DW1_01(主库)。

在这里插入图片描述

九、切换验证

9.1 自动切换(DW_MODE=AUTO)

直接 Kill 主库进程,守护进程超过 INST_ERROR_TIME联系不上主库后触发切换流程,确认监视器收到两个守护进程的确认后,自动将 DW1_02 提升为新主库。

在这里插入图片描述

在监视器前台观察日志,能看到切换的完整过程。主库重启后,守护进程自动将其降级为备库,执行 show 确认角色变化。

在这里插入图片描述

在这里插入图片描述

9.2 手动切换(DW_MODE=MANUAL)

# 在监视器执行 login switchover GDW1.DW1_01 # 切换到指定实例

在这里插入图片描述

在这里插入图片描述


十、实时备库 vs 异步备库,到底差在哪?

维度 主库(PRIMARY) 实时备库 异步备库
角色声明 ALTER DATABASE PRIMARY ALTER DATABASE STANDBY ALTER DATABASE STANDBY
读写权限 可读可写 只读 只读
REDO 日志角色 产生 REDO 接收并重演 接收并重演
dmarch.ini LOCAL + REALTIME + ASYNC LOCAL + REALTIME(反向)+ ASYNC 只有 LOCAL
dmwatcher DW_TYPE GLOBAL GLOBAL LOCAL
TIMER_INI 1(开,负责推送) 1(开,切换后备用) 0(关,只接收)
参与切换决策 (可成为新主库) (不参与)
数据延迟 毫秒级 = 定时器间隔

十一、日志是怎么传过去的?MAL 机制

MAL(Message Access Layer)是达梦守护系统的底层通信框架,所有的日志传输、心跳检测、切换指令都跑在 MAL 上。

+------------------+                    +------------------+
| 主库进程          |   MAL_PORT(5336)   | 备库进程          |
| MAL 发送端        | -----------------> | MAL 接收端        |
|                  |    << REDO 日志 >>  |                  |
+------------------+                    +------------------+
        |                                       |
   MAL_DW_PORT(5436)                    MAL_DW_PORT(5436)
        |                                       |
        v                                       v
+------------------+   << 心跳+指令 >>  +------------------+
| 守护进程 dmwatcher| <---------------> | 守护进程 dmwatcher|
+------------------+                    +------------------+
        |                                       |
  MAL_INST_DW_PORT(5536)           MAL_INST_DW_PORT(5536)
        |                                       |
        v                                       v
  数据库实例                              数据库实例
(守护向实例发"切换"指令走这里)

十二、出了故障怎么处理?

备库故障与异常处理(核心原则:保护主库)

故障或变慢:无论备库是宕机,还是因网络/磁盘原因响应变慢,主库都会立刻将其归档状态置为无效(Invalid)并暂停同步,以防拖慢主库的业务处理。

主库故障处理

自动切换模式:监视器自动选举合适的备库接管。注意:对网络稳定性要求极高,网络抖动极易引发脑裂。

手动切换模式:系统不自动干预,需DBA通过监视器手动执行接管命令。

故障重启:原主库修复重启后,会根据日志(LSN)等记录,自动判定是继续做主库,还是降级为备库重新加入集群。

故障自动恢复机制(Recovery)

自动触发:备库重启或网络恢复后,主库守护进程会自动触发恢复流程,无需监视器或人工干预。

核心动作:主库向备库发送归档日志来同步历史数据。当有多个备库需要恢复时,系统支持动态并行恢复,大幅提升效率。

恢复完成:数据同步一致后,备库归档恢复为有效状态,主库短暂切为 Suspend 后立刻切回 Open,集群恢复正常。

场景及方案

场景 推荐方案 原因
同城双机房(延迟 ≤10ms) 实时主备 低延迟
异地容灾(北京→广州,延迟 ≥50ms) 异步备库 跨城延迟高,实时同步会拖慢主库
金融核心系统 实时主备 + 确认监视器 业务要求
报表/分析只读场景 异步备库开放只读 可接受数据轻微滞后,保护主库
升级演练/变更测试 在异步备库上折腾 不影响生产
评论
后发表回复

作者

文章

阅读量

获赞

扫一扫
联系客服