资讯动态

PostgreSQL流复制与pgpool-II高可用配置及避坑指南

发布时间:2026/10/27 11:28:16 来源:尧图企业网站定制
简介面向数据库运维与架构人员的高可用方案文档聚焦 PostgreSQL 主从流复制与 pgpool-II 的组合落地适合需要搭建主备库、实现故障切换、读写分离和连接池管理的 Linux 环境实践者。文档围绕流复制同步/异步模式、WAL 日志传输机制、pgpool-II 的连接池与负载均衡策略展开并给出环境规划、主库配置、备库初始化、pgpool 配置及启动测试等完整章节目录还覆盖防火墙、SELinux、网络、hosts、时钟同步和系统资源调整等准备工作便于按步骤核对与复现。读者可据此理解同步复制在一致性与写性能之间的取舍以及 pgpool-II 故障切换与只读请求分流的配置逻辑并借助 pg_rman 备份章节完善运维闭环。包内共 1 个 docx 文件约 773KB篇幅紧凑但结构完整可作为方案选型、部署检查与排错思路的参考。已有 470 人学习适合中级数据库从业者用于主备高可用架构的快速梳理与落地对照。1. 主库刚提交备库为什么查不到先把“流复制”这件事说透主库刚执行完一条 INSERT 并提交成功客户端立刻去备库查有时却查不到那一行。很多人第一次搭好 PostgreSQL 流复制都会愣一下监控里复制状态明明是 streaming日志也没报错这个延迟却真实存在。要解释它得先接受一个反直觉的结论——备库不是把数据块“同步”过去的而是靠重放主库的 WAL 日志追出来的。围绕这条链路pgpool-II 的高可用、读写分离、failover 才有地方挂。把这套主从流复制 pgpool-II 高可用方案拆到底层本质上是在回答同一个问题日志从主库到备库中间每一跳都可能丢、可能卡、可能慢整套设计就是围绕这些可能性做取舍。2. WAL 与流复制备库凭什么能“实时”追上主库2.1 WAL 的本质以及 walsender / walreceiver 的接力PostgreSQL 的写入路径里有一条硬规矩先写日志再写数据页。任何一次数据变更都会先在pg_wal老版本叫pg_xlog里记一条 WAL 记录等这个记录安全落盘了事务才算提交成功。这条规矩本来是为了崩溃恢复——数据库进程突然挂了重启时拿 WAL 把不一致的数据页重放一遍数据就回到一致状态。流复制其实是顺手复用了这套机制既然 WAL 已经包含了“把数据推到某个一致状态”所需的全部信息那把它传给另一台机器重放不就等于把那台机器也推到了同样的状态把 WAL 从主库弄到备库历史上只有两条路。第一条叫 WAL 归档base-file主库写完一个完整的 WAL 文件才把它拷到备库备库通常落后主库一个文件延迟以分钟计。第二条就是流复制主库一产生 WAL立刻通过 TCP 长连接推过去延迟通常在亚秒级。文档里给出的选型理由很直接——既然要在线高可用肯定选延迟更低的那条。流复制实际的进程链路是这样一条接力# postgresql.conf 主库关键参数12.x 示例 wal_level replica # 至少 replica 才能支撑流复制logical 更重但支持逻辑解码 max_wal_senders 10 # 允许同时有多少个 walsender 进程决定了能挂几个备库 wal_keep_segments 64 # 主库至少保留的 WAL 段数防止备库落后太多时旧段被回收 # 注意13.x 起这个参数改名为 wal_keep_size单位 MB链路上有四个角色各管一段walwriter主库周期性地把共享缓冲区里的 WAL 页刷到磁盘上的pg_wal。它只管“落盘”不关心有没有备库。walsender主库备库连上来之后主库为每个备库开一个 walsender 进程从pg_wal里读出还没发过的段通过 TCP 推出去。walreceiver备库接收 WAL 流写到本地磁盘上的pg_wal同时回一个确认给主库告诉它“我写到哪个 LSN 了”。startup备库的恢复进程从本地pg_wal里读出日志逐条重放把数据页改成和主库一致。这里有一个很容易被忽略的细节walreceiver写盘和startup重放是两个进程速度并不严格一致。也就是说WAL 已经传到备库磁盘上了数据页却可能还没重放完。所以判断“备库能不能查到某条数据”看的不是walreceiver收到的位置而是startup重放到的位置。这就是第 1 章那个“主库提交了、备库查不到”现象的第一层原因——传输没延迟重放有延迟。pg_stat_replication这个视图能把上面几个位置一次性看清楚主库上执行-- 在主库上查看每个备库的复制状态 SELECT application_name, client_addr, state, -- streaming 表示正常流复制中 sent_lsn, -- 主库已经发出去的 WAL 位置 write_lsn, -- 备库已写盘的 WAL 位置 flush_lsn, -- 备库已落盘的 WAL 位置 replay_lsn -- 备库已重放真正可见的 WAL 位置 FROM pg_stat_replication;排查复制延迟时sent_lsn与replay_lsn之间差得多少就是备库真正“落后”多远。只看state streaming会骗人血泪经验是至少把replay_lsn一起看一眼。2.2 同步与异步synchronous_standby_names 到底改变了什么流复制默认是异步的。异步的意思是主库提交事务时只保证自己的 WAL 已经落盘不等备库确认就返回。好处是写性能不受备库影响坏处是主库突然崩溃时已经发给备库但还没重放的那一小段 WAL 可能丢——丢的量取决于replay_lsn和主库最新提交之间的差通常很小但不是零。同步复制把这段窗口关掉。打开它只要两个条件同时满足synchronous_commit on并且synchronous_standby_names被设成一个非空值。这时主库提交事务前必须等到指定的备库回复“这段 WAL 我已经 flush 到磁盘”事务才向客户端返回成功。# 主库 postgresql.conf 同步复制配置 synchronous_commit on # 语法数字 (备库名列表)数字表示至少要几个备库确认 # 例 1指定一个备库必须确认 synchronous_standby_names standby1 # 例 2任意两个备库中至少一个确认即可 synchronous_standby_names 1 (standby1, standby2) # 例 3所有列出的备库都要确认更安全但备库一挂主库就卡死 synchronous_standby_names 2 (standby1, standby2)这里的“备库名”不是主机名而是备库primary_conninfo里配的application_name。这一点很关键如果备库连上来时没设application_name主库在synchronous_standby_names里根本匹配不到它同步复制就是空转看起来配了实际还是异步。很多“配置都对、性能却上不去”的翻车现场追到底就是这个匹配问题。同步复制和异步复制之间的取舍文档讲得很克制但落到生产上要考虑三件事维度异步复制同步复制主库写性能不受备库影响受最慢的那个确认备库影响主库崩溃丢数据可能丢少量 WAL理论上不丢已提交事务备库故障时主库继续写主库可能被卡死取决于确认配置适用场景读多写少、能容忍秒级丢失强一致、不能丢一笔我一般把同步复制当成一种“一致性保险”而不是默认选项。它的代价不只是延迟更是可用性备库一挂主库可能跟着阻塞这在高频写入的库里会迅速演变成雪崩。所以真要用同步synchronous_standby_names里至少留一个可以降级的备库并且把主库的wal_sender_timeout、statement_timeout算清楚别让一个慢备库拖死整个写入链路。3. 主备配置12.x 那条 recovery.conf 断代线3.1 pg_hba.conf 与复制用户认证这一关必须一次配对流复制建立连接这件事走的是 PostgreSQL 自己的一套认证流程和普通客户端连接一样要过pg_hba.conf。差别在于复制连接请求的数据库字段是replication不是某个具体库名。所以主库的pg_hba.conf里必须专门为复制留一条规则否则备库的walreceiver会一直连不上日志里报的通常是no pg_hba.conf entry for replication connection。主库上创建复制用户的动作很简单关键是权限用对-- 在主库上创建复制专用账号只给 REPLICATION 权限不给超级用户 CREATE ROLE repl WITH REPLICATION LOGIN PASSWORD replpass;然后主库的pg_hba.conf加一条# 允许备库网段用 repl 账号发起复制连接 # TYPE DATABASE USER ADDRESS METHOD host replication repl 192.168.1.0/24 scram-sha-256注意DATABASE列写的是replication这是连接类型而不是库名。这条规则写错的概率极高——要么地址网段漏了备库 IP要么把replication写成了具体的库名结果备库连上来直接被拒。这是部署阶段最常见的翻车点之一后文避坑章节还会展开。备库初始化用pg_basebackup它会把主库的数据目录完整拷一份同时自动带上复制所需的配置# 在备库上执行从主库拉一份基础备份 pg_basebackup -h 192.168.1.10 -p 5432 -U repl -D /data/pgsql/data \ -Fp -Xs -P -R # -Fp 普通文件格式-Xs 流式复制 WAL-P 显示进度-R 自动写恢复配置-R这个参数在 12.x 前后行为不同正好引出下一节的断代线。3.2 12.x 前后备库标识的迁移recovery.conf 去哪了PostgreSQL 12 是一个分水岭。12 之前备库靠数据目录下的recovery.conf文件进入恢复模式12 及以后这个文件被彻底移除恢复参数合并进postgresql.conf恢复模式改用数据目录下的一个信号文件recovery.signal来触发。这个改动不只是一次配置搬家它改变了“备库到底是什么状态”的判断方式——以前看文件在不在现在看信号文件在不在。11.x 及以前的写法# 11.x 及以前recovery.conf放在数据目录下 standby_mode on primary_conninfo host192.168.1.10 port5432 userrepl passwordreplpass application_namestandby1 recovery_target_timeline latest12.x 以后的写法# 12.x 及以后这些参数直接写进 postgresql.conf primary_conninfo host192.168.1.10 port5432 userrepl passwordreplpass application_namestandby1 recovery_target_timeline latest # standby_mode 参数被取消改为在数据目录下放一个 recovery.signal 文件 # 备库启动时检测到该文件就进入恢复模式这个变化带来的直接后果是老版本升级到 12.x 的迁移过程如果照搬老的recovery.conf备库根本不会进入恢复模式直接以普通实例启动然后数据就分叉了。另一种情况是初始化之后忘了创建recovery.signal备库能启动但不会接收 WALpg_stat_replication里根本看不到它。这两种现象本质不同排查时要先确认版本再判断是该找recovery.conf还是找recovery.signal。pg_basebackup -R在 12.x 以上会自动把primary_conninfo写进postgresql.conf并在数据目录创建recovery.signal所以用这个参数初始化基本不会漏。但代价是如果你之后又在postgresql.conf里手动覆盖了一次配置两处冲突时以最后加载的为准排查时容易看花眼。4. pgpool-II中间件的四种职责与 VIP 漂移4.1 连接池、读写分离、限流如何在同一个入口完成pgpool-II 的位置很明确它坐在客户端和 PostgreSQL 之间客户端以为自己连的是一台数据库实际上连的是 pgpool。这个“中间人”身份让它能一次性揽下四个职责文档里列得很清楚——连接池、复制、负载均衡/读写分离、限制超过限度的连接。其中最容易误解的是连接池。它缓存的是到后端 PostgreSQL 的真实连接当客户端用相同参数连进来时pgpool 直接复用一个已有连接省掉一次 TCP 握手和认证的开销。对短连接、高并发查询的场景这一层省下来的时间很可观。读写分离是 pgpool 最有价值的部分。它靠 SQL 语句分类来决定往哪发SELECT 这类只读请求按权重分给备库INSERT/UPDATE/DELETE 发主库。main 库的权重设成 0就表示只写不读备库权重设成非 0就承担读请求。代码如下# pgpool.conf 关键参数4.x 示例 listen_addresses * port 9999 # 客户端连 pgpool 的端口 backend_hostname0 192.168.1.10 backend_port0 5432 backend_weight0 0 # 权重 0只走写不参与读负载均衡 backend_flag0 ALLOW_TO_FAILOVER # 允许故障时被切走 backend_hostname1 192.168.1.11 backend_port1 5432 backend_weight1 1 # 权重 1参与读负载均衡 backend_flag1 ALLOW_TO_FAILOVER # 打开读请求分发 load_balance_mode on # 健康检查定期探后端是否活着 health_check_period 10 health_check_timeout 20限制连接这一项和 PostgreSQL 原生的做法不同PostgreSQL 到了max_connections就直接拒连pgpool 则是把超出的连接放进队列等而不是立刻报错。这个差异在业务高峰期体验差别很大——原生拒绝会给客户端抛错队列等待至少还能撑一阵但代价是排队可能越积越长需要配合num_init_children和max_pool一起调不然队列本身会变成新的瓶颈。状态查询用 pcp 工具一个命令就能看到后端节点的健康状态# 查看 pgpool 集群中各后端节点的状态 pcp_node_info -h 127.0.0.1 -p 9898 -U pgpool -n 0 # -n 0 表示查 0 号后端节点status 字段为 2 表示 up为 3 表示 down4.2 watchdog 与 failover虚拟 IP 漂移的触发链pgpool 的高可用靠两层配合watchdog负责 cluster 内节点之间的互相监控failover负责在后端数据库故障时切换。两者串起来才是完整的容灾链。watchdog 在 pgpool 节点之间建一个集群每个节点定期互相发心跳。主节点上配置一个虚拟 IPdelegate_IP客户端实际连的是这个 VIP而不是任何一台物理机的 IP。当主 pgpool 节点故障watchdog 会把 VIP 漂移到还活着的那个 pgpool 节点上客户端不用改连接串就能继续工作。# pgpool.conf watchdog 相关配置 use_watchdog on wd_hostname 192.168.1.10 # 本机 pgpool 节点的主机名或 IP delegate_IP 192.168.1.100 # 对外暴露的虚拟 IP客户端连这个 wd_port 9000 # watchdog 节点之间通信端口 # 漂移后的启动脚本VIP 落到本机时执行用于挂上 IP if_up_cmd /usr/local/bin/if_up.sh $_IP_$ eth0 if_down_cmd /usr/local/bin/if_down.sh $_IP_$ eth0后端数据库故障的切换靠的是failover_command。pgpool 探测到主库不可达会触发这个脚本脚本里可以写提备库为新主的逻辑比如在备库上执行pg_ctl promote然后让 pgpool 更新 backend 的角色。文档里 failover 测试的做法就是手动停掉主库的postgres进程或 pgpool 进程观察 VIP 是否已经漂移、节点状态是否更新这是验证整条容灾链是否真的通的最直接方式。需要提醒一点VIP 漂移和数据库提主是两件独立的事。VIP 只代表“客户端连的地址变了”不代表“新主库已经能写”。如果 failover 脚本里只做了 VIP 切换没做promote客户端连上去会发现读能读、写报错。检查的先后顺序是先看pcp_node_info里节点状态再看pg_stat_replication新主库上应该已经没有 stream 角色最后才验证写入。这个顺序如果反过来排查会绕一大圈。5. 避坑与排查那些文档没写的翻车现场5.1 备库连不上日志只报 no pg_hba.conf entry现象备库启动后pg_stat_replication里完全看不到它备库日志里反复出现no pg_hba.conf entry for replication connectionwalreceiver一直在重连。原因主库pg_hba.conf里没为复制连接单独放行。常见写法错误有两种一是把DATABASE列写成了具体库名而不是replication二是ADDRESS网段没覆盖备库 IP尤其是备库 IP 配成了不在同网段的地址。解决在主库pg_hba.conf里补一条host replication repl 备库IP/32 scram-sha-256然后pg_ctl reload让主库重新加载配置。reload 不需要重启但pg_hba.conf的改动必须 reload 才生效。5.2 同步复制配了主库写入却越来越慢甚至卡死现象打开同步复制后主库写入延迟从毫秒级跳到秒级备库一旦网络抖动主库整个写入链路跟着挂起业务开始大面积超时。原因synchronous_standby_names指定了某个备库必须确认但那个备库的application_name没和主库配置对上导致主库一直等一个“不存在的确认”或者备库本身负载过高flush 响应慢主库被拖住。解决先在主库pg_stat_replication里核对备库的application_name确保和synchronous_standby_names中列的一致再把确认策略从“全部备库”降级成“至少一个”1 (standby1, standby2)给自己留一个降级空间。同步复制这件事宁可退一步做异步也不要为了强一致把可用性赔进去。5.3 pgpool 报认证失败客户端连不上 9999 端口现象后端数据库和 pgpool 本身都正常客户端连 pgpool 的 9999 端口报认证失败或password authentication failed。原因pgpool 的认证走的是自己的一套配置pool_hba.conf负责前端认证规则pool_passwd负责存放用户名与密码或密码哈希的映射。如果用户不在pool_passwd里或者pool_hba.conf里没放行客户端网段就会连不上——哪怕这个用户在 PostgreSQL 里是真实存在且密码正确的。解决用pg_md5或pool_passwd工具把对应用户写进pool_passwd检查pool_hba.conf是否有对应条目然后确认enable_pool_hba on已经打开。前端认证和后端认证是两套独立配置任何一套配错都表现为“连不上”排查时得分开看。5.4 备库复制断裂日志报 requested WAL segment has already been removed现象备库跑了一段时间后突然停止复制日志里报requested WAL segment ... has already been removedpg_stat_replication里备库消失。原因wal_keep_segments设得太小而备库因为网络或负载原因落后了一段时间主库已经把备库还没收到的旧 WAL 段回收了。备库再请求时文件已经不在了。解决短期靠重新用pg_basebackup拉一份基础备份恢复长期把wal_keep_segments调大13.x 以上是wal_keep_size或者上 WAL 归档让备库可以通过 restore_command 从归档里补旧段。这个坑的本质是主库回收 WAL 的策略只认自己的磁盘空间不认备库落后了多少配置得站在“最坏情况备库能落后多久”的角度去算。5.5 VIP 漂移成功但客户端连接仍然指向旧地址现象failover 测试里主库或 pgpool 进程停掉后VIP 确实漂移到了另一台机器但客户端应用还是连不上连接串里写的像是“没生效”。原因VIP 漂移只改的是网络层地址客户端如果用了本地 DNS 缓存或连接池缓存的旧连接它仍然拿着旧地址去连。另一种原因是delegate_IP和if_up_cmd的网卡名不匹配脚本执行了但没真正把 IP 挂到网卡上。解决先在漂移后的节点上执行ip addr确认 VIP 真的挂上了再让客户端侧清掉 DNS 缓存和应用层连接池if_up_cmd里的网卡名必须和实际网卡一致这个参数错了没有任何报错只会“看起来漂移了、实际没挂上”。检查的先后顺序是网络层 → 客户端缓存 → 应用重连。6. 设计取舍同步与异步、备份与恢复之间怎么选整套方案跑通之后真正难的不是把组件装起来而是回答“到底要开同步还是异步、备份做到什么粒度”。这两个问题本质上都是取舍而取舍的依据只能来自业务对丢数据的容忍度。同步复制解决的是“主库崩溃丢已提交事务”的问题代价是写延迟和可用性。异步复制反过来——写性能好、可用性高但崩溃时可能丢一两秒的 WAL。我的建议是把“同步”看成一条可降级的一致性保险默认用异步关键业务表或关键时间段再切同步同步的确认策略至少要留一个备库做兜底千万别写成“全部备库都要确认”否则任何一台备库抖动都会把主库拖死。这个选择没有标准答案但有一个硬指标可以对照——业务能接受的 RPO恢复点目标是零还是几秒答案不同配置就不同。备份是另一层保险它和第 2 章的流复制是互补而不是替代关系。流复制保证的是“主库挂了还有一台近实时的备库顶上”pg_rman 保证的是“误删、误更新、数据被破坏时能恢复到任意时间点”。文档里把这两件事分得很清楚流复制是线上的、秒级的pg_rman 是离线的、基于全备 WAL 归档的配合 PITR 可以回到某个具体时刻。把备份脚本写成可重复执行的形态比写对一次更重要#!/bin/bash # pg_rman 全备脚本简化示意参数按实际环境调整 export PGHOME/usr/local/pgsql export PGDATA/data/pgsql/data export BACKUP_PATH/data/pg_rman export PATH$PGHOME/bin:$PATH # 执行一次全量备份保留 7 天 pg_rman backup --backup-modefull --with-serverlog --keep-data-days7 # 备份完必须 validate否则坏备份和没备份没区别 pg_rman validate--with-serverlog会把服务器日志一起备进去恢复时排查现场用得上validate这一步千万别省没有校验的备份在真正需要恢复时才发现损坏等于白做。归档脚本pg_rman_archive.sh则定期把 WAL 归档到备份目录全备 归档合起来才构成 PITR 的完整链条——只做全备、不做归档最多恢复到上一次全备点中间那几个小时的增量就丢了。还有一个容易忽略的取舍主备节点的操作系统和 PostgreSQL 大版本最好保持一致。流复制的二进制格式在跨大版本时可能不兼容跨小版本虽然一般能用但也别赌。这个约束在设计阶段就得定下来别等上线后才发现其中一台机器还停在旧版本。从那以后我每次规划这类方案都会先把三件事写进设计文档再动手同步还是异步、RPO 是多少、备份能不能覆盖到 PITR。这三件事不先定后面装多少组件都是玄学。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑