
做数据库运维的朋友十有八九都遇过这样的场景业务上线了PostgreSQL突然后半夜挂掉主从切换靠人肉VIP还飘错地方第二天被业务群艾特到怀疑人生。后来我把这套方案稳定跑了两三年PostgreSQL repmgr主从复制 keepalived故障转移三个组件配在一起实现了数据库层的主从自动切换和网络层的VIP漂移应用那边只要连VIP几乎感知不到底层节点挂了。这套东西不复杂也不需要多大内存两台普通服务器就能搭起来很适合中小业务和不想引入Patroni那一整套etcd体系的团队。这篇博文就是我完整搭建和踩坑的全过程从架构选型、环境准备、主从克隆、keepalived联动到故障模拟和问题速查都给你捋清楚。我尽量写得像实际操作中你会遇到的顺序先讲清楚为什么选这套组合再一步一步把配置落地。最后附上我在生产环境里踩过的几个大坑保你能少走弯路。1. 方案选型与整体架构1.1 为什么我选repmgr keepalived而不是Patroni先把我最常被问的问题回答了明明Patroni现在很火为什么我还在用repmgr keepalived原因很简单Patroni依赖etcd或ZooKeeper做分布式共识等于你除了两套数据库还要再维护至少三节点的高可用元数据集群。对于很多业务规模在几十万连接量级以内、预算有限、运维团队又不大的人来说这个复杂度是得不偿失的。repmgr本身专注在PostgreSQL复制管理上它负责跟踪主从节点的健康状态、维护复制关系、注册节点、以及自动提升从库为主库。keepalived只负责一件事让一个虚拟IP在节点之间平滑漂移。数据库层和应用访问层两边各管各的配合起来逻辑非常清晰。故障时repmgr把候选从库提升为主库keepalived检测到老主挂了就把VIP漂到新主上应用无感知。整条链路的组件数量少出问题时排查面小我觉得这比“全家桶”方案更适合绝大多数传统企业场景。当然Patroni有更细的调度策略和更好的防脑裂机制如果你的团队已经懂etcd且业务要求秒级自动恢复那选Patroni没毛病。但如果你只是想低成本解决“主库别挂了就半小时没服务”的问题用我这套组合足够。1.2 高可用架构到底长什么样这套集群至少需要两台物理机或虚拟机分别叫nodeA和nodeB。nodeA为主nodeB为从另外再规划一个VIP假设是192.168.1.100绑定在主库所在的节点网卡上。客户端连接串指向192.168.1.100不关心背后谁是真正的PostgreSQL。正常情况下nodeA监听VIPrepmgr复制流从nodeA流向nodeB主从数据时刻一致。一旦nodeA故障包括进程崩溃、断电、操作系统卡死等情况nodeB上的repmgrd会通过WAL流的中断感知到主库失联经过几次重试后自动执行promote把自身提升为新的primary。同时nodeA上的keepalived因为健康检查失败会撤销VIP占用并向外广播由于keepalived的VRRP协议nodeB上的keepalived会立刻接过VIP并将它绑定到自己的网卡上。整个过程通常在5~15秒内完成具体取决于你的健康检查间隔和repmgr的升级策略参数。需要提醒的是这套架构中PostgreSQL的复制类型默认是异步的。如果nodeA在崩溃瞬间还有一些WAL日志没有发送给nodeB那么切换后会丢失最后那一小段事务数据。避免这种丢数你可以在PG里开启同步复制把repmgr.conf里的replication设为synchronous但同步复制会增加主库事务延迟且如果备机挂掉主库会阻塞。考虑到“数据库可用性”和“数据不丢失”的权衡我一般建议默认异步但对实时性要求非常高的业务比如支付订单则必须同步复制并配合repmgr的witness节点来做仲裁。2. 环境准备与基础依赖2.1 服务器规划与系统配置先说硬件两台机器最低配置2核CPU、4GB内存磁盘建议SSD用来跑数据目录的盘至少40GB。我实际搭建用的是一台CentOS 7.9和一台Rocky Linux 9跨大版本混搭过也惊险跑住了但我不建议你学我最好是同一发行版、同一内核分支避免后期出现一些莫名其妙的兼容问题。生产环境更推荐CentOS 7.9以上的商业稳定分支或者直接用Rocky/AlmaLinux 9。主机名要提前规划好我直接用nodea和nodeb然后在两台机器的/etc/hosts里互相写入解析记录。比如192.168.1.11 nodea 192.168.1.12 nodeb这个hosts文件比DNS靠谱至少不依赖外部DNS服务。接下来关闭SELinux不然它可能拦截PostgreSQL的跨节点复制连接甚至让keepalived配置加载失败。改/etc/selinux/config把SELINUX设为disabled然后重启或执行setenforce 0。防火墙方面如果你用firewalld需要放行几个端口和协议PostgreSQL的5432端口keepalived的VRRP如果走多播则放行112协议单播则放行自己指定的UDP端口以及repmgr需要通过5432去连到对端所以只要5432放开即可。如果你们用iptables注意规则顺序和隐含的DROP策略别把回包丢弃了。系统层面还有几个建议。时间同步必须做流复制对时间偏差不算特别苛刻但主从时间差太大时某些WAL日志的时间戳会让你排查问题很痛苦。用chrony同步到公司内部NTP服务器就行。另外把vm.swappiness设为10减少Swap换页数据库性能更稳定。2.2 PostgreSQL版本选择与环境依赖看到热搜里有人在问“postgresql下载哪个版本”我的建议非常明确生产环境优先选择你所用大版本的最近小版本。比如目前在16.x系列里挑16.4以上的或者刚出的17.x如果还没经过足够长时间验证就别急上17。官方同时维护多个稳定版本你只要在官方yum仓库里就能直接拉取。热词里提到的“postgresql 16便携版”那是Windows环境搞便携包用的Linux环境不适用别去搜集成包直接走官方仓库最稳。安装依赖上如果你用源码编译需要readline-devel、zlib-devel、flex、bison和gcc。用RPM方式的话依赖会被自动处理好。我这里以PostgreSQL 16为例使用官方仓库方式安装在CentOS/Rocky上执行# 安装yum仓库CentOS 7/Rocky 9对应不同rpm注意替换 yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-$(rpm -E %{rhel})-x86_64/pgdg-redhat-repo-latest.noarch.rpm yum install -y postgresql16-server postgresql16-contribrepmgr不能用yum默认源装需要用PGDG的repmgr包它和PostgreSQL内部版本绑定。比如PG16对应repmgr5.4.x安装包名一般是repmgr16。2.3 网络层和防火墙策略这里单独提一下网络层很多人集群搭不起来的真实原因不在数据库反而在网络。PostgreSQL主从复制走的是5432端口你需要确保两台节点能通过TCP互相访问。用psql -h 192.168.1.11 -U repmgr -d postgres -c select 1来回测别急着用防火墙开放然后还报没权限。测通过之后我再把VIP子网划分确认一下。VIP的选择注意一点不要和任意一台服务器的真实IP在同一个网段里碰撞比如两台服务器都在/24那VIP可以选同一个网段的可用地址但必须确认没人使用。如果你有严格安全策略可以把VIP放到单独的/24或/27子网中性能上稍微有区别但能降低冲突概率。3. PostgreSQL主从安装与repmgr配置3.1 安装PostgreSQL并初始化实例主库和从库都得安装PostgreSQL二进制包但数据目录只需要在主库上做完整性初始化从库的数据会通过基础备份拉取过来。在主库上先初始化一个实例使用postgres用户执行sudo -u postgres /usr/pgsql-16/bin/initdb -D /var/lib/pgsql/16/data -E UTF8 --localeen_US.UTF-8initdb完成后修改postgresql.conf里和复制相关的几个核心参数。对我来说这一组是底线配置listen_addresses * port 5432 wal_level replica max_wal_senders 10 max_replication_slots 10 archive_mode on archive_command /bin/true hot_standby on wal_log_hints on shared_preload_libraries repmgrwal_levelreplica是必须的它允许WAL包含足够的信息供流复制使用。max_wal_senders至少要大于你要配置的从库数量这里留10个余量。max_replication_slots对应我们启用replication slot如果不清理从库断开连接后主库的WAL会被一直保留最后磁盘撑爆所以这个参数给了10个宁可多点也别少。archive_mode如果是on且archive_command为/bin/true仅表示归档成功但实际不归档这个做法可以让你后续想要物理备份时不用再重启实例。shared_preload_librariesrepmgr必须在两个节点提前设置不然repmgr扩展无法加载。再修改pg_hba.conf添加类似下面的规则把repmgr用户和流复制权限弄明确host replication all 192.168.1.0/24 scram-sha-256 host all all 192.168.1.0/24 scram-sha-256注意不能为了省事用trust否则在公网环境下一个psql就能把你表干光。设置好之后启动主库并创建专门给repmgr用的用户和库。3.2 通过repmgr注册主库并克隆从库repmgr的配置在两台节点上几乎一样只是node_id和conninfo里的地址不同。我的主库/etc/repmgr/16/repmgr.conf长这样node_id1 node_namenodea conninfohostnodea dbnamerepmgr userrepmgr passfile/var/lib/pgsql/.pgpass port5432 pg_bindir/usr/pgsql-16/bin replication_userrepmgr replication_slotsyes use_replication_slotsyes data_directory/var/lib/pgsql/16/data async_querytrue reconnect_attempts2 reconnect_interval2 failoverautomatic promote_command/usr/pgsql-16/bin/repmgr -f /etc/repmgr/16/repmgr.conf standby promote follow_command/usr/pgsql-16/bin/repmgr -f /etc/repmgr/16/repmgr.conf standby follow从库配置文件除了node_id2node_namenodebconninfo指向nodeb外其他基本一致。然后创建repmgr角色在master节点上执行sudo -u postgres psql -c CREATE USER repmgr SUPERUSER LOGIN PASSWORD repmgrpass; sudo -u postgres psql -c CREATE DATABASE repmgr OWNER repmgr; sudo -u postgres /usr/pgsql-16/bin/psql -d repmgr -c CREATE EXTENSION repmgr;使用超级用户创建repmgr扩展是为了它能管理复制槽和切换权限。随后在主库执行repmgr -f /etc/repmgr/16/repmgr.conf master register这时主库就被repmgr识别为enrolled。从库上先只启动一次PostgreSQL很多教程让你先“空启动”从库再克隆其实更稳妥的是在从库初始化一个空data目录后直接执行:sudo -u postgres /usr/pgsql-16/bin/repmgr -f /etc/repmgr/16/repmgr.conf standby clone -h nodea -U repmgr -d repmgr这个命令会以主库为源用pg_basebackup方式把主库整个数据目录拉取过来自动创建standby.signal文件之后从库就可以正常启动并进入standby模式。如果你不小心让从库先正常启动了一次后续克隆会因为data目录已有内容而失败这也是我建议“别急启动”的原因。克隆完成后启动从库的PostgreSQL然后注册从库sudo -u postgres /usr/pgsql-16/bin/repmgr -f /etc/repmgr/16/repmgr.conf standby register这时执行repmgr cluster show你能看到node1 primary、node2 standby的清晰状态。记得在两台机器上配置~/.pgpass避免repmgr连接时每次都提示输密码直接在脚本和守护进程里无感连接。3.3 配置repmgrd并开启自动故障转移repmgrd是repmgr的守护进程它负责监控复制链路如果主库挂了它会让从库自动promote。需要单独启用systemd服务CentOS/Rocky上一般叫repmgr16。修改repmgr.conf时你已经把failoverautomatic开了。自动提升还需要用repmgr的witness节点吗如果你只有两台节点从库自己监测到主库失联后会尝试提升但存在脑裂风险主库没有真正崩溃只是网络分区了那么主库还活着从库却提升成了主库两个库同时接收写请求等网络恢复就乱套了。所以强烈建议在专用第三个节点上装一个没有数据、只参与repmgr选举的witness节点。我这套安装过程先不开witness因为多数小环境只有两台但要提醒你两节点模式下自动failover不是绝对安全的。如果不加witness可以在从库promote时设置standby_connect_nodenodea不对这参数是用于从库跟随。实际上两节点场景最简单可靠的策略是repmgrd只做“检测主库失联后自动提升”同时依赖keepalived的健康检查来阻止旧主突然复活抢占VIP。我们后面会讲。开启repmgrd没那么多讲究直接启动服务systemctl enable --now repmgr16日志在/var/log/repmgr/repmgr.log我习惯用它来排查切换失败问题。repmgrd启动之后你可以故意在两个节点上跑repmgr cluster show看它是alive状态。3.4 手工切换演练自动切换之前先做一次优雅的switchover验证整个切换流程不报错。在主库上执行sudo -u postgres /usr/pgsql-16/bin/repmgr -f /etc/repmgr/16/repmgr.conf standby switchoverrepmgr会先检查从库是否健康然后关闭主库把从库提升成主库并重定向原主库角色变成从库。这个过程会短暂影响连接通常几秒。切完后再次执行repmgr cluster show应该看到两个节点的角色互换。如果这里都失败请务必先排查复制延迟和重放进度而不是直接做故障演练否则你可能把一个坏掉的主从集群玩成双主。4. keepalived故障转移配置4.1 安装keepalived并配置VIPkeepalived负责给应用提供一个永远可达的IP它不会关心数据库内部状态。安装包很简单yum install -y keepalived配置主节点nodea的/etc/keepalived/keepalived.conf我用的是单播模式生产环境不依赖组播更稳global_defs { router_id pgha_nodea enable_script_security } vrrp_script check_pgsql { script /etc/keepalived/check_pgsql.sh interval 2 fall 2 rise 2 user root } vrrp_instance VI_1 { state MASTER interface eth0 virtual_router_id 51 priority 150 advert_int 1 authentication { auth_type PASS auth_pass 1234 } unicast_src_ip 192.168.1.11 unicast_peer { 192.168.1.12 } virtual_ipaddress { 192.168.1.100/24 } track_script { check_pgsql } notify_master /etc/keepalived/pgsql_state.sh MASTER notify_backup /etc/keepalived/pgsql_state.sh BACKUP }从节点nodeb配置把state改为BACKUPpriority改成100低于主库virtual_router_id必须一致auth_pass一致unicast_peer改成指向nodea的IP。master和backup的优先级差异确保正常情况下VIP留在nodea。fall 2表示连续2次脚本判断失败才切换rise 2表示连续2次成功才恢复这在数据库闪断时能避免频繁抖动。4.2 健康检查脚本别只检查pgsql端口网络上有太多教程里健康检查脚本就一句话pg_isready -h 127.0.0.1 -p 5432这是典型的坑。想象一下nodea是主库repmgrd把它降级成了standby进程还活着、端口还开着但业务写入已经不可能成功此时keepalived还认为它健康VIP就是不漂业务挂得更彻底。所以我的脚本不仅检查进程还要检查它有没有资格当主库。脚本/etc/keepalived/check_pgsql.sh内容大致如下#!/bin/bash PGC/usr/pgsql-16/bin/pg_isready REPMGR/usr/pgsql-16/bin/repmgr CONF/etc/repmgr/16/repmgr.conf # 1. 检查本机PostgreSQL进程状态 $PGC -h 127.0.0.1 -p 5432 -U postgres /dev/null 21 if [ $? -ne 0 ]; then exit 1 fi # 2. 检查本节点在集群中的角色非primary则拒绝让VIP停留 ROLE$($REPMGR -f $CONF node status --compact | grep -E Node type: | awk {print $3}) if [ $ROLE primary ]; then exit 0 else exit 1 fi脚本第一段保证数据库进程可用第二段看repmgr认为本节点是不是primary。只有主库才允许keepalived挂VIPstandby角色即使进程活着也判定为不健康立刻触发VIP漂移。这个设计思路非常重要能防止“主库被降级但VIP没走”的尴尬。pgsql_state.sh脚本用于keepalived状态切换时执行一些收尾比如在成为MASTER时把本机PostgreSQL服务拉起成为BACKUP时不需要额外操作也可以把遗嘱清理。我通常在里面写一行logger方便看日志。4.3 keepalived和repmgrd的联动时序问题搭建完整套后很多人会问到底先让repmgr切换还是先让VIP漂答案是先repmgr后VIP但你不可能硬编码这个顺序。实际的时序是主库故障瞬间keepalived脚本检查失败VRRP进入交换状态同时repmgrd在从库上通过复制中断感知到问题尝试提升。repmgr提升需要时间和日志同步keepalived切换也不一定就慢。如果VIP先漂到从库但repmgr还没有完成promote此时从库还处于只读状态应用连接会报“read-only transaction”的错。这个窗口期在所难免只能通过调优缩小。我的经验是把keepalived的interval和fall设得保守一点比如interval3、fall3给repmgrd多留几秒同时把repmgr的promote尽快用手工方式验证好毕竟自动提升动作本身很快只要replication slot没有延迟几秒内就能完成。5. 故障模拟与问题排查5.1 模拟主库宕机验证自动切换配置完成后选个低峰期做一次故障演练。我建议不要直接kill -9 postmaster先做更真实的systemctl stop postgresql-16或者模拟断电。在nodea上执行systemctl stop postgresql-16立刻在nodeb上观察tail -f /var/log/repmgr/repmgr.log如果一切正常日志里会出现类似“standby promotion”的记录然后nodeb从standby变为primary。同时在nodea上观察keepalived日志因为nodea的keepalived脚本退出1它会丢弃MASTER状态VIP会广播到nodeb。你可以在nodeb上执行ip addr show eth0确认192.168.1.100已经在nodeb网卡上。之后用psql通过VIP连上数据库执行简单的create table验证可写。这个环节看起来爽但别忽略一个问题PostgreSQL进程被stop后repmgrd在nodea上可能还在运行它会尝试连接nodeb的新主并把自己重新识别为standby但由于数据目录短时间内带有旧主身份需要人工node rejoin。这一步可以写成脚本但不是主线。恢复节点时让nodea重新启动数据库执行sudo -u postgres /usr/pgsql-16/bin/repmgr -f /etc/repmgr/16/repmgr.conf node rejoin -h nodeb -U repmgr -d repmgr --force-rewindrepmgr会用pg_rewind把nodea的数据同步到nodeb的最新状态再作为standby重新加入集群。如果你的PG版本开了wal_log_hints并做了正确归档pg_rewind才能成功这也是我之前配置里要开wal_log_hintson的原因。5.2 常见问题速查表我把这几年被问得最多的问题整理成一张表直接抄答案。问题现象可能原因解决思路keepalived启动报exited with permanent error config.keepalived.conf语法错误比如缩进用了Tab、引号没闭合、interface名写错用keepalived -t -f /etc/keepalived/keepalived.conf语法检查改配置后重启keepalivedVIP无法绑定到网卡interface名字错误网卡没启用或者IP和已存在地址冲突ip addr show查实际网卡名ping VIP确认是否冲突repmgr standby clone失败无法连接主库防火墙没放开5432pg_hba.conf没授权repmgr用户或监听地址只设置成了localhost检查两端防火墙用psql从从节点手动连接主库主从切换后新主库不接受写操作VIP漂到了新主但应用连接串还引用旧VIP或者PG参数default_transaction_read_only被设置检查VIP归属使用show transaction_read_only;确认从库promote后已经关闭只读repmgrd一直重启repmgr.conf里node_id或conninfo不对或者重复注册节点查看repmgr.log查repmgr node status必要时删掉节点记录重新注册keepalived没有跟随主库漂移健康检查脚本没判断角色脚本权限不对VRRP通信被阻断手动执行脚本看返回码检查keepalived日志中的priority变化自动切换后主从数据不一致异步复制导致丢失部分WAL属正常情况如果业务不能容忍开启同步复制并挂witness节点旧主恢复后疯狂抢VIPkeepalived没有配置nopreempt或脚本判断不严格健康脚本必须检查primary角色BACKUP节点priority低必要时master后加nopreempt5.3 缩短切换时间并尽量避免脑裂两个节点的集群最需要防的是“裂脑”。现实里最常见的一个场景是网络抖动而非主机宕机主库还在跑但repmgrd在从库上因为连不上主库就自动提升了自己于是双主出现。这是最危险的事比丢几秒数据严重多了。所以两节点场景下要么你把repmgrd的自动failover关掉改成人工确认后手动promote要么你加witness节点让从库在提升前先询问witness“主库是不是真死了”。witness本身不存数据库全量只是跑着repmgr协议三个节点组成了一个简单仲裁。如果企业网络质量不错你只想缩短切换时间那就把keepalived脚本interval调到1秒fall设为1repmgr.conf里的reconnect_attempts设为1reconnect_interval设为1。这样探测周期差不多3~4秒整个切换时间能压到5秒以内。但代价是网络轻微抖动也容易触发切换频繁切换反而比不切更伤业务。我一般保守interval2fall2切换时间在10秒左右用户是能接受的。6. 实操心得与几个容易踩的坑这套组合我算是有很深的感情了下面这段话是真正跑过之后才总结出来的不是看文档能看出来的。第一个坑是repmgr.conf容易抄错导致“主从注册了但角色无法识别”。每个节点的node_id必须全局唯一conninfo里不要写localhost直接写对端主机名或IPpassfile指向的.pgpass权限必须是600否则repmgr偷偷跳过。第二个坑是keepalived配置文件的缩进。keepalived对配置格式非常敏感一个多余的空格或者用Tab缩进都会报permanent error config然后服务起不来。热词里那句话出现率极高我每次都会先运行keepalived -t检查语法再排查配置文件里有没有不可见字符。还有virtual_router_id必须在同一组节点里保持一致如果两个节点的router_id重复但物理网关坐落在不同广播域会互相干扰。第三个坑是健康脚本的权限。如果你的脚本是root创建的运行用户没执行权限keepalived的track_script会直接判定失败VIP可能从一开始就漂不到主库上。我用chmod x /etc/keepalived/check_pgsql.sh并且在脚本第一行写好#!/bin/bash再在keepalived.conf里user root绝不能省。第四个坑是PostgreSQL升级后repmgr包要同步升级。很多同学发现主从切换失败一看版本PG 16配了repmgr 4.x协议不兼容导致repmgrd直接退出。官方repmgr版本和PG版本是绑定的RPM包名带了PG大版本号如repmgr16升PG时记得升repmgr不然复制关系会乱。还有个小细节如果你是在云平台上跑安全组要额外放行VRRP协议。keepalived默认用多播云网络支持度不佳建议像我这样使用unicast_src_ip和unicast_peer的单播模式。别在云厂商的控制台只放开TCP/UDP端口而忽略了协议号112到时候发现VIP死活不漂日志里全是VRRP advertisement丢包。最后分享一个我常用的自检小脚本在bash里定义vip_status()用ip addr show和repmgr node status --compact同时输出当前VIP归属与节点角色调试时一眼就能看出究竟是数据库没切换还是VIP没漂移省掉很多无用功。记住高可用是“可用性”和“一致性”的平衡不是一台机器永远Thrive而是挂了一台业务还能撑过去。把这两个默认参数、两个脚本和一次完整故障演练都做扎实了这套集群就真正完工了。