1. 项目概述
1.1 为什么选择达梦读写分离集群
这里先交代一下背景。项目上线初期只有一台达梦数据库服务器,业务量小的时候一切正常,但运行半年后,随着业务终端接入数量增长,报表查询、统计汇总、日常业务读写全部压在同一台实例上,主库CPU经常飙到80%以上,慢查询开始拖累在线业务的响应速度。最要命的是,写事务和大型查询之间的锁竞争越来越明显,业务高峰时段偶尔出现“明明已经提交成功,但应用端反馈要等几秒”的情况。
最初想过简单粗暴地加硬件,但数据库的瓶颈往往是单实例架构问题,换再好的机器也只是把天花板抬高一点,治标不治本。所以这次直接考虑达梦读写分离集群。所谓读写分离,核心思路就是把事务型读写操作集中到主库节点,把只读的查询、报表、统计类负载分流到从库节点,在不改变应用代码逻辑的前提下,让主库专注处理写入,从库分担读取压力。对于以读多写少为主、查询量持续上升的业务场景,这种架构性价比很高,实施周期也相对可控。
1.2 这套方案适合谁、能解决什么问题
如果你正面临下面这些情况,达梦读写分离集群大概率值得认真评估:
- 系统读请求远超写请求,单库CPU和IO已经出现明显瓶颈
- 报表、BI、统计类查询频繁执行全表扫描或大范围扫描,拖慢核心交易响应
- 需要提升数据库整体的可用性,要求主库故障后具备自动切换能力
- 希望在数据库层解决读写负载分担,而不是先改一版应用读改写分离代码
当然,它不适合所有场景。如果业务本身写入量极大、事务吞吐要求极高,读写分离并不会减少写入压力,主库依然是瓶颈,这时候应该考虑分布式架构或专业的分库分表方案。
1.3 整体方案架构
先说清楚最终部署的拓扑结构,方便后面阅读时始终有全局概念。本次环境规划如下:
| 节点角色 | 节点名称(虚构) | IP规划 | 说明 |
|---|---|---|---|
| 主库节点 | DMSRV01 | 192.168.56.101 | 对外提供读写服务,以写为主 |
| 从库节点1 | DMSRV02 | 192.168.56.102 | 只读实例,承担查询负载 |
| 从库节点2 | DMSRV03 | 192.168.56.103 | 只读实例,承担报表与分析负载 |
| 确认监控节点 | DMMON01 | 192.168.56.104 | 集群状态监控与故障处置 |
三个数据节点之间通过主从复制机制保持数据一致,其中主库实时将Redo日志传输到从库并应用,从库的数据延迟控制在秒级甚至毫秒级。应用端通过JDBC连接串配置读写分离数据源,写操作自动路由到主库,读操作按负载均衡策略分发到从库。确认监控节点负责心跳检测、状态判定和对主库故障的自动切换决策。
这套架构里,最关键的两个设计点:一是日志传输的完整性如何保证,二是应用透明访问时读写路由如何实现。后面的章节会逐个展开。
2. 部署前的核心规划
2.1 环境与版本选型
达梦数据库版本众多,读写分离集群属于企业级功能,社区版或试用版不一定内置,选型前务必先确认许可证支持范围。不同大版本的集群配置命令、监控视图都有差异。顺手提个建议,部署前把数据库版本统一,拿select * from v$version;核对。
本次部署的操作系统是银河麒麟V10(ARM架构),数据库版本为达梦8。老实说在BIOS层面注意的点不少,ARM环境下个别参数默认值和X86平台并不一致,比如MEMORY_TARGET的默认策略、页面大小PAGE_SIZE的取值等。如果沿用之前X86的部署经验,初始化实例时极有可能出现性能参数水土不服。因此前期规划阶段就要明确平台架构,所有参数以当前平台实测值为准。
网络层面,三个数据节点之间要求万兆或千兆专网互通,延迟最好稳定在1ms内。这套集群的日志实时传输对网络抖动敏感,如果中间隔了防火墙、跨了三层路由,就很容易出现主从延迟波动。
2.2 初始化参数规划
部署集群前,手动创建实例时就要把关键参数想清楚。这里列出一份实践中校验过的基础配置(假设数据目录为/dmdata,端口使用默认的5236):
[dmdba@node1 ~]$ dminit PATH=/dmdata PAGE_SIZE=8 EXTENT_SIZE=16 CASE_SENSITIVE=1 CHARSET=1 LOG_SIZE=2048 SYSDBA_PWD=自定义强密码 SYSAUDITOR_PWD=自定义强密码几个参数的解释值得展开一下。PAGE_SIZE决定数据库页大小,选8KB比较通用,适合混合OLTP与OLAP负载;如果业务全是海量分析型查询,可以选16KB甚至32KB,但一旦初始化后无法修改。CASE_SENSITIVE是标识符大小写敏感开关,业务系统若在别的数据库上开发过,迁移前必须确认原库的设置,否则后续所有表名、字段名在SQL中都要加双引号,能把人折磨疯。CHARSET=1表示UTF-8字符集,选它基本不会出错。
内存参数的调整建议在实例初始化完成后,通过系统函数动态设置,方便对比效果:
alter system set BUFFER_POOL_SIZE = 2048; alter system set MAX_BUFFER_POOL_SIZE = 4096;这里要注意一个常见误区:BUFFER_POOL_SIZE不是越大越好。在一次压测中我把缓冲池从2GB调到8GB,结果操作系统物理内存不足,产生了大量内存换页,性能反而下降了三成。达梦的内存结构分为共享池、缓冲区、日志缓冲区等多个部分,总内存占用必须服从操作系统可用内存总量约束,别只顾一个参数。
2.3 归档配置
读写分离集群的数据同步机制中,归档日志是核心媒介。主库的Redo日志不间断地写入联机日志文件,同时归档线程将日志切换到归档目录。从库通过读取主库的归档日志来恢复数据。因此,主库必须开启归档模式,并且归档目录要有足够的空间、独立的IO通道。
alter database mount; alter database archivelog; alter database add archivelog 'type=local dest=/dmdata/arch'; alter database open;归档目录建议与数据文件、联机日志分别放在不同的物理磁盘上。这样做的好处有两层:一是分散IO压力,避免日志写入和数据文件写入抢磁盘;二是防止单个磁盘故障同时毁掉数据和日志,把恢复的基础给端掉。曾经遇到过一台机器数据盘和归档盘在同一块盘上,结果坏道导致日志和归档一起丢失,主库被迫全量重建,教训很深刻。
归档空间的大小估算,按每日日志量的4到5倍预留比较稳妥。比如高峰期一天产生50GB归档,就预留200GB以上。主库如果归档空间写满,数据库会挂起不提供服务,这是不少生产事故的根源。明明只是日志满了,业务却完全停摆,这个问题在达梦上出现过不止一次。
3. 主从复制配置全流程
3.1 数据库实例基础配置
在配置集群之前,首先保证三个节点上各自有一份能正常启动的数据库实例。注意一点,从库的实例并不是多余的空库,它们需要和主库拥有完全一致的结构。最保险的做法是先通过DTS工具把主库的业务数据和用户对象迁移到从库,保证初始数据一致,然后再开启复制功能。反过来,如果从库是全新初始化的实例,一旦启动日志同步后,任何结构不匹配都会导致复制中断。
这里还要特别强调用户和权限的同步。很多人在做主从复制时只同步了业务表数据,漏掉了业务用户、存储过程、自定义类型、同义词以及相关权限,结果是从库上应用切过去之后查询直接报“表或视图不存在”或“权限不足”。虽然达梦有整库备份恢复可以解决多数问题,但实践中仍建议在开启同步前,人工核对一次DBA_USERS、DBA_TAB_PRIVS等系统视图。
启动实例之后,逐个节点设置实例名。实例名在整个集群中必须唯一,否则监控节点无法区分不同成员。实例名的设置位置在dm.ini中,对应参数为INSTANCE_NAME。
3.2 配置主库的即时归档与日志发送
进入主库的配置环节。达梦读写分离集群的复制基础是一组名为“即时归档”的配置项。它的作用就是告诉主库:每写一份联机日志,立刻把日志数据传输到指定的从库节点,而不是等归档线程慢慢写文件再传。
主库dm.ini中需要明确以下几个关键项:
ARCH_INST1_ARCH_TYPE = REMOTE ARCH_INST1_ARCH_DEST = DMSRV02 ARCH_INST1_ARCH_PORT = 5237 ARCH_INST1_ARCH_IS_REALTIME = 1这段配置表示:主库节点会实时将日志发送到名为DMSRV02的节点,端口为5237。如果你有多个从库,则继续追加ARCH_INST2_ARCH_TYPE等参数。注意从库上的日志接收端口(对应DMSERVER端口)应该保持一致,简化运维心智负担。
在达梦中,这套即时归档的生效是依赖dmarch.ini配置文件的。配置要点是保证[ARCHIVE]段的ARCH_REALTIME参数为1,并且在dm.ini中打开对应ARCH_INST关联。配置完成后,重启主库实例,然后通过系统视图验证日志发送是否正常:
select * from v$arch_status; select * from v$dm_arch_status;如果看到ARCH_TYPE显示REMOTE、VALID状态为VALID,说明主库侧已经和从库建立了日志传输通道。这里有个常见的坑:初次配置完后,主库日志传输状态很顺利,但一重启操作系统,主库起不来了,提示归档路径无效。原因多半是dmarch.ini里的ARCH_DEST写的是主机名,而系统重启后主机名解析失败。建议统一用IP地址,或者确保/etc/hosts的映射稳定可靠。
3.3 从库的恢复与同步状态校验
从库侧要做的工作相对简单,但顺序不能错。先把从库实例启动到MOUNT状态,然后通过达梦的恢复工具,用主库的历史备份进行恢复:
rman target / RMAN> restore database from '/backup/primary_full_20250101.dmp'; RMAN> recover database; RMAN> alter database open;恢复完成后,从库进入正常运行状态。此时主库的即时归档会把后续的Redo日志数据持续发送到从库,从库的MAL系统负责接收、校验并应用日志。验证主从是否同步成功,可以从主库侧发起一个对比查询:在两边的同一张业务表上分别执行count统计,观察结果是否一致。
如果数据量过大,count整表会消耗不少资源。更轻量级的方法是在主库执行一条插入或更新语句,随后立刻到从库查看该记录是否已经出现,延迟通常也就是几秒钟。有一次我连续测试了多张表都没发现延迟,心里还暗自庆幸,实际上是因为监控SQL恰好命中了缓存。建议测试时加select /*+NO_CACHE*/提示,或者针对一张从未被访问过的测试表操作,这样得到的结果才可信。
4. 确认监控节点与自动故障切换
4.1 确认监控机制的原理
达梦读写分离集群的可用性保障,依赖独立的确认监控节点(DMMON01)。这个节点平时不承载业务数据,专职干两件事:监控集群各节点的健康状态,并在主库失去响应时触发故障切换流程。
它的工作机制,简单说就是对监控对象循环发送心跳探测。主库、从库都会在固定周期内向监控节点上报自身状态,一旦监控节点在超时时间内未收到主库的心跳,就触发切换仲裁。仲裁时会结合其他节点的反馈,判断主库是“真死”还是“假死”。避免出现主库只是短暂空闲但进程仍存活、却被误判为故障的情况。
监控节点的配置是在dmmonitor.ini文件中完成的。核心配置示例:
MON_INST_IP = 192.168.56.101 MON_INST_PORT = 5236 MON_DW_CONFIRM = 1 MON_LOG_PATH = /dmdata/monitor_log MON_LOG_INTERVAL = 60MON_DW_CONFIRM这个参数需要特别解释一下。它表示监控节点是否为“确认模式”。在确认模式下,一旦检测到主库故障,监控节点可直接选择新的主库并通知其他节点切换;非确认模式下,监控节点只提供状态展示,不做自动切换动作。生产中务必设为确认模式,否则故障发生后,集群处于“看得到问题但不处理”的尴尬状态,高可用形同虚设。
4.2 配置自动切换流程
配置自动切换前,先要理解达梦集群的“主库状态机”。整个集群中同一时刻只有一个主库,其他节点均为从库。当主库故障被监控节点确认后,监控节点会从剩余从库中选择数据最完整、日志序列号最接近主库的节点,提升为新主库。其余从库自动将日志源切换到新主库,保证整个集群继续正常工作。
开启自动切换时,dm.ini中的两个参数值得关注:
ALTER_MODE_STATUS = 1 ENABLE_DW_RAFT = 1ALTER_MODE_STATUS=1允许节点状态手工或自动调整;ENABLE_DW_RAFT则启用了类似多数派选举的仲裁机制。启用RAFT模式后,切换过程不再完全依赖单一监控节点,而是依赖集群内多数节点的共识来决定谁是新的主库。这种机制能更可靠地防止网络分区时产生“双主”局面。简单理解:没有RAFT时,如果主库和监控节点之间的网络断了,监控节点可能会误判主库故障;而多数派共识机制可以更好地处理这类网络分区问题。
有一点必须反复提醒:自动切换的触发是有条件的,并不是监控节点一发现主库不通就立即切换。达梦内部设有连续失败次数、超时时间等多项判定参数,默认情况下要等若干个心跳周期都失败才触发。这个参数在实践中要根据业务容忍度调整。核心交易系统往往希望5秒内完成切换,那就把心跳间隔调小、判定次数调低;如果业务可以接受30秒的闪断,建议把阈值适当放宽,减少因网络瞬时抖动造成的误切换。
4.3 手动切换与故障演练
高可用架构如果没做过故障演练,那和没有高可用差不多。上线后,我组织了一次完整的主库宕机演练,流程如下:
- 登入主库,执行
checkpoint;让数据尽量刷盘,记录当前日志序列号 - 通过
kill -9强制杀掉主库进程,模拟最恶劣的宕机场景 - 观察确认监控节点的日志,记录故障发现时间与切换发起时间
- 查看应用连接状态,确认读写分离数据源是否自动指向新主库
- 对新主库执行关键业务单条SQL验证读写功能,对从库执行查询验证只读功能
演练结果中有一个细节值得记录:集群在主库进程被杀后约8秒完成了新主选举和从库重新同步,但应用端的数据库连接池没有在第一时间感知到主库地址变化,部分连接仍然指向旧地址,导致间歇性连接失败。解决方法是修改应用侧的连接串,允许数据源地址在故障切换后动态刷新,同时设置连接池的无效连接回收时间,调低到20秒以内。
关于手动切换,生产环境中偶尔需要主动进行主备倒换,比如主库所在物理机需要升级硬件。达梦提供了ALTER DATABASE PRIMARY和ALTER DATABASE STANDBY两条指令,可以安全执行。手动切换前务必先检查从库的日志应用延迟是否为零,否则可能丢失最后的几笔事务。在实际操作中,我会先执行:
select * from v$arch_status where arch_type='REMOTE' and valid='VALID';确认两端日志完全同步后再执行切换指令,避免数据不一致。
5. 应用侧读写分离配置
5.1 JDBC数据源配置
集群部署完成后,应用侧能否按预期将读写操作分开,很大程度上取决于数据源的配置。达梦官方JDBC驱动完整支持读写分离数据源,建议使用dm.jdbc.driver.DmDataSource配合事务类型判断实现读写路由。
DmDataSource dataSource = new DmDataSource(); dataSource.setUrl("jdbc:dm://192.168.56.101:5236?compatibleMode=oracle"); dataSource.setUser("appuser"); dataSource.setPassword("password"); dataSource.setReadOnly(false);这个配置本身很简单,但它的读写分离能力有限。因为这种原生数据源不做SQL级的路由,它只会根据连接是否处于只读状态决定发往主库还是从库。真正的读写分离,通常在DAO层或通过中间件、代理层完成。比如在应用中使用AbstractRoutingDataSource,根据当前事务的是否只读切换目标数据源。
一个更实用的做法是:在应用层定义两套独立的数据源。一个指向主库的写库实例,一个指向负载均衡后的从库读实例。业务代码中,对强一致性要求的读取(例如登录后立即读取自己的资料)也走主库;对弱一致性的报表类查询走从库。等应用积累了足够经验,再把路由粒度从接口级扩展到事务级。
5.2 连接池参数调整
连接池配置和读写分离配合得好不好,效果天差地别。如果业务有大量短连接请求,且所有连接都打到主库,那么读写分离配置得再好也是空转。因此连接池的defaultReadOnly按场景进行区分,例如:
spring: datasource: primary: hikari: read-only: false readonly: hikari: read-only: true以HikariCP为例,read-only=true的连接会被HikariCP标记为只读,驱动层可能会对这类连接关闭部分缓存与锁机制,提升查询性能。同时建议将只读数据源的最大连接数设置得比写库大一些,因为从库承担的查询并发量本来就更高。
连接池的maxLifetime和idleTimeout也要注意配比。达梦服务端默认的会话空闲超时是60分钟,如果连接池的maxLifetime大于这个值,空闲连接会被服务端主动断开,但连接池不知道,下一次请求时就会报“连接已失效”,触发重连,白白增加延迟。我在实践中把HikariCP的maxLifetime调成55分钟,idleTimeout为10分钟,有效避免了连接被服务端清理后的无效请求。
5.3 从库延迟对业务的影响
读写分离架构下最棘手的问题,是从库数据延迟。即便是最理想的实时同步,从库的数据也会比主库晚那么一瞬。这种延迟在大部分读取场景下可以接受,但在需要读后即写、写后即读的强一致场景下,可能会产生严重问题。
举一个真实事故:业务上线后,用户反馈偶尔在提交订单后刷新列表,新订单不出现。排查方向排查方向最初以为是缓存失效,后来定位到是读写分离后,用户提交订单写到了主库,紧接着刷新列表的查询却路由到了从库,而从库还没来得及应用主库的那笔事务,结果新订单查不到。这类问题的本质是路由策略没有考虑数据一致性等级。
解决方式有三种,按复杂度递增排列:
- 对强一致读取强制走主库,具体到查询方法上标记为强制主库路由
- 在从库读取前增加“延迟容忍窗口”,超过窗口仍读不到就走主库
- 引入全局事务ID绑定,同一事务或同一会话内,写后读都路由到主库
第三种方案最优雅,但对应用改造要求高。目前项目里采用的是第一种方案的变体:在事务注解上增加@Transactional的属性,将瞬时读操作(1秒内必须看到最新数据)显式标注为readOnly=false,强制走主库;其余长期报表类查询继续走从库。
6. 常见故障与排查实录
6.1 主从状态不一致
现象:主库和从库的表数据对不上,从库上查询结果与主库存在差异,但v$arch_status显示日志传输正常。
排查过程:首先确认主库的归档日志是否存在缺失。曾遇到过归档日志目录写入权限的问题,主库已成功传输了某段日志,但从库在恢复时因为归档文件权限不足,静默跳过了一部分记录。从库的dm_arch_status可能依然显示正常,因为状态标志更新的是“已接收”,而不是“已应用”。
解决方法:检查从库数据目录和归档目录的所有者是否为dmdba,权限是否包含写和执行权限。如果确认无误,直接使用主库最新备份重建从库,不要试图把差异数据手工补回来,手工核对差异数据在实际操作中极易遗漏。
6.2 监控节点误切换
现象:在一个风雨交加的夜晚,监控节点报告主库故障并自动切换,但切换后业务恢复正常,事后检查主库进程还活着。
排查过程:持续的网络抖动导致监控节点与主库之间的心跳超过了超时阈值。主库本身未宕机,但监控节点因无法收到心跳,误判主库异常并触发切换。这种问题网络分区、监控节点与主库间的链路抖动、主库短暂CPU峰值等都可能引发。
解决方法:一是调高监控的超时判定次数。二是为监控节点做冗余,部署两个监控节点,通过RAFT机制形成多数派意见。三是给主库所在服务器足够的CPU余量,避免高峰期CPU长时间接近100%导致心跳处理不及时。
6.3 从库归档空间不足
现象:部署两周后,从库的归档目录被打满,从库日志应用停滞,主从延迟持续增大,但主库本身无明显异常。
排查过程:检查从库dmarch.ini中的归档空间配额和数据库日志轮转策略。分析后发现,从库的归档目录里积累了大量不必要的历史归档文件。默认情况下达梦不会自动清理过期归档,除非配置了清除策略。
解决方法:为从库开启自动清理归档功能,具体在dmarch.ini中增加归档文件保留天数参数,例如保留7天。同时写一个定时脚本,每天凌晨压缩并转移7天前的归档到备份存储。归档清理务必保留足够的历史时长,否则出现主库磁盘损坏时,想依靠从库数据追回丢失事务,会发现早期日志已经没了。
6.4 连接池连接耗尽
现象:业务高峰时应用日志频繁出现“等待获取连接超时”。
排查过程:这个报错是典型的连接池连接耗尽。读写分离后,从库的连接池被报表类请求占满,而正常业务查询也经常走从库,导致从库连接池被大量长时间报表查询占据。进一步分析慢SQL,发现有一条跨多张表的关联查询每次执行超过40秒。
解决方法:优化慢SQL,将大查询放到独立的只读报表库,与应用业务库分离。同时为只读数据源配置等待获取连接的超时时间,并启用连接泄漏检测,避免未被正确归还的连接长期占用池子。
6.5 网络瞬断导致日志应用追不齐
现象:主从节点间的网络设备升级,物理链路中断数十秒,恢复后从库一直处于“追赶”状态。
排查过程:网络恢复后,主库的即时归档通道重新建立,但中间几十秒的日志在主库侧已经轮转,从库的实时日志通道无法补齐缺口。
解决方法:达梦对这种场景有内置的日志自动续传机制,主库检测到从库的日志断层后,会从归档目录重新发送缺失区间。但如果归档目录中相关日志已被清理,就只能全量重建从库。因此归档保留策略不能只考虑存储空间,还需考虑网络中断补偿恢复的时间窗。
7. 关键参数速查与验证指标
7.1 配置清单速查
下面这份表是我在完成多个达梦读写分离项目后沉淀的检查清单。部署完成后逐项对一遍,能大幅降低遗漏概率。
| 检查项 | 期望结果 | 命令/方法 |
|---|---|---|
| 归档模式 | 已启用 | select arch_mode from v$database; |
| 即时归档状态 | VALID | select * from v$arch_status; |
| 主从节点实例名 | 唯一且匹配配置 | select instance_name from v$instance; |
| 监控节点确认模式 | 已确认 | show monitor info; |
| 从库日志应用延迟 | 小于5秒 | select * from v$recover_status; |
| 主从两端表数据一致 | 一致 | 随机抽样表对比count与checksum |
v$recover_status这个视图能直接看到每个从库的日志序列号与主库的差异,如果长期不为0,就要注意了。运维上可以建一个定时任务,每分钟采样一次主从日志序列号差值,超过阈值就告警。
7.2 性能验证思路
读写分离是否真正有效,不能只看配置完没完事,必须做压测。我采用的压测方案是模拟业务实际流量特征:70%的查询、20%的更新、10%的插入,持续压测30分钟。
压测前记录主库的CPU使用率、IO等待时间和连接数基线。压测过程中重点观察主库CPU是否明显下降、从库CPU是否平稳抬升、主从延迟是否随并发量的增长而线性恶化。一个健康的读写分离集群,压测结束后主库CPU应该比单库部署时明显降低,从库的利用率增加,且主从延迟不会出现跳跃式增长。
如果压测发现主库CPU降不下来,首先排查SQL是否真的路由到了从库。直接在从库上开启SQL日志,执行一条压测SQL,如果从库日志中能看到该SQL,但主库没有,说明路由正确;如果两边都能看到,那就说明连接池配置没有生效,应用仍在用旧数据源。这比盲目调大缓冲区大小要有效得多。
8. 项目总结与个人体会
回头整理这次部署,我认为最容易被忽视的,不是哪条命令、哪个参数,而是业务侧的读写路由策略。数据库层的集群架构只是提供了“能力”,如何把业务的读流量导到从库,才是读写分离价值能否兑现的关键。建议在项目启动初期,就让业务开发团队深入参与路由策略设计,梳理清楚哪些查询可以容忍秒级延迟,哪些查询必须强一致。这个梳理工作越早做,后面改造的成本越低。
另一个体会是,故障演练不能省。读写分离集群比单库多了监控节点、日志传输、自动切换等多个故障点,任何一个环节出问题都可能造成业务闪断。上线后定期做一次切换演练,把主库、从库、监控节点分别模拟宕机一遍,才能对集群的韧性有真实把握。
最后再分享一个实用习惯:每次集群参数调整后,第一时间把dm.ini、dmarch.ini、dmmonitor.ini的变更记录同步到配置管理库中,标注变更时间、变更内容和变更原因。达梦集群的排查过程中,八成的问题靠“对比正常与异常时的配置差异”就能定位。保留一份完整的历史配置变更记录,能让你在故障发生时少走大量弯路。