操作 PostgreSQL 高可用时,repmgr 是很多团队的首选工具。但围绕它有一个场景经常被一笔带过:两节点集群加一个 witness 节点,然后在计划维护窗口里做一次 switchover。我见过不少同行把 failover 跑得很溜,遇到 witness 注册和 switchover 联动时却容易漏掉细节。这篇文章就把这两件事讲透,从为什么要加 witness、怎么注册,到 switchover 各阶段发生了什么、坑在哪,适合正在使用或准备用 repmgr 管理 PostgreSQL 主备切换的 DBA、运维和平台工程师直接对照执行。
1. Witness 节点:两节点集群的“仲裁票”
1.1 两节点集群的脑裂问题
先想一个最简单的场景:一主一备,两个节点。正常情况下主库承担读写,备库接收 WAL 并保持热备。可一旦主备之间的网络出现分区,或者主库短暂失联,节点之间就失去了明确的“谁说了算”的信号。这时备库如果自作主张提升为主库,而原主库其实还活着,两边就会同时接受写入,脑裂就发生了。
脑裂的代价不是“多一个主库进程”这么简单,而是两边的 WAL 历史会分叉,数据会不一致。等网络恢复,你没法简单地把它们重新拼回去,只能靠日志和备份做非常痛苦的手工对账。真正的生产事故里,这类问题往往不是发生在硬件故障时,而是发生在网络抖动、交换机切换、防火墙误伤这种“主库其实没死”的场景中。
witness 节点就是用来解决这个问题的。它是一个独立于主备之外的轻量 PostgreSQL 实例,作用是提供一票仲裁:当主备失联时,备库只有在能联系到 witness 的情况下才被允许提升。这样一来,单点网络分区就不会随便触发双主;而如果主库真的彻底挂了,备库还能拿到 witness 的确认,正常接管。
1.2 Witness 节点到底是什么
很多刚接触 repmgr 的人以为 witness 是一台“空数据库实例”或者一个配置文件,实际上它是一个完整的、需要正常启动的 PostgreSQL 实例。它不像普通的 standby 那样去拉主库的 WAL,也不承载业务数据,但它要运行 repmgr 扩展,保存 repmgr 的元数据,用来参与集群状态的仲裁判断。
资源上它不需要和生产主库同规格。我常用的配置是 1 核 2GB 内存、20GB 磁盘的小虚拟机,足够跑 repmgr 的元数据和心跳检测。它不应该承担备份、慢查询分析或者其他业务压力,否则它自己挂掉反而会影响高可用判断,这属于典型的“好心办坏事”。
1.3 什么情况下不需要 Witness
不是所有集群都需要加 witness。如果你的集群已经有三个或更多数据节点,节点之间天然有大多数投票机制,再加 witness 反而增加维护成本。或者你们用的是外部仲裁机制,比如基于 etcd / Consul 的选主脚本,也不需要 repmgr 的 witness 参与。只有那种“一主一备、但希望自动化 failover 更稳”的两节点集群,witness 才是性价比最高的方案。
注意:witness 提供的是仲裁能力,不是数据冗余。它不解决“主库磁盘坏了”这类问题,别指望拿 witness 当备份。
2. 添加 Witness 前的准备与初始化
2.1 环境准备清单
注册 witness 之前,有几件事必须确认,否则后面命令会连环报错:
- PostgreSQL 大版本和 repmgr 版本在主备、witness 三台机器上保持一致。版本不一致时,repmgr 连接检查很容易失败。
- witness 节点上需要有可用的 repmgr 管理库和 repmgr 扩展。一般流程是创建超级用户 repmgr,创建数据库 repmgr,再在这个库里创建扩展。
- repmgr.conf 需要单独配置,node_name、conninfo、data_directory 要写好。witness 节点的 node_name 要和其它节点不冲突。
- pg_hba.conf 要放行 repmgr 用户从主备 witness 三个地址的连接。
- SSH 互信要提前配好。repmgr 切换和 rejoin 会通过 SSH 执行远程命令,这一步经常被忽略。
具体初始化命令大致是:
# 在 witness 节点执行 createuser -s repmgr createdb -O repmgr repmgr psql -d repmgr -c "CREATE EXTENSION repmgr;"这只是把基础环境搭好。真正把它“挂”进集群,要靠注册动作。
2.2 注册 Witness 的完整命令
注册 witness 时,执行命令所在的机器是 witness 节点,通过-h指向当前主库:
repmgr -f /etc/repmgr.conf witness register -h pg1 -d repmgr -U repmgr --force参数拆开看很清晰:
-f指定 repmgr.conf,里面已经有 node_name、conninfo 等基础信息。-h pg1告诉 witness 当前主库是 pg1,注册时会去主库里写一份节点记录。-d repmgr -U repmgr指定管理库和管理用户,和创建扩展时保持一致。--force是关键。如果 witness 节点本地已经存在旧的 repmgr 元数据,或者主库上的节点表里有残留记录,不加--force会直接报“node already registered”。
成功时终端会返回类似NOTICE: witness node "witness1" (ID: 3) successfully registered的信息。这里的 ID 是集群内自动分配的节点 ID,不要手工指定。
2.3 注册后的验证
注册只是第一步,关键是看集群是否真的认它。在任意节点执行:
repmgr -f /etc/repmgr.conf cluster show正常输出里应该能看到三行:pg1 是 primary,pg2 是 standby,witness1 是 witness。witness 行的 Status 列带* running,说明它与主库的连接是健康的。再执行repmgr cluster status,同样能看到WITNESS角色的节点。
验证期间最好再tail -f看一下 repmgr 的日志文件,确认没有连接失败、版本不匹配这类的错误信息。
2.4 初始化失败的常见原因
我实际踩过的坑里,出现频率最高的是这几个:
- 没有在 witness 节点初始化 repmgr 管理库和扩展,注册时直接报找不到扩展。
-h指向了 standby,而不是 primary,导致注册信息落到错误节点上。- pg_hba.conf 里没有加 witness 节点的 IP,报
could not connect to host。 - repmgr.conf 的
node_name和 conninfo 里的主机名对不上,导致 SSH 连接时用错地址。
提示:注册时尽量用 IP 或统一的主机名,别一半写别名一半写 IP。repmgr 对“同一个节点两个叫法”这种问题很敏感。
3. Switchover 原理与实操
3.1 先分清 Failover 和 Switchover
failover 是主库突然挂掉,备库自动提升;switchover 是人工计划内切换,把主库身份主动让给备库。两者的共同点是角色会发生交换,但 switchover 的目标是零数据丢失、可预测、可回退。repmgr 的standby switchover命令专门处理计划内切换。
这里要特别强调:repmgr 本身不做虚拟 IP 漂移,也不管理负载均衡器。它只管数据库节点角色切换。如果你依赖 VIP 或连接池,需要在切换后另外处理流量切换,这是 repmgr 之外的工作。
3.2 Switchover 内部发生了什么
repmgr 执行 switchover 时,大致会做这么几件事:
- 先做预检,确认目标 standby 可以升级:检查复制连接、WAL 接收状态、是否落后、是否有阻塞事务。
- 把原主库降级(demote),让它不再接受新的写入连接。
- 把目标 standby 提升(promote)为新主库。
- 更新集群元数据,让其它节点都认识到新主库是谁。
- 原主库重新以 standby 身份加入集群,如果需要还会借助 pg_rewind 回退不一致的 WAL。
整个过程不是一条命令背后“刷”一下就完成的,每一步都会有日志输出。理解这个流程,比背命令有用得多,因为排查问题时你能知道卡在哪一步。
3.3 执行前的检查清单
切换前我会习惯性过一遍这几个检查项,宁可多花两分钟:
repmgr -f /etc/repmgr.conf cluster show确认节点状态正常,没有节点处于 unreachable。repmgr -f /etc/repmgr.conf cluster status确认没有 pending failover 或异常角色。- 查看
pg_stat_replication,确认 standby 的 WAL 接收没有长时间中断。 - 检查数据库里有没有长时间未提交的事务,尤其是有没有持锁超过几分钟的会话。
- 如果集群里还有级联节点,需要确认它们都指向正确的上游节点。
- 确认 witness 在线。部分版本的 repmgr 在 switchover 预检时要求 witness 可访问,witness 挂了会直接中止切换。
3.4 实操命令与示例输出
switchover 执行前,先生成一份 dry-run 报告:
repmgr -f /etc/repmgr.conf standby switchover --dry-rundry-run 不会真正切换,只是模拟一遍预检。输出类似:
INFO: running command on node "pg2" (ID: 2) NOTICE: following usage of pg2 requires all witnesses to be accessible INFO: node "pg2" is a viable candidate INFO: all nodes are able to confirm consistency INFO: switchover would be successful看到switchover would be successful才可以继续。接着在准备变成新主库的 standby 节点上执行:
repmgr -f /etc/repmgr.conf standby switchover执行成功后,repmgr 会输出提升备库、重新挂载原主库的 NOTICE。这时再用repmgr cluster show查看,角色应该已经互换。
3.5 切完别急着走:收尾验证
切换成功不代表真的万事大吉。我会做四件事:
- 再看一次
repmgr cluster show,确认 pg2 是 primary,pg1 是 standby,witness 的 upstream 指向新主库。 - 在新主库上跑
pg_stat_replication,确认旧主库已经以 standby 身份重新连接上来。 - 确认时间线已经切换。新主库的 timeline 会比之前 +1,备库都在跟随新时间线。
- 从应用侧连一下新主库,确认连接串、密码、权限都没问题。
很多线上事故不是切换失败,而是切换成功了但应用还连着旧主库。VIP、DNS、连接池这些流量入口一定要提前准备好切换方案。
4. Witness 在 Switchover 前后的联动与手工校正
4.1 自动联动与版本差异
switchover 过程中,repmgr 会尝试把 witness 节点重新关联到新主库上。多数较新版本能自动完成这一步,切换后 witness 的 upstream 会指向新 primary。但版本差异确实存在,我在某些老版本上遇到过 switchover 已经成功、witness 还指着旧主库的情况。
判断方法很简单:repmgr cluster show里看 witness 行的 Upstream 列,如果它指向的已经不是当前 primary,说明联动没有完成。这时候 failover 仲裁是失效的,必须手工校正。
4.2 手工对齐 Witness 的命令
先把 witness 从集群元数据里摘掉:
repmgr -f /etc/repmgr.conf witness unregister --node-id=3再用新主库作为注册目标重新登记:
repmgr -f /etc/repmgr.conf witness register -h pg2 -d repmgr -U repmgr --force这里的--node-id要填 witness 当前的节点 ID,可以从 cluster show 输出里看到。重新注册后,再验证一遍cluster show和repmgr cluster status。
重要:如果 witness 和原主库之间网络不通、或者原主库已经被降级,直接 unregister 可能连接不上。这时可以先修通网络,或者用 repmgr 的清理工具强制修正元数据。不要在不了解后果的情况下随便删节点表记录。
4.3 如果 Witness 宕机,切换会怎么走
计划内 switchover 时 witness 宕机,preflight 阶段往往会直接报错,repmgr 会出于安全考虑拒绝切换。因为它无法确认仲裁节点可用,强行切换等于把集群推进一个没有保险的 failover 状态。
遇到这种情况,我的建议是先把 witness 恢复起来,再执行切换。切换本身通常只有一两分钟窗口,没必要为了赶时间绕过仲裁检查。如果 witness 确实短期无法恢复,至少要在操作记录里写明“切换后 failover 保护失效,需要尽快恢复 witness”。
5. 常见问题与排查速查表
5.1 常见错误速查
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| switchover: unable to contact witness | witness 宕机、网络不通、pg_hba 未放行 | 先恢复 witness,或临时跳过 preflight 并在事后补齐注册 |
could not connect to server | 端口、防火墙、conninfo 配错 | 检查 postgresql.conf、pg_hba.conf、监听地址 |
node already registered | 本地或主库有残留元数据 | 加--force重新注册,必要时先 unregister |
WAL rewind required | 新主库和旧主库 timeline 分叉 | 使用node rejoin --force-rewind做 pg_rewind |
| 切换后旧主库没出现在集群里 | 自动 rejoin 失败 | 手工在旧主库执行repmgr node rejoin |
| 应用还是连不上/连到旧主库 | VIP、DNS、连接池未切换 | 核对流量层配置,切换 VIP 或权重 |
| repmgr 日志显示角色不一致 | 元数据和实际进程状态脱节 | 使用repmgr cluster cleanup或重新注册异常节点 |
5.2 排查路线:从现象到日志
遇到异常,我的习惯是严格按顺序看三样东西:
repmgr -f /etc/repmgr.conf cluster show看整体状态,判断是哪个节点失联。repmgr -f /etc/repmgr.conf cluster status看详细节点信息,包括 upstream、是否 pending。- 去看 repmgr 日志,通常在
/var/log/postgresql/repmgr.log或/var/log/repmgr/repmgr.log,重点找ERROR和WARNING行。
很多人一上来就翻 PostgreSQL 主日志,方向就偏了。repmgr 的元数据和它的连接状态,只有 repmgr 自己的日志最清楚。抓这三样基本能定位八成问题。
5.3 一次切换后老主库无法重新加入的处置
switchover 最常翻车的后继问题,是旧主库无法重新以 standby 身份加入。原因是它当时还在接受写入或者 WAL 位置已经分叉。这时不要强行把它拉起来,否则旧主库的数据会污染整个集群。
正确做法是在旧主库上执行:
repmgr -f /etc/repmgr.conf node rejoin --force-rewind -v--force-rewind会让 repmgr 调用pg_rewind,把旧主库的数据往回同步到新主库的时间线。执行前一定要确认旧主库上没有未归档的、需要保留的新数据——pg_rewind 会丢掉旧主库上比新主库多出来的 WAL 记录,这是不可逆的。真有数据要救,先做物理备份再 rejoin。
6. 从零开始的演练记录:Witness + Switchover 完整跑一遍
6.1 模拟环境与初始状态
用一套虚构环境举例:PostgreSQL 15,repmgr 5.4,三个节点叫 pg1、pg2、witness1。pg1 是 primary,pg2 是 standby,witness1 是新加的 witness。
初始状态:
ID | Name | Role | Status | Upstream ----+----------+---------+-----------+---------- 1 | pg1 | primary | * running | 2 | pg2 | standby | running | pg16.2 添加 Witness 并验证
witness1 上按前面 2.1 的步骤初始化数据库和 repmgr 扩展,然后执行:
repmgr -f /etc/repmgr.conf witness register -h pg1 -d repmgr -U repmgr --force再回到 pg1 上查看集群状态:
ID | Name | Role | Status | Upstream ----+----------+---------+-----------+---------- 1 | pg1 | primary | * running | 2 | pg2 | standby | running | pg1 3 | witness1 | witness | * running | pg1看到 witness1 出现在集群里,upstream 指向 pg1,说明注册成功。
6.3 Switchover 执行全过程
先在 pg2 上跑 dry-run:
repmgr -f /etc/repmgr.conf standby switchover --dry-run确认没有问题后,执行正式切换:
repmgr -f /etc/repmgr.conf standby switchover输出里会出现 promote pg2、reattach pg1 的过程。切完后集群状态变为:
ID | Name | Role | Status | Upstream ----+----------+---------+-----------+---------- 1 | pg1 | standby | running | pg2 2 | pg2 | primary | * running | 3 | witness1 | witness | * running | pg2到这里,计划内切换已经完成,witness 也跟着新主库走。
6.4 演练后的遗留事项
我会在演练结束后固定检查四件事:
- 旧主库 pg1 是否真的处于 standby 状态并持续追 WAL。
- witness1 是否在新主库的元数据里,能正常参与后续 failover 仲裁。
- 应用连接池是否切到 pg2,旧连接是否全部断开重建。
- 如果有 VIP,确认 VIP 已经绑到 pg2 上。
这一步别省略。演练的价值就在于把“看起来成功”变成“真的可用”。
最后分享一点实战体会:这类操作最容易翻车的地方其实不是命令本身,而是时间窗口之外的细节——日志级别太低导致排错困难、VIP 漂移脚本没被验证、witness 没纳入监控。我自己的习惯是每次演练都把 repmgr 的日志级别调到 LOG,并在 switchover 前后各抓一份 cluster show 留档。这两份输出能帮你把一半的“灵异事件”变成看得见的问题。后续如果有机会,再把 keepalived 联动和 failover 的完整流程整理出来,也是一套很实用的组合拳。