1. 理解witness它到底在集群里扮演什么角色做PostgreSQL高可用repmgr是我用得最多的一套方案。它轻量、贴近PG生态和系统的pg_ctl、流复制原生配合得很好。但repmgr的自动failover有一个被很多人低估的前提集群里的“票数”要够。两节点主备集群在只有两台机器时一旦网络闪断备库会认为主库挂了主库可能也觉得被孤立了两边各自想当家。这时如果没有第三个角色从中间做仲裁自动failover不但不能救你反而可能把集群带进脑裂。repmgr提供的witness节点就是解决这个投票权问题的关键角色。这篇文章我会从witness的职责讲起把主备节点的基础配置、witness注册、自动failover实操演练、脑裂防护原理、故障恢复流程全部走一遍最后附上我在实际部署中踩过的坑和问题排查表。内容适合正在用repmgr做高可用、但还没真正用过witness的人也适合已经把witness配起来了但没想清楚它为什么能防脑裂的运维同学。1.1 没有witness之前两节点集群的投票困境先还原一个典型场景。集群里只有主节点node1和备节点node2repmgr开启了failoverautomatic。正常情况下node2通过流复制从node1拉取WALrepmgr通过心跳监控node1的状态。某天早上机房网络出现抖动node1和node2之间的心跳包连续丢失。repmgr在node2上检测到主节点不可达按照自动failover的逻辑开始尝试提升node2为新主。这个过程本身没有问题问题是node1其实还活着只是它和node2之间的网络被切断了。于是node1继续对外提供服务node2也提升了自己开始接受写入。两个主节点同时存在对客户端来说就是一场灾难写入可能分到两边数据在两边各自增长之后无论怎么合并都收不回来。这就是脑裂。在两节点体系里没有第三方的“裁判”任何检测都只是单方面的判断。备库说主库死了没有别的证据来验证这个判断对不对。很多人觉得加了自动failover就万事大吉可真正把两节点方案用到生产环境的人多半都遇到过类似的左右互搏。要打破这个僵局就必须引入一个不参与数据复制、只负责“表决”的角色这就是witness。1.2 witness是怎么打破僵局的witness的中文含义是“见证人”在repmgr集群里它的职责就是观测和投票。它不保存业务数据不参与WAL复制也不承担客户端连接它只做三件事持续监测主节点和备节点的可达性维护集群拓扑信息在主节点故障发生时参与候选节点的选举。CPU、内存、磁盘的消耗都很小一台1核1G的小机器就够了但它必须和主备处于同一个可控网络内并且对两个节点都有独立的网络路径。witness节点本身没有太多高可用要求因为它的核心价值是独立性与仲裁再冗余也要避免和主库或备库放在同一台物理机上。在自动failover的选举中repmgr会收集所有节点的连接状态。正常情况下备节点如果要提升自己需要获得多数票加入了witness之后集群的总票数变成三份主、备、witness各占一票。当主备之间网络中断时备节点如果仍然能连上witness而主节点失去了和witness的联络那么备节点就拿到了2比1的多数可以名正言顺地提升。反过来如果主节点能联系到witness而备节点不能那么集群会认为备节点才是异常的那一方不触发提升。这个“少数服从多数”的规则就是witness防止脑裂的核心价值。1.3 witness节点和Standby的区别别混为一谈把witness理解成“一个不存数据的备库”是常见的误解。Standby节点是用流复制同步数据的它随时可以接管写入是业务数据的完整副本witness节点只是注册在repmgr元数据里的一个观察点它身上没有PG实例也不需要配置流复制更没法直接升级为主节点。repmgr在witness上运行的是一个监听进程它会同步集群信息但不会跟随WAL数据增长也不接收业务流量。换句话说witness提供的是“信息”Standby提供的是“能力”两者职责完全不同。在部署资源上witness独立于PG数据目录不需要初始化PG实例只需要安装repmgr和对应的libpq依赖即可。这正是它省钱、轻量的原因也因此把它部署在独立的低配虚机上非常合适。不过要注意repmgr的witness功能对repmgr版本有要求不同大版本之间注册命令和参数略有差异配置前最好先确认版本。2. 搭建前的环境规划与repmgr基础配置witness再好用也得放在一套正确配置的repmgr集群里才有效。如果主备节点的repmgr基础配置都还没调好直接去注册witness结果大概率是集群状态显示witness离线甚至failover根本不触发。所以这一部分先把环境规划和基础配置完整过一遍再谈witness注册。2.1 节点规划与版本选择我就按自己常用的生产环境规格来举例。三台机器分别命名为node1、node2、witness1。实际分配如下节点角色IP地址规格建议是否安装PGnode1主节点10.0.0.112核4G起是node2备节点10.0.0.122核4G起与主同规格是witness1见证节点10.0.0.131核1G即可否PG版本建议选当前仍在支持期的大版本比如15或16repmgr则建议用当前活跃的大版本比如repmgr 5.x。主备节点的repmgr版本必须完全一致witness节点也建议保持一致否则注册过程中可能出现元数据格式不兼容的情况。操作系统层面没有强制要求只要repmgr能安装运行就行我平时在Linux环境居多直接用包管理安装很省事。需要提醒的是不同repmgr大版本对PG版本的兼容范围不同安装前先看官方文档确认版本兼容矩阵。2.2 repmgr.conf核心参数逐条说清楚repmgr的配置集中在repmgr.conf里主备两个节点的配置不同点只有node_id、node_name和conninfo里的host。其余参数保持一致。我贴一份主节点示例node_id1 node_namenode1 conninfohost10.0.0.11 userrepmgr dbnamerepmgr port5432 data_directory/var/lib/postgresql/15/main use_replication_slotsfalse reconnect_attempts6 reconnect_interval10 log_file/var/log/postgresql/repmgr.log log_levelINFO monitoring_historyyes failoverautomatic promote_command/usr/local/bin/repmgr_promote.sh follow_command/usr/local/bin/repmgr_follow.sh逐条解释一下。node_id是集群内唯一数字标识主备各不相同node_name是节点别名最好和主机名对应conninfo是repmgr连接目标库的连接串user和dbname必须是repmgr元数据库及其专用用户data_directory指向PG数据目录。use_replication_slots设置为false是很多生产环境的共同选择因为槽位在某些异常场景下会阻塞WAL清理备库长时间离线时主库的WAL会一直堆积用文件复制再校验的方式会更省心但如果你能接受槽位带来的校验便利也可以保持默认true具体看团队的运维习惯。reconnect_attempts和reconnect_interval控制主节点不可达后的重连尝试次数和间隔这个组合决定了repmgr多久判定主库“真挂了”。比如6次间隔10秒大约1分钟内有连续心跳失败就触发故障评估。参数给得太短容易误判给得太长则业务恢复慢需要结合监控告警阈值调整。failoverautomatic是自动failover的总开关只有设成automaticrepmgr才会在主节点故障时主动提升备节点。promote_command和follow_command是提升和重新跟随时要调用的外部脚本脚本内容因环境而异但必须配置不配置的话自动提升动作无法完成。monitoring_historyyes会让repmgr把监控历史写入元数据库排查问题时非常有用。2.3 主节点与备节点的初始化流程配置写好后第一步是创建repmgr元数据库用户和数据库。用超级用户执行CREATE USER repmgr WITH SUPERUSER LOGIN PASSWORD your_password; CREATE DATABASE repmgr OWNER repmgr;repmgr在初始化时需要对节点做大量管理操作必须使用超级用户角色这一点和很多运维工具不同别图省事只给普通权限。然后编辑pg_hba.conf允许三个节点的repmgr用户互相访问host all repmgr 10.0.0.0/24 md5接下来在主节点注册repmgr -f /etc/repmgr/15/repmgr.conf primary register看到类似“successfully registered”的输出说明主节点已经进入repmgr的集群拓扑。接着在备节点上克隆主节点数据。首次克隆会清空备节点的data_directory然后通过流复制协议从主节点拉取全量数据repmgr -h 10.0.0.11 -U repmgr -d repmgr -f /etc/repmgr/15/repmgr.conf standby clone repmgr -f /etc/repmgr/15/repmgr.conf standby register克隆完成后在任意节点执行repmgr cluster show正常会看到一主一备两行记录状态分别是primary和standby。到这里基础集群已经就绪。这一步不完成witness也没法正确感知拓扑所以不要跳过。2.4 配置自动failover必须开的参数自动failover不是简单设个failoverautomatic就结束的。前面的配置里还有几个容易漏掉的参数这里强调一下。promote_command脚本必须能被repmgr进程用户执行脚本内部执行pg_ctl promote或者repmgr standby promote。我的脚本一般是这样#!/bin/bash repmgr -f /etc/repmgr/15/repmgr.conf standby promote -vfollow_command脚本则负责让老主节点在新主节点恢复后重新作为备库跟随内部执行repmgr node rejoin。两个脚本都放到/usr/local/bin目录下并给足执行权限。不要小看这两个脚本如果只配了failoverautomatic而promote_command为空故障发生时repmgr会判定配置不完整自动提升直接失败。另外还要确认repmgr用户在所有节点的pg_hba.conf里都有互访权限因为failover时repmgr需要在备节点上执行升级命令而这个备节点可能无法访问原主节点访问权限不足时提升动作会卡住。配置好之后先在正常状态下用repmgr cluster show确认节点状态再考虑注册witness这样后面排错会简单很多。3. witness注册与自动failover完整实操基础集群就绪后开始做witness的注册、自动failover的演练和故障恢复。这一环节是整个方案的核心我建议在生产环境启用前无论如何都要完整演练一遍用演练结果校验参数设置和脚本是否真的可行。3.1 在witness节点上安装与注册witness节点上不需要初始化PG数据目录但必须安装repmgr及其依赖。在Linux上直接用包管理器安装repmgr即可。安装完成后创建witness自己的repmgr.conf。注意这个配置和普通节点的配置有差异。我在witness节点上的配置长这样node_id3 node_namewitness1 conninfohost10.0.0.13 userrepmgr dbnamerepmgr port5432 data_directory/var/lib/postgresql/15/main log_file/var/log/postgresql/repmgr-witness.log log_levelINFO witness_srvnodenode1node_id取一个与主备都不重复的数字node_name用witness1conninfo里的host写witness自己的地址data_directory可以随意指定一个空目录因为witness不会真正写PG数据。witness_srvnode用于指定witness要关联的节点名一般指向主节点这样witness才能知道自己的“参照物”是谁。这个参数在不同repmgr版本里写法不完全一样配置前先repmgr --version确认版本再对照官方文档。之后执行注册命令repmgr -f /etc/repmgr/15/repmgr.conf witness register看到witness registered的提示后回到任意节点执行repmgr -f /etc/repmgr/15/repmgr.conf cluster show这时候集群拓扑里应该出现三行记录witness1的状态是witness。到这里witness就算接进来了。需要注意的是witness注册后不会自动启动监控进程注册只完成元数据登记真正持续监控主备状态的是每个节点上运行的repmgrd守护进程。witness节点上也需要启动repmgrd否则它只是登记在案不干活。启动命令是repmgrd -f /etc/repmgr/15/repmgr.conf start3.2 触发一次真实的failover演练注册完witness马上做一次故障模拟。我做演练时不会直接在生产库上乱停都是先在测试环境确认无误后再考虑真实环境。模拟主节点故障的方式有很多最彻底的是直接停掉主节点的PG实例pg_ctlcluster 15 main stop或者直接pg_ctl -D /var/lib/postgresql/15/main stop -m fast停止主库后观察备节点上的repmgr日志通常是/var/log/postgresql/repmgr.log。正常流程是这样的repmgrd先是连续多次连接主节点失败等到重连次数用尽进入故障确认阶段接着witness节点被拉入仲裁repmgr判断备节点和witness之间网络正常最终执行promote_command提升node2。整个过程中日志里会出现类似这样的关键内容[INFO] connection to primary node node1 lost [INFO] unable to reconnect to primary node after 6 attempts [INFO] promoting standby node2 to primary [INFO] node node2 promoted to primary日志确认完成后用repmgr cluster show再检查一遍集群状态node2应该变成primarynode1显示为failedwitness1状态正常。到这里一次自动failover已经真实完成了。演练中我特别关注的是节点1还在运行时node2是否成功获得了多数的支持——这也是witness存在的意义主库虽然停了但witness还在备库能连接到witness所以提升动作不会因为得不到多数票而中止。3.3 故障恢复原主如何重新加入集群自动failover只是第一步更考验运维水平的是故障恢复。原主node1恢复后不能直接启动PG并让它继续当主必须让它重新作为备库加入新主node2的集群。repmgr提供了node rejoin命令结合pg_rewind可以快速重建原主节点repmgr -f /etc/repmgr/15/repmgr.conf node rejoin --force-rewind -d host10.0.0.12 userrepmgr dbnamerepmgr port5432前提是原主节点在故障前开启了wal_log_hints或设置了checksum否则pg_rewind无法使用。如果没开这两个选项就只能用standby clone重新全量克隆。rejoin成功后原主节点会自动注册为standby并通过流复制追赶新主的WAL。这一步完成后再执行repmgr cluster show理想状态是看到node1和node2两行都是ready状态node1作为standby跟在node2后面。接下来不要忘记把witness的witness_srvnode或者相关配置检查一下若关联到旧主可能需要重新调整确保下一次failover时witness还能正常参与仲裁。很多人在演练后忽略了这一步结果第二次故障来临时witness根本不在拓扑里自动failover瞬间失效。4. 自动failover背后的决策逻辑与脑裂防护手动跑一遍演练只是看到了现象。下面把repmgr自动failover背后的决策逻辑拆开你会更清楚witness在哪个环节起作用也更能理解为什么网络分区场景下witness的价值尤其明显。4.1 repmgr的监控与选举逻辑repmgr的自动failover由repmgrd守护进程驱动。repmgrd在每个节点上运行周期性检测主节点是否可达。检测方式通过connection_check_type参数控制常见的有ping检测和真实连接检测两种。只做ping检测速度快但可能误判——因为ICMP和数据库连接的可达性不一定一致真实连接检测更准确代价是每次检测都会发起一次数据库连接。生产环境我倾向用connection_check_typeconnection配合适当的reconnect_attempts和reconnect_interval让误判概率降到最低。确认主节点不可达后repmgrd不会立刻提升备节点还需要走一个选举过程。repmgr会对所有候选standby做排序排序依据主要看三个维度备节点的LSN位置是否最新、备节点与当前主节点的断开时长、备节点能否与witness正常通信。数据最接近主节点、且能被多数节点认可的备节点会成为新主。这里“认可”二字的含义在有两节点的集群里很暧昧而有了witness参与后它变成了一个明确可计算的多数票规则这也是repmgr官方推荐使用witness的原因之一。4.2 脑裂场景推演三种网络分区情况用witness做仲裁后网络分区不再是简单的“主备互不可见”而会分化成三种情况。第一种备节点和witness之间的网络中断主节点正常。这种情况下备节点虽然自己觉得主库不可达但它连不上witness拿不到多数票repmgr不会发起提升。集群保持主节点继续服务相当安全。第二种主节点和witness之间的网络中断备节点正常。此时备节点能连上witness而主节点失去与witness的联系。当备节点发现主库不可达时它会和witness建立一个共识主节点失联并且witness也认可这一点。备节点拿到多数支持顺利提升旧主即使还活着也无法获得半数以上票数不能继续以主身份服务。第三种主备之间中断但witness和两边都通。这是最微妙的情况。备节点能连witness主节点也能连witness但两边互相不能通信。此时repmgr会结合LSN位置、监控历史、节点角色做综合判断一般来说如果备节点的数据足够新且能连接witness它会被提升但极端情况下如果主节点仍然在提供服务而客户端流量全部打到主节点那么备节点提升后可能造成短暂的双写窗口。这也是为什么生产环境里通常会把witness放在一个与主备两边都保持独立链路的网络区域并通过脚本在failover时及时摘除旧主的VIP从访问入口把风险兜住。4.3 事件通知与外部组件联动witness解决了仲裁问题却没有能力帮“业务入口”自动切换。repmgr本身不管理VIP不更新DNS也不操作负载均衡器。这些外部动作都要通过event_notification_command参数挂接到repmgr的事件通知机制里。repmgr在节点状态变化、提升完成、节点加入等关键事件发生时会调用你指定的脚本把事件类型、节点名、当前状态通过参数传进去。我会写一个事件脚本在promote事件发生时把VIP从旧主切换到新主并调用健康检查接口确认新主可写。脚本本身不用很复杂关键是把事件类型判断和切换动作写清楚#!/bin/bash EVENT_TYPE$1 NODE_NAME$2 NODE_STATUS$3 case $EVENT_TYPE in promote) /usr/local/bin/switch_vip.sh to ${NODE_NAME} ;; follow) /usr/local/bin/switch_vip.sh to ${NODE_NAME} ;; esac在这个框架下witness的作用就不只是防止脑裂了它还参与形成一套完整的“检测-仲裁-提升-入口切换”链路。witness的心跳状态也能作为独立监控项喂给告警系统让运维在witness失联时提前介入。5. 常见问题速查与经验心得最后把我在witness方案落地过程中反复踩过的问题整理成速查表再聊聊自己的一些体会。这些问题看似零散但任何一个处理不好都可能让自动failover在关键时刻掉链子。5.1 高频问题排查清单现象可能原因排查/处理办法witness注册后cluster show看不到witness上的repmgrd没启动或repmgr版本不一致启动repmgrd检查版本一致自动failover未触发repmgr.conf里failover未设置automatic或promote_command为空确认failoverautomatic脚本可执行提升动作卡住日志提示权限不足repmgr用户没有超级用户权限或pg_hba.conf未放行用超级用户角色放行集群网段备节点提升后旧主还在提供写入无VIP切换或未安装witness脑裂未防住配置事件通知脚本切换VIP确认witness在线node rejoin时报pg_rewind错误未开启wal_log_hints或checksum开启后重启或改用standby clonewitness频繁离线告警witness与主备网络路径不稳定或资源过低检查网络、升级节点规格日志出现“unable to connect to witness”witness防火墙未开放对应端口或conninfo配置错误检查5432端口连通性核对conninfo排查时先分清是网络问题、配置问题还是权限问题。我的经验是先看repmgr日志再看cluster show的输出最后才去翻操作系统日志这三步走下来绝大多数问题都能定位。5.2 实操中踩过的坑第一个坑是witness和主库放在同一台机器。刚开始做测试的时候为了省一台机器我把witness直接注册在主节点上。结果模拟主备网络断开时主节点和witness从未真正失去连接仲裁机制形同虚设witness只剩“形式上的存在”。要记住witness的价值就是独立第三方节点和主备任何一方共宿都会破坏它的仲裁能力。第二个坑是rejoin之后忘记重新确认witness的关联关系。有一次演练做完node2成了新主node1作为备库回归我没动witness配置认为witness的srvnode指向node1也没关系。结果第二次故障测试时witness一直试图连接旧主node1而node1已经不再是主节点仲裁信息完全错乱failover成了碰运气。后来我养成一个习惯每一次角色切换之后都主动重新执行一次witness相关的检查必要时重新注册绝不让它带着旧拓扑继续工作。第三个坑和版本有关。repmgr 4和repmgr 5的witness配置差异不小我参考网上的教程时混着看注册完才发现witness_srvnode参数在旧版本里根本不支持。后来我固定用一个版本所有机器统一安装文档也只认官方文档少走了很多弯路。建议你在动手前先把repmgr --version打出来查清对应版本的手册再操作。5.3 一点个人使用体会跑了这么多年PG高可用我对witness的判断是它不是一个华丽的功能却是一个把自动failover从“听起来能用”变成“真的敢用”的基础设施。两节点集群可以省成本但省掉witness风险太高三节点可以不用witness但多数情况备节点成本不低。所以用了repmgr之后我一直坚持在两节点方案里带一个独立低配的witness节点代价几乎可以忽略换来的却是一个能说明白的仲裁机制。实际运维时我也会把自动failover当成一个“最低保障”而不是“万能药”。它解决的是主节点故障时的快速恢复问题但数据丢失的可能仍然存在尤其是流复制采用异步模式时主节点故障前未同步的WAL会丢。所以我会同时保留常规备份和PITR机制定时检查备节点的延迟把witness方案当作整个容灾体系里的一个重要构成而不是所有问题的答案。另外建议每个新手在真正上线前把failover演练至少做两次一次在测试环境里主动停主库完整走一遍提升和恢复一次在灰度环境里模拟网络分区观察witness的仲裁是否按预期工作。只有这两次演练都顺利通过我心里才有底把自动failover放心地用进生产。