简介:围绕 PostgreSQL 主从流复制与 pgpool-II 高可用落地,这份技术方案文档面向数据库运维、架构设计及集群部署人员,重点解决主备数据同步、故障切换、读写分离和连接池管理等实际问题,兼顾生产部署与学习验证场景。压缩包仅含 1 个 docx 文件,约 773KB,内容以方案说明、环境规划与配置记录为主,便于按章节查阅和复用。文档从流复制的同步/异步模式及 WAL 日志传输机制讲起,说明同步复制对一致性与写性能的影响、异步复制在写入速度与数据丢失风险之间的取舍,并结合 pgpool-II 的连接池、复制、负载均衡/读写分离、连接数限制与备份模块,覆盖软件环境、主备节点准备、主库配置、备库初始化、pgpool-II 配置、启动测试及故障模拟切换等环节,还涉及防火墙、SELinux、hosts、时钟同步与 sysctl 等系统资源调整,适合需要搭建高可用 PostgreSQL 集群的读者参考。目前已有 471 人学习。
1. 数据库只靠单机运行,业务峰值一来就只剩两种结局
很多团队第一次遇到数据库瓶颈时,第一反应是换更大的机器:加内存、换 SSD、把 CPU 从 8 核升到 32 核。硬件升级能解决性能问题,却解决不了另一类问题:主库磁盘损坏、进程被误终止、机房单点断电,任何一个环节出问题,业务就会直接停摆。这时即使机器配置再高,数据也只有一份,恢复只能靠备份,而备份的 RPO(恢复点目标)往往按小时计算。
这份「Postgres 主从流复制 + pgpool 高可用方案」要解决的,正是从单机数据库走向双节点在线集群的完整落地路径:用 PostgreSQL 原生流复制把 WAL 日志实时送到备节点,用 pgpool-II 做连接池、读写分离与故障切换,再用 pg_rman 做离线归档备份兜底。它适合正在做数据库高可用毕设、课程设计的学生,也适合需要给一套可直接参考的部署文档的运维和 DBA。下面按需求、设计、实现、避坑、验收的顺序拆开讲。
2. 总体架构设计:三个组件如何分工
这套方案的价值不只在于「装了三个软件」,而在于把在线同步、离线备份、流量调度三件事拆给三个组件,各管一段。理解分工之后,再看配置文件里的上百个参数,才知道每个参数在替谁做决策。
2.1 组件职责划分与数据流向
整体是一条主链路加一条旁路。主链路上,客户端不直接连数据库,而是连到 pgpool-II 暴露的虚拟 IP;pgpool-II 根据 SQL 类型把写请求发给主库、只读请求分散到主备;主库的 walsender 进程把 WAL 页推给备库的 walreceiver,备库的 startup 进程持续重放日志保持数据一致。旁路上,pg_rman 周期性做全量备份并持续归档 WAL,主备同时失效时从这里恢复。
| 组件 | 部署位置 | 核心职责 | 失效影响 |
|---|---|---|---|
| PostgreSQL 主库 | 节点 1 | 承接写请求、产生 WAL | 触发 failover,虚拟 IP 漂移 |
| PostgreSQL 备库 | 节点 2 | 重放 WAL、承接只读查询 | 读能力下降,主库继续写 |
| pgpool-II | 双节点各一套 | 连接池、读写分离、健康检查、VIP 管理 | 客户端无法接入,需排查中间件 |
| pg_rman | 独立备份目录 | 全量备份 + WAL 归档,支持 PITR | 不影响在线业务,但失去误删恢复能力 |
一个容易忽略的设计点:pgpool-II 本身也是双节点部署并启用 watchdog,两个 pgpool 互相监控、争抢虚拟 IP。如果只部署一个 pgpool,它自己就是新的单点,高可用等于没做。通常的做法是让 pgpool 与 PostgreSQL 同机部署(本方案就是这个思路),省机器也省网络跳数,代价是单机故障时两个角色同时漂移,切换逻辑要更谨慎。
2.2 同步复制与异步复制的取舍
PostgreSQL 流复制默认异步:主库提交事务后不等备库确认,延迟通常在毫秒到 1 秒之间,主库突然崩溃可能丢少量事务。同步复制则要求synchronous_standby_names非空且synchronous_commit = on,主库必须等备库写完 WAL 才返回提交成功,代价是每次写事务都多一个网络往返。
选型判断很直接:金融账务、订单扣减这类不能丢数据的场景选同步;日志、埋点、报表类能容忍秒级丢失的选异步。本方案在新版本环境里给了synchronous_commit的配置位置,实际交付时我一般先按异步上线,观察主备延迟稳定后再切同步,避免一开始就因为网络抖动把写入拖垮。
# postgresql.conf 主库关键参数(12.x 示例) wal_level = replica # 至少 replica,才能支撑流复制 max_wal_senders = 10 # 允许的 WAL 发送进程数,需大于备库数量 wal_keep_segments = 128 # 保留的 WAL 段数,防止备库落后被清理 synchronous_commit = on # 开同步提交 synchronous_standby_names = '*' # 匹配任意一个备库参数说明:wal_level决定 WAL 记录的信息量,设成minimal就无法做流复制;max_wal_senders要和实际备库数留出余量;wal_keep_segments是防翻车的关键,备库短暂断连再回来时,如果主库已经把需要的 WAL 段清掉,复制就只能重建。synchronous_standby_names写'*'表示任意一个备库确认即可,也可以写具体应用名做精确控制。
3. 主从流复制的落地实现:从环境准备到切换验证
这一章是整份方案里最需要照着敲的部分。原文把流程拆成环境准备、资源调整、安装、配置复制、切换验证几段,这里按实际执行顺序重新组织,把每一步的判断依据补上。
3.1 系统层准备:那些装库之前必须先做完的事
装 PostgreSQL 之前有四件事必须先处理,顺序错了后面全要返工。第一,关闭防火墙或放行 5432、9999(pgpool)、9898(pcp)端口;第二,关闭 selinux,否则数据目录权限会被拦截;第三,配置/etc/hosts写好主备节点的主机名解析,因为后面 SSH 免密和复制连接都依赖主机名;第四,主备节点做时钟同步,否则日志时间戳对不上,排查问题时基本等于蒙眼。
# 关闭防火墙与 selinux(RHEL/CentOS) systemctl stop firewalld && systemctl disable firewalld setenforce 0 sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config # 内核参数:共享内存与信号量,按物理内存调整 cat >> /etc/sysctl.conf <<'EOF' kernel.shmmax = 68719476736 kernel.shmall = 4294967296 vm.swappiness = 10 EOF sysctl -p # 资源限制:postgres 用户的进程数与文件句柄 echo "postgres soft nofile 65536" >> /etc/security/limits.conf echo "postgres hard nofile 65536" >> /etc/security/limits.conf设计说明:kernel.shmmax是单个共享内存段的最大字节数,通常设成物理内存的一半左右,太小会导致could not resize shared memory segment报错;vm.swappiness调低是为了让数据库尽量不走 swap,避免出现响应时间忽高忽低的玄学问题;nofile不放开的话,连接数一多就会报too many open files。这些参数原文归在「资源调整」一章,实际执行必须放在安装之前。
安装环节两个节点要做完全一致的动作:建postgres用户、把安装包解压到统一目录、修改目录属主、配置PGDATA和PATH环境变量、用initdb初始化主库。备库不需要单独 initdb,直接从主库拉一份基础备份即可,这是流复制区别于逻辑复制的地方:物理层面完全一致,连数据块布局都一样。
3.2 主库配置与备库拉取:pg_basebackup 的正确用法
主库侧要改两个文件。postgresql.conf打开流复制相关参数;pg_hba.conf放行备库的复制连接,这一条漏了就会出现经典报错no pg_hba.conf entry for host。
# pg_hba.conf:允许备库以复制用户连接 host replication repl_user 192.168.1.0/24 md5 host all all 192.168.1.0/24 md5接着在主库创建复制用户并授权:
CREATE ROLE repl_user WITH REPLICATION LOGIN PASSWORD 'your_password';然后到备库执行基础备份拉取。这里有个版本坑:PostgreSQL 12 之前备库靠recovery.conf标识,12 之后这个文件被取消,改成在postgresql.conf里写primary_conninfo,并在数据目录放一个空的standby.signal文件。用错版本的做法,备库要么起不来,要么起来之后是个独立主库,复制关系根本没建立。
# 备库执行:从主库拉基础备份 pg_basebackup -h 192.168.1.10 -p 5432 -U repl_user \ -D /data/pgdata -F p -X stream -P -R # -F p 表示明文格式,-X stream 表示同时拉取所需 WAL # -R 会自动生成 standby.signal 和 primary_conninfo(12+ 版本)-R参数是省事的关键,它会自动把连接信息写进postgresql.conf并创建standby.signal,但连接串里的密码不会自动补全,需要在primary_conninfo里手动加上password=xxx,或者配.pgpass。如果是 12 以下版本,则要手写recovery.conf,内容包含standby_mode = 'on'和primary_conninfo。
3.3 复制状态检查与主备切换
两个实例都起来之后,不要急着装 pgpool,先在数据库里确认复制真的在跑。主库上查pg_stat_replication,能看到备库的state是streaming、sync_state符合预期,才算成功;备库上查pg_stat_wal_receiver,status应为streaming。用select pg_current_wal_lsn()和备库的pg_last_wal_replay_lsn()对比,两个值差距持续扩大就说明复制卡住了,通常是网络或 WAL 被清理导致。
进程层面也要看:主库应有walsender进程,备库应有walreceiver和startup进程。备库处于恢复状态时是只读的,可以接查询但不能写入,这一点在读写分离配置里必须让 pgpool 知道,否则写请求被分发到备库就会直接报错。
手动切换的做法是:先停主库,用pg_ctl promote把备库提升为新主库,再把原主库改配置反向接回来。这套操作在生产上应该由 pgpool 的 failover 自动完成,手动只用于演练和应急。演练时必须确认原主库已经彻底停止,否则会出现双主写入导致数据分裂——这类故障基本没有后悔药。
4. pgpool-II 配置:连接池、读写分离与故障切换
pgpool-II 是这套方案里参数最密的一层,配置文件动辄上千行。不用全看,抓住连接、后端节点、健康检查、看门狗四组参数即可。
4.1 pgpool.conf 的核心参数分组
连接池相关参数决定复用效率;后端节点参数告诉 pgpool 主备分别是谁;健康检查参数决定多久发现一次故障;watchdog 参数决定两个 pgpool 之间怎么选主。
# 连接池 listen_addresses = '*' port = 9999 num_init_children = 100 # 预启动的子进程数,决定最大并发连接 max_pool = 4 # 每个子进程对每个后端维持的连接数 # 后端节点 backend_hostname0 = '192.168.1.10' backend_port0 = 5432 backend_flag0 = 'ALWAYS_PRIMARY|DISALLOW_TO_FAILOVER' backend_hostname1 = '192.168.1.11' backend_port1 = 5432 backend_flag1 = 'DISALLOW_TO_FAILOVER' # 健康检查 health_check_period = 10 # 每 10 秒检查一次 health_check_timeout = 20 health_check_user = 'pgpool_check' # 读写分离 load_balance_mode = on参数说明:num_init_children × max_pool大致决定了 pgpool 能占用的后端连接总数,这个值必须小于 PostgreSQL 的max_connections,否则连接池反而成为瓶颈;backend_flag里标ALWAYS_PRIMARY的节点不会被自动降级,适用于主库必须固定的场景;health_check_period设太短会造成频繁误判,设太长则故障发现慢,10 秒是常见起点。health_check_user需要在数据库里真实存在且有连接权限,否则健康检查全部失败,pgpool 会认为所有后端都挂了。
读写分离依赖load_balance_mode = on,pgpool 根据 SQL 是否在事务块内、是否为写语句来判断路由。注意:写在显式事务里的读请求不会被分发到备库,这是为了保证一致性,理解这点才能解释为什么某些场景下负载均衡「看起来没生效」。
4.2 watchdog、VIP 与 failover 的联动
故障切换能不能真的一分钟以内完成,取决于这三者配合。watchdog 让两个 pgpool 互相探活,主 pgpool 持有虚拟 IP;后端 PostgreSQL 主库挂掉时,pgpool 触发 failover,把备库提升为主库(failover_command里调用pg_ctl promote),同时更新后端节点角色,虚拟 IP 不变,客户端连接串完全不用改。
# failover_command 示例:提升备库并重载配置 failover_command = '/usr/local/pgpool/bin/failover.sh %d %H %P %D %R %M'%d是失效节点编号,%H是失效主机名,%P是旧主库端口,%D是新主库数据目录,%R是新主库端口,%M是旧主库 ID。脚本里要根据这些参数决定是「提升备库」还是「把原主库重新接入」。我一般把提升动作写成幂等脚本,重复执行不会出错,因为 failover 在某些网络抖动下可能被触发多次。
VIP 漂移要在 pgpool 的if_up_cmd/if_down_cmd里绑定网卡名和虚拟地址,并在arping_cmd里配置免费 ARP,否则交换机缓存不更新,客户端会短暂连到旧节点。这一步在虚拟机环境里表现尤其明显,经常出现「pgpool 显示切换成功但应用连不上」的假象。
4.3 启动顺序与连接测试
正确的启动顺序是:先起 PostgreSQL 主备,确认复制正常;再起两个 pgpool;最后用pcp_watchdog_info看 watchdog 状态、用show pool_nodes看后端节点状态。通过 pgpool 端口连进去执行select inet_server_addr(),能看出当前请求被路由到哪台机器,反复执行几次观察是否在主备之间轮转,读请求应该两边都有。
# 查看后端节点状态与角色 psql -h 192.168.1.100 -p 9999 -U postgres -c "show pool_nodes;" # 确认当前连接落在哪个实例 psql -h 192.168.1.100 -p 9999 -U postgres -c "select inet_server_addr(), pg_is_in_recovery();"pg_is_in_recovery()返回f说明落在主库,返回t说明落在备库。如果所有查询都只落在一台机器上,先检查load_balance_mode,再检查语句是否被包在事务里。
5. 避坑与排查:五类最容易翻车的问题
5.1 备库拉取失败或复制中断
现象:pg_basebackup报错退出,或复制跑一段时间后备库pg_stat_wal_receiver状态变成stopped。
原因通常是三类:pg_hba.conf没放行复制连接;主库max_wal_senders用完;备库落后太多,需要的 WAL 段已被主库清理。
解决:先在主库确认pg_hba.conf有replication条目并pg_ctl reload;show max_wal_senders确认余量;把wal_keep_segments调大,长期方案是配复制槽(replication slot),但复制槽会让主库无限保留 WAL,必须配合磁盘监控,否则磁盘被撑爆是另一个事故。
5.2 双主写入导致数据分裂
现象:切换之后两边都在接受写入,数据对不上。
原因:原主库没有真正停掉就被提升,或者 pgpool 的健康检查误判导致主库被降级但应用还连着它。
解决:切换前强制确认原主库进程已终止;backend_flag给主库加ALWAYS_PRIMARY防止被自动降级;failover 脚本里加互斥锁,避免并发触发。这类问题一旦发生,只能靠备份做时间点恢复,代价极高。
5.3 pgpool 报 all backend nodes are down
现象:客户端连 pgpool 直接报错,show pool_nodes里所有节点status为 down。
原因:健康检查用户不存在或密码错;pgpool 与数据库之间的pool_hba.conf没放行;pgpool.conf里的后端地址写错。
解决:先用psql以健康检查用户的身份直连后端验证;检查pool_hba.conf是否包含 pgpool 所在网段;确认backend_hostname解析正确。还有一个隐蔽原因:pgpool 启动时数据库还没起好,健康检查连续失败后节点被标记为 down,需要pcp_attach_node手动拉回。
5.4 虚拟 IP 漂移后客户端连不上
现象:failover 日志显示成功,VIP 已在新节点,但应用连接超时。
原因:ARP 缓存未刷新,或if_up_cmd里的网卡名与实际不符(比如配置里写eth0,机器上实际是ens33)。
解决:配置arping_cmd发送免费 ARP;用ip addr确认网卡名后再写配置;切换后手动在客户端arp -d清缓存验证。这个坑在云主机和虚拟化环境里出现频率极高。
5.5 归档没开导致备份不可用
现象:pg_rman 备份成功,但恢复时提示找不到需要的 WAL 段。
原因:archive_mode没开,或archive_command指向的目录不可写,WAL 归档悄悄失败。
解决:archive_mode = on,archive_command用cp或rsync并保证返回码正确;定期查pg_stat_archiver的failed_count,这个值大于 0 就说明归档在丢。备份能不能恢复,只有在真正恢复一次之后才知道,这一点后面还会提。
6. 备份与验收:怎么证明这套方案真的可用
6.1 pg_rman 全量与归档备份脚本
pg_rman 的价值在于把基础备份和 WAL 归档串成一条链。配置上先开归档模式,再初始化备份目录,然后写两个脚本:一个做周期全量,一个做增量归档。
#!/bin/bash # pg_rman_full.sh:每周全量备份,保留最近 4 份 export PGDATA=/data/pgdata export BACKUP_PATH=/backup/pgrman pg_rman backup --backup-mode=full --with-blocksize=32768 -Z pg_rman validate pg_rman delete 4#!/bin/bash # pg_rman_archive.sh:每小时归档备份,保留 24 份 export PGDATA=/data/pgdata export BACKUP_PATH=/backup/pgrman pg_rman backup --backup-mode=archive --with-blocksize=32768 -Z pg_rman validate pg_rman delete 24参数说明:--backup-mode=full做全量,archive只备份新增 WAL;-Z开启压缩节省空间;validate校验备份文件完整性,这一步不能省,否则备份出来是坏的就白做了;delete N按保留份数清理。注意archive_command里的逻辑运算符要用&&而不是;,否则复制失败也会返回 0,PostgreSQL 会以为归档成功,继续清理 WAL。
6.2 验收清单与答辩要点
验收不是「服务起来了」就算完,要有可复现的检查项。下面这张表是我交付时必走的顺序,学生答辩时也可以照着演示。
| 验收项 | 操作 | 通过标准 |
|---|---|---|
| 复制状态 | 主库查pg_stat_replication | state = streaming,延迟在秒级内 |
| 读写分离 | 多次执行只读查询看inet_server_addr() | 主备均有命中 |
| 备库只读 | 在备库执行 insert | 报错,拒绝写入 |
| 主库故障切换 | 杀掉主库 postgres 进程 | VIP 漂移到节点 2,应用连接不中断 |
| pgpool 故障 | 停掉持有 VIP 的 pgpool | 另一节点接管 VIP |
| 备份可恢复 | 在测试实例上用 pg_rman 恢复到指定时间点 | 数据与预期一致 |
答辩或交付演示时,重点讲三件事:为什么选流复制而不是逻辑复制(物理一致、延迟低、无需额外组件);为什么 pgpool 要双节点部署(避免中间件成为新单点);备份为什么必须做恢复演练(备份成功不等于能恢复)。这三问答清楚,方案的说服力就够用了。
从那以后我每次做完高可用方案,都会强制走一遍「拔电源式」验收:不是优雅停止服务,而是直接 kill 进程,看系统在真实故障下的表现。优雅停止掩盖的问题太多了,只有真故障才能暴露切换脚本的漏洞。希望这套结构能帮到你,把方案从文档变成能跑起来、也敢演示的系统。
本文还有配套的精品资源,点击获取