
1. 这个拆分需求到底是怎么回事先说下我最近遇到的一个真实场景。某客户的一套业务系统跑在人大金仓数据库的集群架构上平时是主库加两个备库的标准配置跑了大半年一直挺稳。结果业务方突然提了个需求——要把其中一套测试环境从集群里拆出来变成单实例独立运行。理由也很实际测试环境的数据要频繁造数、备份恢复、跑各种压测脚本如果一直跟着生产集群走每一次全量备份、每一次扩容都会拖累主库的同步压力和磁盘空间而且测试库的权限管控和集群里的生产数据放在一起审计上也比较别扭。这种需求在数据库运维里其实一点都不罕见。很多人一听到“集群变单实例”就下意识觉得是退步、是降级但实际在工程场景里单体架构反而更适合某些特定负载。开发联调、压测环境、容灾演练的临时库、数据分发的中转库这些场景对“独立性”的要求远高于“高可用”单实例拆出去之后运维边界清楚备份策略各自独立连故障影响半径都小了很多。回到技术本身KingbaseES 的集群架构底层是基于流复制实现的主备同步外加集群管理组件做故障切换和仲裁。把一个备库节点从集群中干净利落地摘除、再把它转成可独立读写的单实例听起来只是“删个节点”的事但实际操作里涉及配置文件、同步状态、集群元数据、系统视图等一系列状态的一致性稍有不慎就会留下半集群半单机的僵尸节点重启后集群组件还会尝试把它拉回来甚至出现双主冲突。这篇文章我就把这个案例完整复盘一遍从拆分前的现状盘点开始到执行摘除的具体操作再到单实例化之后的验证和收尾把每一步的“为什么”也一并说清楚。如果你手头也有类似的 KingbaseES 集群要拆、要缩减节点或者只是想把集群架构里某套环境独立出来这篇内容应该能帮你少走不少弯路。2. 动手之前先得把这三张底牌确认清楚2.1 集群到底是怎么搭的拓扑和角色不能靠记忆拆分集群的第一步不是敲命令而是把当前架构完整捋一遍。很多运维事故都出在“我以为它是这样搭的”上面。我这次处理的环境是三节点架构node1 是主库Primarynode2 和 node3 都是备库Standby通过集群管理组件统一纳管。从业务角度说node2 是实时的热备节点node3 平时除了接收主库的 WAL 日志同步之外还承担一些只读查询的负载。测试环境用的数据就在 node3 上。这里有一个非常容易踩的坑备库上可能不只有同步过来的数据还可能有历史遗留的本地对象。我在拆分前检查 node3 的时候发现它上面有一个专门给测试同学用的业务账号以及几张手工创建的结果临时表。这些对象在主库上不存在属于备库本地写入的残留。在正常情况下备库处于 recovery 状态时是不能写入的但如果是早期手工搭建、或者从别处迁移过来的节点确实可能存在这种“半同步半本地”的状态。所以第一步一定是确认当前每个节点的角色是什么、同步关系是什么、集群管理组件的元数据里记录了几个节点。用一句话总结别拿“我记得”当依据直接查系统视图和管理工具的输出。# 查看数据库角色和同步状态 SELECT name, setting FROM sys_settings WHERE name IN (kingbase.force_parallel_mode, max_connections); # 查看当前节点是否是备库 SELECT pg_is_in_recovery();如果在备库上执行 pg_is_in_recovery() 返回 true说明它还在恢复模式下这个状态在拆分时要重点处理。2.2 该备份的备份该留的留安全网永远不嫌多任何架构调整操作第一步永远是确保数据安全。拆分备库这件事很多新手容易有一个错觉备库不是主库数据丢了也不影响生产。这个想法大错特错。备库上如果承载了只读业务、报表查询、数据抽取任务那它就是实际在对外提供服务的节点它的数据对于下游系统来说是“生产数据”。而且拆分之后这个节点要变成独立单实例对外提供读写服务如果拆分前它的数据就不完整那拆分后整个测试环境的数据基础就是错的。我在这次操作前做了一次全量备份用的是一致性备份方式。虽然 node3 是备库正常情况下通过流复制与主库保持同步但由于它有过本地写入的历史遗留我不能完全信任它的数据与主库完全一致。稳妥的做法是先做一次基础备份再通过比对主库和备库的关键表行数、校验位点信息确认数据差异在可接受范围内。这里分享一个经验备库的数据一致性校验不能只靠 pg_is_in_recovery() 这种状态判断必须同时看同步位点和同步状态。KingbaseES 的流复制里可以通过视图查看当前备库的接收位点received_lsn和重放位点replay_lsn这两个值跟主库的发送位点sent_lsn做对比差距越小说明数据越新。-- 在备库上查看接收和重放位点 SELECT pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn(); -- 在主库上查看发送位点 SELECT pg_current_wal_flush_lsn();如果备库的 replay_lsn 已经追上主库的 flush_lsn说明数据同步没有积压可以安全拆分。如果差距较大需要先观察一段时间确认是正常延迟还是同步故障。2.3 拆分后的路径归属单实例往哪放连接怎么改这个问题很多人会忽略但它直接决定拆分操作能不能顺利落地。node3 从集群摘除、转为单实例之后它的数据目录、配置文件、启动方式、连接端口都要重新定义。如果是同一个主机上的数据目录复用那比较简单原来的数据目录直接继续用就行。但要注意原目录里的配置文件中可能还残留集群组件写入的参数比如集群节点标识、仲裁通讯地址等这些必须一并清理。我是建议把配置文件恢复成标准单实例模板再按业务需要调整端口、监听地址、内存参数这些而不是在原配置上打补丁。如果是迁移到新主机那还要考虑数据目录搬迁、权限属主、防火墙端口等因素复杂度会高很多。我这次处理的情况比较简单node3 和 node1、node2 不在同一台物理机node3 拆出来后继续用原主机原目录只是把集群组件停掉、配置清理掉。还有一个要提前确认的点是拆出来的单实例要不要继续同步主库的数据如果测试环境需要持续从生产同步最新数据那其实是“备库转级联备库”的思路而不是真正的单实例化。真正的单实例化意味着它不再跟任何主库产生复制关系数据流完全独立。我这次拆分的目标是后者——测试环境完全独立后续通过备份恢复或者数据导出导入的方式从生产获取数据而不是靠实时同步。这一点必须在动手前跟业务方对齐否则拆完之后业务发现测试库数据不更新了又是一轮扯皮。3. 集群拆分最核心的一步如何干净地“摘除”节点3.1 停调度的正确姿势先让集群组件“忘记”这个节点集群组件的核心职责是监控节点状态、维护集群元数据、在异常时触发切换。所以拆分节点的第一步是先让集群组件不再把这个节点当作集群成员而不是先把数据库进程杀掉。顺序反了就会出问题。打个比方集群组件就像一个班主任三个学生分别是三个节点。你如果想让学生 C 转学正确的做法是先跟班主任说“C 要转走了你别再管他了”然后让 C 收拾东西离校。如果你直接让 C 偷偷跑了班主任点名发现少一个人就会启动“找人流程”也就是触发故障切换那就麻烦了。具体的操作路径是通过集群管理工具把 node3 从集群节点列表中剔除。这一步会同步更新集群元数据让剩余节点node1 和 node2不再把 node3 视为可用备库。操作完成后建议立即确认集群状态确保 node1 和 node2 之间的主备关系仍然健康。# 通过集群管理工具查看节点状态 ksql -U system -d testdb -c SELECT * FROM sys_stat_replication; # 查看集群组件管理的节点列表以实际环境为准 cluster_tool list node这里特别提醒一点剔除节点和停数据库进程之间最好留出一点时间间隔。我实际操作时是先剔除节点、观察集群状态正常再停掉 node3 上的数据库进程而不是两条命令连着敲。这么做的目的是避免集群组件还没完全更新元数据就发现备库进程消失误判为节点故障。3.2 清理流复制残留recovery 状态是单实例化的最大拦路虎节点从集群摘除之后它本身仍然处于“备库”状态。这时候它的数据目录里会有一个标识文件recovery.conf 或者 standby.signal只要这个文件在数据库启动后就会进入恢复模式持续尝试从主库获取 WAL 日志而不是作为一个独立的读写实例运行。我在实际环境里看到不止一个案例拆分时只做了集群组件剔除忘了处理这个标识文件。结果数据库重启之后日志里大量出现连接主库失败的报错节点一直卡在 recovery 状态业务连上去只能读不能写报错信息也极具迷惑性——看起来像是主库网络不通其实只是备库标识没清掉。单实例化的关键操作就是把主备复制的“身份标识”删掉让节点从“我是别人的备库”变成“我就是我自己”。在 KingbaseES 中如果使用的是 recovery.conf 方式需要重命名或删除该文件同时把配置文件中的 primary_conninfo 等复制相关参数注释掉。如果使用的是 standby.signal 方式直接删除该文件即可。# 切换到数据目录 cd /home/kingbase/data # 查找复制标识文件 ls -la | grep -E recovery|standby # 如果是 recovery.conf备份后移走 mv recovery.conf recovery.conf.bak # 如果是 standby.signal备份后删除 mv standby.signal standby.signal.bak做完这一步之后建议先不要急着启动数据库而是先检查配置文件里有没有残留的复制参数。常见的有 primary_conninfo、primary_slot_name、restore_command 这些如果还在要么注释掉要么改成单实例下需要的值。特别是 restore_command如果配置了从归档目录恢复单实例启动时可能会触发意外的归档恢复行为。3.3 这个节点挂了集群会怎样拆分前先想好“剩余拓扑”把 node3 拆掉之后集群就变成 node1 主库 node2 备库的两节点架构。这个架构能不能稳稳运行取决于集群组件是否支持两节点下的高可用以及你对“脑裂”风险的容忍度。KingbaseES 的集群在偶数节点下仲裁投票容易出现平票问题。如果集群组件需要多数派投票才能完成故障切换那两节点架构在主库故障时可能出现仲裁失败、备库无法自动提升的情况。所以拆分前要跟业务方确认拆掉 node3 之后node2 继续当备库高可用能力是否还需要如果 node2 也故障了是允许人工介入恢复还是需要自动切换集群组件的仲裁机制是否支持两节点模式通常需要引入投票节点才能凑成奇数我这次处理时业务方的诉求是测试环境彻底独立生产环境的 node1 node2 继续组成两节点高可用集群并且同意引入一个仅参与投票的 witness 节点来避免脑裂问题。这个 witness 节点不承载业务数据只参与仲裁投票。这也算是一个经验集群缩减节点前一定要把“剩余拓扑能不能自洽”想清楚而不是先把节点拆了再想高可用怎么补。如果业务方对剩余集群的高可用要求不变那通常需要额外引入一个轻量仲裁节点否则两节点的集群在主库故障时容易陷入僵局。4. 单实例启动之后这三个细节决定成败4.1 启动参数和系统视图的“变身”验证清理完复制标识、调整完配置之后就可以启动数据库了。但这里的“启动”不是简单地执行 systemctl start 或者 pg_ctl start 就完事而是要带着验证目标去启动。我第一次做这类操作时犯过一个错误启动数据库后只看进程在不在、端口通不通就认为成功了结果过了几个小时业务反馈写入报错一查才发现节点仍然处于只读模式。所以现在我的习惯是启动完之后立即执行一组“变身验证”命令确认节点已经从备库角色切换为主库角色。-- 验证是否处于恢复模式 SELECT pg_is_in_recovery();这个查询如果返回 false说明节点已经不再从外部接收 WAL 流进入了独立读写模式。如果返回 true说明 recovery 标识没清理干净或者配置里还有其他复制参数在生效。接下来还要确认复制槽、复制连接的残留情况。如果原配置里定义了持久化复制槽单实例化之后这些槽通常已经没有意义了但残留的复制槽可能占用 WAL 保留空间导致磁盘增长异常。建议检查一下把不需要的槽清理掉。-- 查看残留复制槽 SELECT * FROM pg_replication_slots;对于不再使用的复制槽可以手动删除SELECT pg_drop_replication_slot(slot_name);这一步很多教程不会提但在实际运维中非常重要。我之前遇到过拆分后磁盘空间莫名其妙增长的情况排查了半天才发现是残留复制槽导致 WAL 文件无法清理。4.2 清理集群元数据和监控残留别让它在“半集群”状态下裸奔节点从集群摘除后集群管理组件的元数据里虽然已经没有这个节点了但主机上可能还残留着集群组件的服务进程、监控 agent、日志轮转任务。如果这些残留进程还在运行它们会反复尝试连接集群管理端产生无意义的报错日志甚至干扰数据库端口的正常监听。我这次处理的机器上就发现了一个残留的集群监控进程在数据库启动后还在定时探测数据库的健康状态。由于集群端已经不把这个节点视为成员监控探测的结果一直是异常这个进程就不断重试、不断写日志把磁盘空间一点点吃掉了。正确的做法是停掉并禁用与集群组件相关的服务只保留数据库本身的进程。可以通过操作系统的服务管理工具来设置开机不启动。# 停掉集群管理相关服务以实际服务名为准 systemctl stop kingbasecluster.service systemctl disable kingbasecluster.service这里有一个细节倒值得说一说很多人直接 disable 之后就不管了但我建议做这一步之前先看清楚这个服务跟数据库进程是不是同一个。如果误把数据库服务本身 disable 了重启机器之后数据库起不来那就尴尬了。稳妥的做法是先看服务状态再决定停哪些。我在实际操作时的顺序是先通过服务管理工具列出所有跟数据库、集群相关的服务确认哪些是数据库主进程、哪些是集群管理组件再分别处理。数据库服务保留集群管理服务停用。4.3 内存参数是否要跟着调整备库变单实例别忘了资源的重新分配备库角色时有些内存参数可能是按照“尽量少占用资源”的思路配置的比如 shared_buffers、work_mem、max_connections 等。变成单实例之后这个节点要独立承担读写业务资源配比也要跟着调。我这次拆分的 node3 原来承载的只是只读查询max_connections 配置得比较低。业务方确认测试环境后续会跑一些并发造数脚本我就把 max_connections 调大了一些同时把 shared_buffers 从原来的 2GB 调到了 8GB——当然这个值是根据机器总内存算的不是拍脑袋定的。这里分享一个简单的计算参考shared_buffers 一般设置为机器物理内存的 25% 左右比较稳妥work_mem 则要看实际查询复杂度来调不能一味加大否则并发一高反而撑爆内存。调整完之后不要忘记重启数据库让参数生效并且要观察启动后的内存占用情况。# 查看当前内存参数 psql -U system -d testdb -c SHOW shared_buffers; psql -U system -d testdb -c SHOW work_mem; psql -U system -d testdb -c SHOW max_connections;修改配置文件后需要重启数据库进程。重启之后用系统工具确认内存占用在合理范围内不要出现启动即 OOM 的情况。5. 拆完不是终点这轮验证做了才算真正收工5.1 写读验证不只要连得上还要真的能写入单实例化之后最基本也最重要的验证是“这个库能正常读写”。很多人做完架构变更后只测试了 SELECT以为能查询就万事大吉结果业务跑起来才发现写入报错。这种问题在备库转单实例的场景里尤其常见——如果 recovery 标识清理得不彻底数据库仍然处于只读模式SELECT 正常但 INSERT 和 UPDATE 会报错。我的验证习惯是两步走。第一步是确认角色状态也就是前面说的 pg_is_in_recovery() 为 false。第二步是实际建一张临时表插入几条测试数据再查询出来然后删除。这一套流程走完读写的权限、表空间、事务日志写入都验证过了才算真正放心。-- 建一张临时验证表 CREATE TABLE test_verify (id int, note text); INSERT INTO test_verify VALUES (1, single instance write test); SELECT * FROM test_verify; DROP TABLE test_verify;如果这几条语句都能顺利执行那基本可以认定单实例的读写链路是完好的。这个验证过程还有一个附加价值验证业务账号是否有建表、写入的权限免得后续业务方接入时才发现权限没有同步过来。5.2 连接和端口检查旧配置里的坑往往藏在“重启之后”拆分操作完成、数据库正常启动后还需要从“外部视角”检查一下这个实例的连接链路。因为备库时代它的监听端口、监听地址可能是为集群内部通讯配置的单实例化之后客户端接入方式可能已经不同了。具体检查项包括监听端口是否还是预期的端口比如默认的 54321 或其他自定义端口监听地址是否覆盖了业务需要访问的网卡防火墙是否放行了新客户端所在网段的访问认证配置文件pg_hba.conf里是否有对应网段的访问规则。我这次拆分时遇到一个情况原来备库的监听地址只配置了内网 IP测试同学从办公网跳板机连不上去一开始以为是数据库没起来后来发现是监听地址没放开。这种问题在拆分场景里特别容易发生因为备库时代的网络权限和单实例时代的网络权限往往不一样。还有一个容易忽视的点是应用侧的连接串。如果应用原来是通过集群组件的虚拟 IP 连接数据库的拆分之后虚拟 IP 会从集群组件里移除应用还连着旧地址就会报连接失败。这个需要提前跟业务方对齐确保拆完之后的连接串及时更新。5.3 备份策略重新定义独立实例要有独立的安全感node3 变成单实例之后它不再享受集群层面的备份保护机制也不再跟主库的备份任务绑在一起。如果还沿用原来的备份策略这个独立实例很可能会变成“备份真空区”出了问题只能干瞪眼。我这次拆分后的操作是在独立实例上单独配置了备份计划每天凌晨做一次全量备份同时打开了归档日志确保任意时间点的数据都能恢复到近期状态。备份目录跟原集群的备份目录分开避免混在一起造成恢复时的混乱。这里也顺便提醒一句拆分后的单实例如果业务重要级别不低一定要配置至少一个周期性的恢复演练。备份不是目的能恢复才是目的。单实例没有备库可以依赖备份几乎是唯一的救命稻草。5.4 监测面打通没有集群守护就要有更细的监控兜底原来在集群架构里集群组件会负责节点健康检查、自动切换、状态上报。拆成单实例之后这些守护机制都不存在了。如果没有额外的监控手段兜底数据库挂了可能半小时都没人发现业务反馈了才知道。我在拆分完成后做了一件事把 node3 的数据库状态、磁盘空间、复制状态虽然已经是单实例但仍保留一些通用的监控指标、关键日志告警都接入到了统一的监控面板上。这样即使它脱离了集群的守护运维团队依然能第一时间感知到它的异常。监控的指标不用太多但至少要覆盖进程存活状态、端口连通性、磁盘空间使用率、数据库日志中的 ERROR 级别报错、CPU 和内存使用率。这些指标能覆盖绝大多数单实例运行时的风险场景。6. 如果哪一步出了岔子可以用这个顺序回滚虽然拆分操作本身不难但再简单的操作也有出问题的可能。在这个案例里我给自己留了一条清晰的回滚路径虽然最终没有用上但准备的过程让我执行拆分时心里踏实很多。回滚的逻辑很简单如果拆分后的单实例出现问题需要恢复它在集群中的角色那核心就是把之前清理掉的标识和配置再恢复回来。前提是之前备份了 recovery.conf 或 standby.signal以及记录了原始的主库连接信息。具体的回滚步骤大致是先停掉单实例数据库进程把之前备份的 recovery.conf 或 standby.signal 恢复回去恢复配置文件中 primary_conninfo 等复制参数确认集群管理组件已经把该节点重新纳入管理如果是误拆分需要重新加回需要通过集群工具完成节点添加启动数据库进程观察它是否重新进入恢复模式并开始从主库同步数据。这里要特别说明一点回滚只适用于原来的数据目录还保留、没有做大规模结构变更的情况。如果拆分单实例之后已经跑了很久的业务数据回滚就不再是简单恢复标识文件的事了而是要重新设计数据合并方案那已经属于另一个层面的运维课题了。7. 写在最后一次拆分操作暴露的是整个运维习惯这个案例做完之后我自己复盘了一遍发现这次拆分真正困难的不是“敲哪条命令”而是前面那些看似琐碎的确认工作。拓扑对不对、数据有没有差异、集群组件会不会误判、业务方对高可用的要求有没有变——这些问题任何一个没想清楚都可能在后半夜变成一场救火行动。我个人这几年做数据库运维最大的体会是架构变更操作再怎么小心都不为过。尤其是像“去掉一个节点”这种看起来是减法的工作实际影响的是整个集群的可用性模型和运维边界。拆分前多花半小时确认现状拆分后多花半小时验证和收尾远比出了故障之后花两小时紧急恢复要划算得多。最后再分享一个小技巧做这类操作前把关键的配置文件、系统视图输出都留个截图或者拷贝存档。别偷懒觉得自己记得住。出了问题时这些存档就是排查的“案发现场”能帮你快速定位是哪一步出了问题、哪一条配置被遗漏了。这次拆分结束后我也把整个操作过程中的所有命令、输出、验证结果整理成了一页纸的操作清单下次再遇到类似的拆分需求直接照着清单走安全又省心。