PostgreSQL主备高可用实战:流复制 + repmgr搭建与切换演练
从几年前开始PostgreSQL以下简称PG在国内的应用范围越来越广。不止互联网公司很多传统行业的核心系统也在往PG上迁移。做数据库运维的同行应该都有体会单机PG跑业务一旦主机故障恢复时间少则半小时多则几个小时甚至更长对业务影响非常大。所以主备高可用几乎成了PG上线前的标配。最近我刚在一套测试环境里完整走了一遍 PostgreSQL 安装部署 主备流复制 repmgr 集群管理的全过程从编译安装到主备搭建再到repmgr注册、切换演练和故障恢复算是把这条链路彻底捋了一遍。这篇文章就把整个落地过程、配置细则和我踩过的坑完整记录下来。适合正在调研PG高可用方案、或者想从零搭建PG主备集群的运维和DBA同学参考。1. 方案选型为什么是 PostgreSQL 流复制 repmgr1.1 从业务需求倒推技术选型先说需求场景。有个内部业务系统数据库从单机要升级为双机高可用规划是两台数据库服务器要求数据不丢或者尽量少丢主库故障后能在几分钟内恢复服务最好还能自动切换。这是一个很典型的中小型业务高可用改造诉求。围绕这个需求选型需要考虑几个点数据库版本团队对PG比较熟业务代码也是按PG兼容性写的。复制方案PG自带的物理流复制是最成熟、最稳妥的选择逻辑复制虽然也支持但主要用于数据分发场景做主备切换时会复杂不少。集群管理工具主备搭好后日常监控切换不能靠人肉操作。PG生态里常用的有repmgr、Patroni、etcd结合的方案。Patroni功能更强但引入外部依赖较多对小规模双节点场景反而有点重。repmgr轻量、易上手双节点场景足够。所以最终方案很明确PG物理流复制做主备repmgr做节点注册、状态监控和故障切换。1.2 裸主备和repmgr之间的差距很多人会有疑问PG本身就能搭流复制主库挂了也可以手动把备库promote成主库为什么还要装repmgr这么说吧裸流复制能用但运维体验很差。主库故障后你需要在备库上手动执行 promote还要确认数据是否一致、应用连接要不要切换、原来的主库恢复后怎么重新加入集群。整个过程全靠人工判断一旦操作不熟练很容易出错。repmgr解决的核心问题有三个集群视角所有节点注册到统一元数据中一个命令就能看到谁是主、谁是备、复制是否正常。切换可控既支持故障时的自动切换也支持平时的计划内switchover后者对日常硬件维护非常有用。故障恢复旧主库恢复后可以重新作为备库加入集群不需要删除重建整个实例。这也是我推荐repmgr给这类场景的原因。1.3 双节点拓扑和repmgr角色分配这次的部署拓扑很简单两台物理机称为节点A和节点B。节点A作为初始主库节点B作为备库。两个节点都安装PG和repmgr各自维护一份repmgr元数据。主备之间通过专用账号进行物理流复制。repmgr在设计上要求每个节点都能通过连接串访问其他节点的元数据库因此需要在每个节点上都创建一个独立的repmgr数据库由repl用户管理。这个数据库只存放集群管理信息不存放业务数据量也不大。2. 环境准备与 PostgreSQL 基础安装2.1 服务器规划与系统初始化本次实践用的两台服务器配置都是4核8G、100G数据盘操作系统为 CentOS 7.9。IP规划如下节点A初始主库10.10.20.11主机名pgdb01节点B初始备库10.10.20.12主机名pgdb02系统初始化主要有几项工作关闭SELinux。PG数据目录大概率不在默认位置SELinux如果没有正确处理会导致备库启动或文件读写异常。既然没有复杂的多租户隔离需求直接关闭最省事。配置hosts和主机名。两个节点都要能通过主机名互相解析避免后续repmgr连接时因为hostname解析失败而报错。同步时间。主备复制依赖WAL时间线时间不同步会影响日志排查和故障判断。设置数据盘挂载。数据目录放在独立盘上避免系统盘空间不足拖垮数据库。这些工作做完后开始安装PG。2.2 PostgreSQL 编译安装详细过程关于PG安装方式我这次选择了源码编译安装。一方面是因为测试环境网络受限编译安装后可以把整个目录打包复制到其他机器方便复现另一方面源码编译的目录结构清晰后续做集群配置时更好理解PG各组件之间的关系。PG版本选择15.xrepmgr选择5.4.x这是官方验证过的兼容组合。版本匹配这一点后面还会再强调。先装依赖包yum install -y gcc readline-devel zlib-devel make下载源码并编译wget https://ftp.postgresql.org/pub/source/v15.5/postgresql-15.5.tar.gz tar xf postgresql-15.5.tar.gz cd postgresql-15.5 ./configure --prefix/usr/local/pgsql15 --with-pgport5432 make -j4 make install这里有两个细节说明--prefix指定PG安装路径我习惯把版本号放进路径方便多版本共存--with-pgport设置默认端口虽然postgresql.conf里也能改但编译期设置更保险避免后续工具默认端口不一致。安装完成后把PG的bin目录加入PATH方便日常操作。可以写在/etc/profile里export PATH/usr/local/pgsql15/bin:$PATH export LD_LIBRARY_PATH/usr/local/pgsql15/lib:$LD_LIBRARY_PATH2.3 初始化数据目录与基础配置PG运行不能用root用户需要创建专用系统账户useradd postgres mkdir -p /data/pgdata chown -R postgres:postgres /data/pgdata用postgres用户初始化数据目录su - postgres export PATH/usr/local/pgsql15/bin:$PATH /data/pgdata/data/pgdata /usr/local/pgsql15/bin/initdb -D /data/pgdata -E UTF8 --localeen_US.UTF8 -U postgres初始化完成后先设置基础参数让单机至少能跑起来。编辑/data/pgdata/postgresql.conflisten_addresses * port 5432 max_connections 200 unix_socket_directories /var/run/postgresql/var/run/postgresql目录需要先创建并授权。接下来配置访问规则编辑/data/pgdata/pg_hba.confhost all all 10.10.20.0/24 md5然后启动数据库pg_ctl -D /data/pgdata start到这里PG基础安装就算完成了。接下来进入主备部署的核心环节。3. 主备流复制部署实操3.1 流复制原理与关键参数PG物理流复制的基本原理是主库把每个事务产生的WAL日志连续传输给备库备库接收到WAL后不断重放到自己的数据文件中从而保持与主库一致。备库上的查询通过hot standby机制实现也就是备库在重放WAL的同时还可以接受只读查询。要打开流复制主库必须在postgresql.conf中设置几个关键参数wal_level replica max_wal_senders 10 wal_keep_size 1024wal_level replica表示生成足够详细的WAL支持备库重放这是流复制的最低要求。max_wal_senders指定最多允许多少个WAL发送进程一个备库至少占用一个这里留10个足够。wal_keep_size 1024表示在主库保留1GB的WAL段文件备库如果短暂断连还要尽量跟上主库靠的就是这部分保留日志。PG 15中 old 参数wal_keep_segments已经替代为wal_keep_size按大小而不是段数理解上和调优上都更方便。备库侧则需要在数据目录下放置一个名为standby.signal的空文件PG启动时发现这个文件就会自动以standby模式运行并按primary_conninfo中设置的连接信息去主库拉取WAL。3.2 主库创建复制账号与配置pg_hba.conf在流复制开始前先要建一个专门用于复制的数据库账号。在主库上执行CREATE USER repl REPLICATION LOGIN PASSWORD repl_123;这里的REPLICATION权限是流复制专用的普通用户即使能连数据库也不能作为复制用户使用。建议单独用这个账号做复制和后续repmgr连接不要直接拿业务账号顶替。然后在 pg_hba.conf 中追加两行host all repl 10.10.20.0/24 md5 host replication repl 10.10.20.0/24 md5第一行是让repl用户可以从节点B连到主库管理库第二行是允许节点B建立复制连接。配置完记得 reloadpg_ctl -D /data/pgdata reload3.3 使用pg_basebackup搭建备库数据目录备库的二进制文件已经装好了但数据目录是空的。从主库拉一份全量基础备份是最快的初始化方式。在节点B上执行cd /data rm -rf /data/pgdata/* chown postgres:postgres /data/pgdata su - postgres export PATH/usr/local/pgsql15/bin:$PATH pg_basebackup -h 10.10.20.11 -U repl -p 5432 -D /data/pgdata -Fp -Xs -P -R逐个解释下参数-Fp表示输出格式为普通文件也就是直接生成PG数据目录结构。-Xs表示在基础备份过程中主库产生的WAL日志通过流复制实时传过来避免备份期间出现WAL缺口。-P显示进度备份数据量大了以后能直观看到进度。-R是重点它会在备份结束后自动在数据目录生成standby.signal文件并写好primary_conninfo。这一步省去了手工创建standby标记文件的过程推荐一定带上。备份完成后在节点B上启动备库pg_ctl -D /data/pgdata start启动后在主库执行下面这条SQL应该能看到备库的复制连接SELECT client_addr, state, sync_state, sent_lsn, replay_lsn FROM pg_stat_replication;如果state为streaming说明流复制已经正常建立。为了验证数据同步可以在主库建一张测试表并插入数据然后在备库查询确认数据能及时过来。3.4 流复制参数的一些配置心得流复制部署本身不难难在参数调优和故障判断。这里分享几个实际经验主库的max_wal_senders不要只按备库数量设也要考虑备份工具和归档任务可能占用的WAL发送连接常见的建议是至少为预期的数量再加两三个余量。如果业务写入量很大wal_keep_size可能不够用更合理的做法是使用复制槽slot。repmgr在物理复制场景下也支持维护复制槽后面切换后能自动管理。备库的hot_standby默认就是on不用特别改动。如果备库查询经常出现canceling statement due to conflict with recovery的报错要考虑调整hot_standby_feedback参数。到这里裸的主备流复制已经完成。下一步引入repmgr把两节点纳入统一管理。4. repmgr 安装与配置完整解析4.1 repmgr 版本选择与编译安装repmgr 是管理PG复制和故障切换的工具功能包括节点注册、状态检查、手动/自动切换、克隆重建备库等。当前主流的repmgr 5.x版本中5.4.x支持PG 15这是一个非常关键的点。repmgr版本必须和PG主版本兼容如果版本错位运行各种命令时会出现函数找不到或者元数据表结构错误的问题。我自己就遇到过一次repmgr 5.3配合PG 15在切换时报错的情况最后重新编译到5.4才解决。所以这里推荐稳妥的组合PG 15.x repmgr 5.4.x。两个节点都要编译安装repmgrcd /usr/local/src wget https://github.com/EnterpriseDB/repmgr/archive/refs/tags/v5.4.1.tar.gz tar xf v5.4.1.tar.gz cd repmgr-5.4.1 ./configure --with-pgconfig/usr/local/pgsql15/bin/pg_config make make installconfigure时指定--with-pgconfig让repmgr编译时链接对应PG版本的接口。安装完成后repmgr可执行文件会放入PG的bin目录。4.2 repmgr 元数据库与账号准备repmgr需要在PG实例中保存节点信息和事件记录。建议为它单独创建一个数据库和业务库隔离。在主库上执行以下SQL创建repl用户和repmgr数据库CREATE USER repl SUPERUSER LOGIN REPLICATION PASSWORD repl_123; CREATE DATABASE repmgr OWNER repl;注意repl用户要带SUPERUSER权限因为repmgr在节点注册、切换、重建备库时需要在实例级别执行操作普通用户没有权限完成这些动作。有些生产环境会出于安全考虑收紧该账号权限但维护成本会高不少测试和中小规模环境直接给超级用户即可。两个节点都要创建同样的用户和数据库吗初始状态下只需要在主库创建但建议两个节点都提前创建好这样后续不管谁提升为主库repmgr元数据库都直接可用。备库上同样执行CREATE USER repl SUPERUSER LOGIN REPLICATION PASSWORD repl_123; CREATE DATABASE repmgr OWNER repl;4.3 repmgr.conf 配置文件详解repmgr的配置文件一般放在/etc/repmgr/15/repmgr.conf命名方式通常带有PG大版本号防止多PG版本共存时配置文件混淆。节点A的配置文件内容如下node_id1 node_namepgdb01 conninfohost10.10.20.11 port5432 userrepl dbnamerepmgr data_directory/data/pgdata superuserrepl replication_userrepl replication_typephysical log_levelINFO log_file/var/log/repmgr/repmgr.log各参数含义node_id节点在repmgr集群中的唯一标识不同节点必须不同。node_name节点名建议与主机名保持一致便于识别。conninforepmgr连接当前节点时的连接串user必须是超级用户dbname是repmgr元数据库。data_directoryPG数据目录路径。superuser和replication_user分别是超级用户和复制用户本次两个都指向repl。replication_type物理复制使用physical。节点B的配置与节点A几乎一样除了node_id改为2node_name改为pgdb02conninfo中的host改为10.10.20.12之外其余保持一致。日志目录要先创建并授权mkdir -p /var/log/repmgr chown -R postgres:postgres /var/log/repmgr4.4 注册主节点与备节点配置文件准备好后在主库注册主节点su - postgres export PATH/usr/local/pgsql15/bin:$PATH repmgr -f /etc/repmgr/15/repmgr.conf primary register看到类似NOTICE: registering primary with cluster的日志输出说明注册成功。repmgr会在repmgr库中创建元数据表并记录当前节点为主节点。接着在备库注册备节点repmgr -f /etc/repmgr/15/repmgr.conf standby register如果之前直接用pg_basebackup建立的备库没有注册repmgr这里注册后会检测一次复制状态。注册完成后通过集群状态命令验证整体情况repmgr -f /etc/repmgr/15/repmgr.conf cluster show正常情况下输出结果是两个节点其中一个显示为primary另一个显示为standby且standby的延迟为0。到这里repmgr的集群管理功能已经生效。5. 高可用切换实战从手动到自动5.1 计划内 switchover 的优雅切换高可用方案不能只建不演练。我强烈建议每次部署完先做一次切换演练至少要验证两个场景计划内维护的switchover和故障场景下的promote。计划内切换用switchover命令。比如节点A需要做硬件维护计划把主库切换到节点Brepmgr -f /etc/repmgr/15/repmgr.conf standby switchover --siblings-nodes 2如果不带siblings-nodes参数repmgr也可以根据集群状态自动选择要提升的备节点。双节点场景下指定node_id为2更明确。switchover过程有严格的步骤先把旧主库的WAL完整发送到新主库然后才提升备库。整个过程中数据一致性有保证不会出现数据丢失。我实测下来如果一个库上没有大的未提交事务切换时间通常在几十秒内完成。切换完成后再次查看cluster show节点A变成了standby节点B变成了primaryrepmgr会自动更新节点的角色标记。5.2 自动故障切换与repmgrd监控只靠手动切换显然不够高可用生产要求是主库故障后尽快恢复服务。repmgr通过repmgrd后台守护进程实现自动监测和切换。修改两个节点上的repmgr.conf加入以下参数failoverautomatic promote_command/usr/local/pgsql15/bin/repmgr -f /etc/repmgr/15/repmgr.conf standby promote -f follow_command/usr/local/pgsql15/bin/repmgr -f /etc/repmgr/15/repmgr.conf standby follow -ffailoverautomatic开启自动切换。promote_command是repmgrd在决定提升备库时执行的实际命令注意这里必须使用绝对路径。follow_command用于备库在新主库确立后重新跟随新主库。启动repmgrd两个节点都要执行su - postgres /usr/local/pgsql15/bin/repmgr -f /etc/repmgr/15/repmgr.conf daemon start生产环境建议注册为systemd服务让守护进程随系统自动启动。以下是一个最小systemd单元文件内容放在/etc/systemd/system/repmgrd.service[Unit] Descriptionrepmgr standby daemon Afterpostgresql.service Wantspostgresql.service [Service] Userpostgres Grouppostgres ExecStart/usr/local/pgsql15/bin/repmgr -f /etc/repmgr/15/repmgr.conf daemon start ExecStop/usr/local/pgsql15/bin/repmgr -f /etc/repmgr/15/repmgr.conf daemon stop Typeoneshot RemainAfterExityes [Install] WantedBymulti-user.target需要注意repmgrd本身不负责VIP漂移或负载均衡器的联动。主库切换后应用如果还是连接旧主库IP依然访问不到数据库。生产环境通常配合虚拟IP或者应用连接池的故障转移机制来解决这块在第五节3小节展开。5.3 故障切换后的恢复与集群重组自动切换的场景下假设节点A突然宕机repmgrd在节点B上检测到连接超时后会自动把节点B提升为新的主库。这期间应用会短暂中断但通常几十秒内就能恢复。问题来了节点A恢复后它是旧主库数据落后于节点B不能直接作为备库加入集群。正确做法是在节点A上重新初始化数据目录以节点B为源重新克隆并注册为备节点。具体步骤# 1. 停止并清理旧数据 pg_ctl -D /data/pgdata stop rm -rf /data/pgdata/* # 2. 从新主库克隆数据并生成standby配置 pg_basebackup -h 10.10.20.12 -U repl -D /data/pgdata -Fp -Xs -P -R # 3. 启动数据库进入standby模式 pg_ctl -D /data/pgdata start # 4. 重新注册为备节点 repmgr -f /etc/repmgr/15/repmgr.conf standby register # 5. 确认集群状态 repmgr -f /etc/repmgr/15/repmgr.conf cluster show这一整套操作做完集群又恢复到双节点主备状态。实际演练中我还习惯在恢复流程里加入一条校验克隆完成后在主库一侧检查pg_stat_replication中是否看到旧主库IP对应的streaming连接。确认出现连接后才进行repmgr注册避免注册了一个复制关系还没建立的节点。6. 常见问题与排查技巧实录6.1 复制中断、复制延迟的排查思路流复制不是一直稳定的特别是网络抖动或者主库写入量骤增时备库容易跟不上。遇到复制延迟我一般按以下顺序排查在主库看pg_stat_replication视图确认state是否为streaming。如果state是catchup说明正在追赶如果是stop说明WAL传输中断。查看备库日志。常见日志错误是could not receive data from WAL stream这类通常是网络问题或者主库重启导致的连接断开。检查主库max_wal_senders是否够用。如果业务同时还有在线备份或外部工具在读取WAL发送进程可能被占满备库就抢不到连接。备库的pg_wal目录是否有堆积。如果备库回放速度远小于WAL接收速度需要考虑备库磁盘空间是否不足或者备库上有什么资源争抢。如果是短时网络波动导致的中断备库只要还能连上主库一般会自动恢复。但如果是长时间中断主库设置的wal_keep_size不足以覆盖中断期间的WAL量备库会报requested WAL segment has already been removed这种情况必须重新pg_basebackup重建备库。这也是为什么在有条件时优先使用复制槽的原因。6.2 repmgr 节点注册失败与重注册方法repmgr节点注册失败是新手最容易碰到的问题。总结几类典型报错连接串连不上报connection to server failed。检查conninfo里的IP、端口、用户名密码是否正确以及pg_hba.conf是否允许对应来源的访问。数据库中已有元数据报node pgdb01 already registered。这种情况常见于数据库被克隆或者手工修改过repmgr元数据表。注册时报权限错误确认repl用户有没有SUPERUSER权限。实际处理时最省事的办法是重置repmgr元数据。先在对应节点上执行DROP DATABASE repmgr; CREATE DATABASE repmgr OWNER repl;然后再重新执行节点注册命令。测试环境这么处理没问题生产环境如果repmgr元数据中记录了历史事件可以考虑只删除部分事件表或者导出后再重建。从操作稳定性角度看我一般建议生产环境优先尝试用repmgr node rejoin这类自带命令恢复节点实在不行才考虑重置元数据。6.3 数据库实例级参数不一致导致的切换异常做切换演练时有一个容易忽略的坑备库和主库的postgresql.conf参数不是完全一致的尤其是max_connections、max_prepared_transactions这些参数。备库不允许设置高于主库的值否则从备库提升为主库后无法继续接收旧主库的WAL切换虽然成功了但后续数据复制续不上来。建议在搭建备库时把主库的postgresql.conf同步到备库再按备库身份调整少量参数。repmgr的node check命令也可以做一些基本一致性检测在切换前执行一次能起到预警作用repmgr -f /etc/repmgr/15/repmgr.conf node check6.4 应用连接层的切换配合最后讲一个很容易被遗忘的环节。repmgr完成数据库层的切换后应用端如果不能自动重连高可用效果会大打折扣。应用连接数据库有三种常见模式直接连接固定IP。切换后需要人工修改应用配置或依赖VIP漂移。使用虚拟IP。数据库层切换后通过脚本把VIP绑定到新主库应用连VIP即可。使用连接池或驱动级多主机配置。例如JDBC连接串可以配置多个主机地址客户端自动探测可用节点。我在这套测试环境里采用的是VIP脚本方式。在repmgr.conf中配置两个钩子一个在切换后把VIP绑定到新主库另一个在旧主库恢复为备库后释放VIP最终实现了应用无感知切换。不过要注意生产环境做VIP漂移时需要处理好ARP缓存问题切换后立即在数据库节点上执行一条免费ARP通告否则部分交换机或客户端可能一段时间内仍然把VIP转发到旧节点。最后说点个人感受。这套PG主备加repmgr的方案已经不是新东西了但它确实把双节点高可用的复杂度降到了很低。搭建过程中最花时间的往往不是安装配置而是理解每个参数在切换场景下意味着什么。做完一次完整演练之后再遇到主库宕机就不会慌了因为整个流程是确定的、可重复的。如果你也在规划PG高可用建议先在测试环境把这一整套链路跑通把切换演练记录留存好后续上线才能做到心里有底。