ITPub博客

首页 > 数据库 > PostgreSQL > 数据库教程FGMT59-PostgreSQL负载均衡分发与双主HA架构

数据库教程FGMT59-PostgreSQL负载均衡分发与双主HA架构

原创 PostgreSQL 作者:mfkqwyc86 时间:2026-10-21 11:15:54 0 删除 编辑

数据库教程FGMT59-PostgreSQL负载均衡分发与双主HA架构


## 前言与内容大纲


风哥在数据库一线做了十几年,交付过的生产集群没有一百套也有几十套。这些年被问到最多的问题,已经从"主库挂了怎么切"升级成了"两台机器能不能同时都干活、都能扛流量、挂了还能自动顶上"。这背后其实是把两个相互独立的诉求硬揉在了一起:一个是**高可用(HA)**,任何一个单点故障都不能让业务长时间停摆;另一个是**负载均衡(Load Balancing)**,多台机器的算力都要被用起来,而不是一台忙死、一台闲死。


在动手之前,风哥建议先想清楚三个问题:你的业务能容忍多少数据丢失(RPO),能容忍多长的恢复时间(RTO),读流量与写流量分别占多大比例。这三个答案直接决定架构形态:零丢失强一致,就要上同步复制与多数派仲裁;读多写少,就要把读写分离与连接池做扎实;两者都要,就是风哥本套风哥教程要讲的负载均衡分发加双主 HA 组合。


风哥教程本文的结构如下:第一部分为理论篇,讲清高可用与负载均衡的本质区别、PostgreSQL 复制体系、双主架构的可行形态、组件选型逻辑、脑裂成因与防护、读写分离的一致性边界;第二部分为实战篇,以两台 64G 内存 8 核 CPU 的服务器(主机名 fgedu-net-cn1、fgedu-net-cn2)为载体,从操作系统初始化、源码编译安装、双向流复制搭建、Pgpool-II 连接池与读写分离配置、watchdog 防脑裂、VIP 漂移,到故障切换演练、节点回归与回切,每一步都给出可直接落地的命令与配置;第三部分为总结,由风哥针对本文梳理生产落地时的关键决策点与高频误区。


---


## 第一部分 理论篇


### 1.1 高可用与负载均衡是两个问题


很多架构讨论把"高可用"和"负载均衡"混为一谈,这是第一个要纠正的认知。高可用解决的是**故障时的连续性**:主库宕机、网络分区、机房断电,业务不能长时间中断,切换时间要以秒级计。负载均衡解决的是**正常运行时的吞吐**:把读流量按权重分摊到多个副本,让整集群的 CPU、内存、磁盘 IO 都被利用起来。


用可用性指标量化一下:三个九(99.9%)意味着每年允许宕机约 8.76 小时;四个九(99.99%)是每年 52.6 分钟;五个九(99.999%)是每年约 5.26 分钟。单台数据库服务器要达到四个九以上,靠运气是不行的,必须靠冗余与自动切换。而 RPO 与 RTO 两个指标更是架构设计的地基:RPO 回答"故障时最多丢多少数据",RTO 回答"故障后多久恢复服务"。异步复制对应 RPO 不为零,同步复制可以把 RPO 压到零,但代价是每次提交多一次网络往返。


再说负载均衡。绝大多数互联网业务的读写比在 5:1 到 10:1 之间。数据库的写能力受限于单节点垂直扩容的天花板,而读能力可以通过副本水平扩展。把读流量摊到多台副本上,是性价比最高的扩展路径。但要注意:**负载均衡只能扩展读,扩展写必须靠业务分片或者分布式扩展组件**,这一点在选型时就要想清楚,否则架构做出来是跛脚的。


两者的技术手段也不同。高可用依赖的是复制加自动 failover 加 VIP 漂移;负载均衡依赖的是中间件层的连接分发与查询路由。PostgreSQL 内核只提供了复制这一块基石,其余全部要靠上层组件补齐。风哥在选型时始终坚持一个原则:**先想清楚自己要解决的是可用性问题还是吞吐问题,再去挑组件**,而不是看哪套方案流行就整套搬。


### 1.2 PostgreSQL 复制体系


PostgreSQL 的复制体系分为物理复制与逻辑复制两条线。


**物理复制(流复制)**以 WAL(Write-Ahead Logging)为同步单元。WAL 的本质是"先写日志、后改数据":主库在真正修改数据页之前,先把变更记录顺序写入 WAL 缓冲区,再由 walwriter 进程刷到磁盘。这样即使系统崩溃,数据库也能靠 WAL 把数据页恢复到崩溃前的一致状态。


流复制的完整链路是这样的:主库上的 walsender 进程把 WAL 段实时推送给备库;备库的 walreceiver 进程接收 WAL 并落盘到本机的 pg_wal 目录;备库的 startup 进程持续回放 WAL,把数据页应用到本地。三个进程各司其职,任何一个环节断了,复制延迟就会增大。


流复制有两种模式。**异步模式**下,主库提交事务时不需要等待备库确认,性能最好,但主库瞬间宕机时可能丢失最后一点已提交但尚未送达备库的事务。**同步模式**下,主库至少等待一个备库收到并落盘 WAL 后才返回 commit,数据零丢失,但每次提交增加一个网络往返延迟。工业界最常用的是**同步多数派(quorum commit)**:配置三个节点,`synchronous_standby_names = 'ANY 1 (node1, node2)'`,主库只要等到多数派中的任意一个确认就提交,既保证零丢失,又不会被某个掉线的备库拖死。


复制槽(replication slot)是流复制里容易被忽视的关键设施。主库清理 WAL 段时,如果备库落后太多、WAL 还没被追平就被清理,备库就再也追不上了。复制槽的作用是让主库知道备库的消费位点,从而保留备库尚未消费的 WAL。代价是如果备库长期离线,主库的 pg_wal 会无限膨胀,把磁盘写满。所以复制槽要么配套监控告警,要么设清理上限。


再补一个 checkpoint 与 WAL 关系的基础认知,这对理解复制延迟很重要。checkpoint 是把共享缓冲区里的脏页刷回磁盘的过程,同时更新控制文件里的检查点位置。主库正常运行时,WAL 段在 checkpoint 之后可以被循环复用,但复制槽与 `wal_keep_size` 会阻止它被立刻清理。所以判断"WAL 为什么膨胀"时,要先看是 checkpoint 频率问题、复制槽问题,还是归档跟不上——三者的处理方式完全不同。风哥在排障时最常用的一条命令就是同时查看 `pg_current_wal_lsn`、`pg_last_archived_wal` 与复制槽的 restart_lsn,对比三个位点,问题往往一眼就能定位。


逻辑复制则以数据行为同步单元,通过 publish/subscribe 机制按表复制变更。它允许两端大版本不同、表结构可以裁剪,灵活性高;但只复制 DML,DDL 需要手工同步,且存在两端写入冲突问题。在双主 HA 场景里,物理流复制是最稳的底座,逻辑复制适合做跨库、跨版本的数据分发。


### 1.3 双主架构的三种形态


严格意义上,PostgreSQL 内核并不原生支持"两个节点同时写同一份数据还能保持一致"的多写。MVCC 依赖的事务 ID(XID)在每个节点上独立分配,WAL 也没有合并机制,两个节点同时写同一行必然产生冲突,复制立即中断。工程上能落地的"双主"只有三种形态。


第一种是**双向异步流复制**。两台节点互为主备,各自把 WAL 推给对方,平时各自承担不同的写入分片——比如 fgedu-net-cn1 只写订单表,fgedu-net-cn2 只写用户表,绝不交叉写同一行。它配置简单、读放大可控,但要求业务层严格做写入分片,一旦越界写冲突,复制就会中断,且排查冲突的成本很高。


第二种是**双向逻辑复制**。两个节点各自 publish 一部分表,同时 subscribe 对方的表,按表级双向同步。它能精确到表、容忍版本差异,但 DDL 同步弱、冲突需要应用层规避,配置与维护成本高,适合数据同步到分析库这类场景。


第三种是**Active-Standby 自动切换**。平时一主一备,备库参与读流量,主库故障时备库自动提升为新主。它本质是主从架构,但在负载均衡层的配合下,两台机器平时都在承担读流量,效果上接近"双活";切换由第三方仲裁,不会出现双写冲突。这是工业界最主流、最可靠的形态,也是风哥本套风哥教程实战采用的方案。风哥的建议是:除非业务确实有按表分片的强诉求,否则一律优先选择第三种形态。


### 1.4 组件选型:四层模型


PostgreSQL 高可用生态组件众多,风哥按职责把它们分成四层,选型时逐层补齐,不要一上来就堆全家桶。


**复制层**:PostgreSQL 自带流复制与逻辑复制,无需选型,内核即是最优解。


**故障切换层**:负责监控主库健康状态、故障时提升备库、管理角色切换。主流有四种。Patroni 用 Python 编写,依赖外部 DCS(etcd / Consul / ZooKeeper)做选主与租约,是目前云原生场景下最主流的方案,Kubernetes 里跑 PostgreSQL 基本都用它。repmgr 用自身的元数据表做仲裁,不需要外部依赖,部署简单,适合不想引入 DCS 的团队。Pgpool-II 内置 failover 能力,配合 watchdog 可以做中小规模集群的自动切换。Pacemaker 加 Corosync 是传统 Linux HA 套件,重量级、配置复杂,但稳定性和合规性被老牌企业认可。


**连接路由层**:负责连接分发、读写分离、连接池。Pgpool-II 一体化程度高,能做查询级读写分离与连接池,还能做健康检查与故障切换,但自身是有状态代理,高并发下有转发开销。HAProxy 是纯 TCP 四层代理,性能极强,但不识别 SQL 语义,做不了读写分离,只能按端口分发,通常配合 Patroni 对外暴露 leader 与 replica 两个端口。PgBouncer 专做连接池,性能最好,但不做路由与切换,需要配合其他组件。


**VIP 漂移层**:对外暴露固定虚拟 IP,切换时 VIP 自动漂到新主。Keepalived(VRRP 协议)最常用;Pgpool-II 的 watchdog 也内置 delegate IP 能力,可以在仲裁的同时完成 VIP 漂移。


风哥本套风哥教程的组合是:**流复制 + Pgpool-II(连接池、读写分离、failover、watchdog)+ Keepalived 备选 VIP 方案**,两台机器对等部署,中间件不做单点。这套组合的优点是组件数量少、链路清晰,适合中小规模;规模上到几十个节点、对云原生调度有诉求时,再迁移到 Patroni 加 etcd 的体系。


给读者一张简明的选型对照表,按团队规模与诉求对号入座:两到四节点的中小集群、不想引入外部依赖、要求一体化运维,选 Pgpool-II 加 watchdog;四节点以上、业务在 Kubernetes 上跑、需要租约式强一致选主,选 Patroni 加 etcd;纯读扩展、已有独立 HA 层、只要连接池,选 PgBouncer 加 HAProxy;传统企业、有严格的变更审批与认证要求,选 Pacemaker 加 Corosync。风哥的经验是:组件越少,故障面越小,能用一个组件解决的问题不要用三个。


> 

> 风哥 itpux-com


### 1.5 脑裂、仲裁与多数派


双节点架构最大的敌人是脑裂(Split-Brain):网络分区发生时,两台机器都联系不上对方,都以为对方死了,于是各自把自己提升为主库,客户端被分成两半各写各的,数据从此分叉,再也合不回去。


防脑裂的唯一正解是**仲裁(Quorum)**——必须有一个独立于两节点之外的第三方来裁决谁有资格当主。仲裁的数学基础是多数派:一个节点要当主,必须获得超过半数的选票,公式是 N/2 + 1。三个节点需要两票,五个节点需要三票。没有仲裁者的双节点,网络一断,两边各自只有自己一票,谁都无法合法获得多数派;但如果配置不当(比如没有仲裁机制却允许自动提升),两边都会强行当主,灾难随之而来。


仲裁有两种典型实现。一种是**DCS 选主**:Patroni 通过 etcd 或 Consul 的租约机制选主,主节点持有租约,备节点发现租约失效才参与竞选,DCS 本身保证只有一个赢家。另一种是**watchdog 心跳仲裁**:Pgpool-II 的 watchdog 节点之间互发心跳,并协商出一个 leader,只有 leader 允许持有 delegate IP 并执行切换动作;普通节点与 leader 失联时,会主动降级自身,避免出现两个 leader。


风哥本套风哥教程由于演示资源有限,只有两台机器,会启用 Pgpool-II watchdog 的仲裁机制,并明确告知:生产环境请至少准备三个节点,或者部署一TD立的仲裁主机,否则双节点在极端故障下的行为不可控。


把仲裁场景推演一遍,读者就明白为什么多数派如此重要。假设三个 Pgpool-II 节点 A、B、C,A 与 B、C 之间网络中断,但 B、C 之间正常:B、C 能互相收到心跳,两票形成多数派,B 或 C 合法接管 VIP;A 只有自己一票,无法获得多数派,于是主动降级,把 VIP 让出去。整个过程只有一个赢家,数据不会分叉。反过来,如果是双节点 A、B 互相失联,两边各有一票,谁都没有多数派,理论上都不该当主;但如果配置了"失联即提升"的错误逻辑,两边就会同时提升,脑裂就发生了。所以风哥反复强调:**仲裁不是可选项,是双节点以上架构的必需品。**


### 1.6 读写分离与写后读一致性


读写分离要解决的不只是"把 SELECT 发到备库"这么简单,还有一致性边界问题。异步复制下,应用写入主库后立即去备库读,可能读到旧数据,这就是写后读不一致。延迟的来源有三个:WAL 从主库传输到备库的网络耗时、备库回放 WAL 的耗时、以及备库上长事务或 vacuum 冲突导致的回放停滞。


工程上有三种应对方式。一是**强制路由**:把写后立即读的关键请求强制走主库,这是最简单也最稳妥的做法;二是**事务内粘滞**:在事务内把连接标记为可写,让事务内的读跟随主库,避免同一个事务内读到不一致的数据;三是**接受最终一致**:仅在报表、搜索、异步任务等对实时性不敏感的场景使用备库读。


另外还有一个隐藏坑:备库的 `hot_standby_feedback = on` 参数会让备库把正在执行的事务信息反馈给主库,防止主库清理那些备库还在读取的元组,从而避免备库查询被杀;但代价是备库上的长事务会反过来抑制主库的 vacuum,导致主库表膨胀、WAL 堆积。这个参数开与不开,取决于备库读负载有多重,风哥建议默认开启并辅以长事务监控。


还有一个与隔离级别相关的细节值得单独说明。备库上的查询默认使用快照隔离,回放 WAL 时若与备库正在运行的查询发生冲突(比如主库删了一张备库正在扫描的表),备库会取消那个查询。在读写分离场景下,这种"备库查询被杀"是应用方最常见的问题来源。除了 hot_standby_feedback,风哥还会在应用侧做两层防护:一是把长报表查询放在专门的只读账号下,与在线事务隔离;二是设置 `statement_timeout` 与 `idle_in_transaction_session_timeout`,从数据库侧强制兜底,避免个别失控会话长期占用备库资源。


---


## 第二部分 实战篇


### 2.1 环境规划与系统初始化


风哥本套风哥教程使用两台物理机或虚拟机,硬件规格统一为 **64GB 内存 / 8 颗 vCPU / 系统盘 100GB / 数据盘 2TB SSD**,操作系统为 RHEL 兼容系(CentOS Stream / Rocky / 麒麟均可)。规划如下:


| 角色 | 主机名 | 业务 IP | 说明 |

| --- | --- | --- | --- |

| 节点一 | fgedu-net-cn1 | 192.168.10.11 | 初始主库,Pgpool-II 节点 A |

| 节点二 | fgedu-net-cn2 | 192.168.10.12 | 初始备库,Pgpool-II 节点 B |

| VIP | - | 192.168.10.100 | 对外服务虚拟 IP |

| 仲裁机 | fgedu-net-cn3 | 192.168.10.13 | 生产环境必须,本教程演示用 |


软件以源码编译方式安装,安装根目录统一为 `/fgedudb`,数据库实例名与库名为 `fgedudb`,超级用户 `fgedu`,所有路径遵循风哥的工程约定,不采用传统的固定盘符布局。


网络规划上还要明确一条:业务网段与复制网段建议分开。本教程示例中两台机器处于同一网段,为了演示简化只用了一张网卡;生产环境风哥强烈建议为复制流量单独划一个内网网段或专线,让 WAL 同步不跟业务流量抢带宽,同时也能避免业务波动间接拉高复制延迟。另外,VIP 段要提前在网络设备上做好规划,避免与现有地址冲突;如果跨交换机部署,还要确认 VRRP 组播或单播配置在交换设备上是放通的。


**第一步:设置主机名、配置 hosts 与时间同步。** 两台机器分别执行:


```

hostnamectl set-hostname fgedu-net-cn1    # cn2 上改为 fgedu-net-cn2


cat >> /etc/hosts <<'EOF'

192.168.10.11   fgedu-net-cn1

192.168.10.12   fgedu-net-cn2

192.168.10.13   fgedu-net-cn3

EOF


timedatectl set-timezone Asia/Shanghai

systemctl enable --now chronyd

chronyc sources -v

```


**第二步:数据盘分区与挂载。** 数据盘挂载到 `/fgedudb`,使用 XFS 文件系统,挂载参数针对数据库场景优化:


```

fdisk -l

mkfs.xfs -f /dev/sdb

mkdir -p /fgedudb

cat >> /etc/fstab <<'EOF'

/dev/sdb        /fgedudb        xfs     defaults,noatime,nodiratime 0 0

EOF

mount -a

df -h /fgedudb

```


**第三步:关闭 SELinux 与防火墙。** 生产环境建议按端口放通而非全关,本教程为演示环境简化处理:


```

setenforce 0

sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config

systemctl stop firewalld

systemctl disable firewalld

```


**第四步:内核参数与资源限制。** 按 64G 内存规格调优:


> 

> 风哥教程 113257174


```

cat >> /etc/sysctl.conf <<'EOF'

kernel.shmall = 4194304

kernel.shmmax = 274877906944

kernel.shmmni = 4096

kernel.sem = 5010 641280 5010 128

net.core.netdev_max_backlog = 32768

net.core.somaxconn = 4096

net.ipv4.tcp_syncookies = 1

net.ipv4.tcp_tw_reuse = 1

net.ipv4.tcp_fin_timeout = 30

net.ipv4.tcp_keepalive_time = 300

net.ipv4.tcp_max_syn_backlog = 8192

net.ipv4.ip_local_port_range = 1024 65535

vm.overcommit_memory = 2

vm.overcommit_ratio = 90

vm.swappiness = 1

vm.dirty_ratio = 5

vm.dirty_background_ratio = 2

EOF

sysctl -p


cat >> /etc/security/limits.conf <<'EOF'

fgedu           soft    nofile          1048576

fgedu           hard    nofile          1048576

fgedu           soft    nproc           128000

fgedu           hard    nproc           128000

fgedu           soft    core            unlimited

fgedu           hard    core            unlimited

EOF

```


**第五步:创建用户与目录。**


```

groupadd -g 2001 fgedu

useradd -u 2001 -g 2001 -m fgedu

echo 'fgedu:Fgedu@2026' | chpasswd


mkdir -p /fgedudb/soft

mkdir -p /fgedudb/pgdata/fgedudb

mkdir -p /fgedudb/pgwal

mkdir -p /fgedudb/pg_archive

mkdir -p /fgedudb/backup

mkdir -p /fgedudb/pgpool/run

mkdir -p /fgedudb/pgpool/conf

mkdir -p /fgedudb/pgpool/scripts

mkdir -p /fgedudb/pgpool/log

chown -R fgedu:fgedu /fgedudb

chmod -R 700 /fgedudb/pgdata

```


### 2.2 源码编译安装 PostgreSQL


两台机器都以 fgedu 身份安装。先装编译依赖,缺一不可,否则 configure 阶段会报错:


```

yum install -y gcc gcc-c++ make readline-devel zlib-devel \

    openssl-devel pam-devel libxml2-devel libxslt-devel \

    openldap-devel python3-devel

```


切换到 fgedu 用户,解压源码并执行 configure。风哥强调几个编译选项的意义:`--with-openssl` 启用 SSL 加密连接;`--with-libxml` 与 `--with-libxslt` 提供 XML 处理能力,JSON 相关功能也依赖 XML 支持;`--with-python` 启用 PL/Python 过程语言;`--enable-nls='zh_CN en'` 开启中英文错误消息本地化;`--enable-debug` 保留调试符号,便于生产排障时抓取堆栈:


```

su - fgedu

cd /fgedudb/soft

tar xf postgresql-*.tar.gz

cd postgresql-*/


./configure --prefix=/fgedudb/pg \

    --with-pgport=5432 \

    --with-openssl \

    --with-readline \

    --with-libxml \

    --with-libxslt \

    --with-python \

    --enable-nls='zh_CN en' \

    --enable-debug


make -j8

make install

```


`-j8` 与机器的 8 核 CPU 对应,最大化并行编译速度。编译完成后,配置环境变量,把可执行文件、动态库、帮助文档全部纳入 PATH:


```

cat >> /home/fgedu/.bash_profile <<'EOF'

export PGHOME=/fgedudb/pg

export PGDATA=/fgedudb/pgdata/fgedudb

export PATH=$PGHOME/bin:$PATH

export LD_LIBRARY_PATH=$PGHOME/lib:$LD_LIBRARY_PATH

export MANPATH=$PGHOME/share/man:$MANPATH

EOF

source /home/fgedu/.bash_profile

psql --version

```


风哥的建议:把 PGDATA 环境变量固化到 .bash_profile 里,后续所有 pg_ctl、psql 操作都不用再带 -D 参数,少敲很多命令,也减少人为指定错误目录的风险。


### 2.3 初始化实例与基础参数


在 fgedu-net-cn1 上初始化主库。`--locale=C` 是为了避免 locale 差异导致索引排序行为不一致,`-W` 会提示设置超级用户密码:


```

initdb -D /fgedudb/pgdata/fgedudb -U fgedu -E UTF8 --locale=C -W

```


编写 postgresql.conf,这是 64G 内存 8CPU 规格下的推荐配置,风哥逐段解释:


```

listen_addresses = '*'

port = 5432

max_connections = 1000

superuser_reserved_connections = 10

unix_socket_directories = '/fgedudb/pgdata,/tmp'


shared_buffers = 16GB

effective_cache_size = 48GB

maintenance_work_mem = 2GB

work_mem = 16MB

wal_buffers = 64MB

max_wal_size = 16GB

min_wal_size = 4GB

checkpoint_completion_target = 0.9

checkpoint_timeout = 15min


wal_level = replica

max_wal_senders = 10

max_replication_slots = 10

wal_keep_size = 8GB

hot_standby = on

hot_standby_feedback = on


logging_collector = on

log_directory = 'log'

log_filename = 'postgresql-%a.log'

log_rotation_age = 1d

log_min_duration_statement = 1000

log_checkpoints = on

log_connections = on

log_disconnections = on

log_line_prefix = '%t [%p] %c %u@%d '


archive_mode = on

archive_command = 'test ! -f /fgedudb/pg_archive/%f && cp %p /fgedudb/pg_archive/%f'

archive_timeout = 300

```


参数解读:`shared_buffers` 取物理内存的 25% 即 16GB,是共享缓冲区的合理上限,再大收益递减且增加维护开销;`effective_cache_size` 取 75% 即 48GB,告诉优化器系统总缓存有多大,直接影响索引扫描与顺序扫描的代价估算;`wal_level = replica` 是流复制的最低要求,低于此级别备库无法回放;`max_wal_senders` 与 `max_replication_slots` 决定最多支持多少个复制连接与复制槽;`hot_standby = on` 允许备库接受只读查询;`wal_keep_size` 保留最近 8GB WAL,防止备库短暂离线就断复制;`checkpoint_completion_target = 0.9` 让检查点写入平滑铺开,避免 IO 尖刺。


还有一组后台进程参数需要一并写入,它们决定 autovacuum 与 bgwriter 的行为,对 64G 规格的生产实例很重要:`autovacuum_max_workers = 4` 允许四个并行清理工作者,8 核机器不会因为清理任务挤占业务 CPU;`autovacuum_naptime = 30s` 缩短清理调度周期,避免膨胀累积;`bgwriter_lru_maxpages = 1000` 与 `bgwriter_lru_multiplier = 2.0` 让后台写进程在空闲时主动刷脏页,平滑写入压力。此外,SSD 环境下建议把 `random_page_cost` 调低到 1.1、`effective_io_concurrency` 调到 200,让优化器更愿意选择索引扫描。这些参数可以并入 postgresql.conf 的同一份配置里。


> 

> 风哥数据库教程 itpux-com


pg_hba.conf 追加复制与应用的访问规则,风哥强烈建议生产环境用 scram-sha-256 而非 md5:


```

host    replication     fgedu           192.168.10.0/24         scram-sha-256

host    fgedudb         fgedu           192.168.10.0/24         scram-sha-256

host    fgedudb         fgedu           0.0.0.0/0               scram-sha-256

```


启动主库并验证:


```

pg_ctl -D /fgedudb/pgdata/fgedudb -l /fgedudb/pgdata/fgedudb/startup.log start

pg_isready -h 127.0.0.1 -p 5432

```


创建复制账号与复制槽。复制账号必须具备 REPLICATION 权限;复制槽 `slot_from_cn2` 与 `slot_from_cn1` 分别预留给两个方向的复制链路:


```

psql -U fgedu -d fgedudb -c "CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'Repl@2026';"

psql -U fgedu -d fgedudb -c "SELECT pg_create_physical_replication_slot('slot_from_cn2');"

psql -U fgedu -d fgedudb -c "SELECT pg_create_physical_replication_slot('slot_from_cn1');"

psql -U fgedu -d fgedudb -c "SELECT slot_name, active FROM pg_replication_slots;"

```


### 2.4 双向流复制搭建


在 fgedu-net-cn2 上清空数据目录,用 pg_basebackup 从 cn1 克隆基础备份。`-Fp` 表示平铺文件格式,`-Xs` 表示流式传输 WAL,`-P` 显示进度,`-R` 自动生成备库恢复配置,`-S` 指定使用哪个复制槽,`-n` 不覆盖已有文件(避免误伤):


```

su - fgedu

rm -rf /fgedudb/pgdata/fgedudb/*


pg_basebackup -h 192.168.10.11 -p 5432 -U replicator \

    -D /fgedudb/pgdata/fgedudb -Fp -Xs -P -R \

    -S slot_from_cn2 -n

```


`-R` 参数会自动在备库数据目录里生成 standby.signal 与 postgresql.auto.conf。检查生成结果:


```

cat /fgedudb/pgdata/fgedudb/standby.signal

cat /fgedudb/pgdata/fgedudb/postgresql.auto.conf

```


在 cn2 启动备库:


```

pg_ctl -D /fgedudb/pgdata/fgedudb -l /fgedudb/pgdata/fgedudb/startup.log start

```


回到 cn1 验证复制状态,重点看 state 是否为 streaming、replay_lsn 是否持续追平 sent_lsn:


```

SELECT application_name, client_addr, state, sync_state,

       pg_wal_lsn_diff(sent_lsn, replay_lsn) AS lag_bytes

FROM pg_stat_replication;

```


还可以在 cn2 上反向确认备库视角的复制状态。备库没有 walsender,但可以通过 pg_stat_wal_receiver 查看接收进度,以及通过 pg_last_wal_replay_lsn 查看回放位点:


```

SELECT status, receiver_start_lsn, received_lsn, last_msg_send_time

FROM pg_stat_wal_receiver;


SELECT pg_is_in_recovery() AS is_standby,

       pg_last_wal_replay_lsn() AS replay_lsn;

```


双主架构中,time_line(时间线)概念必须理解:每次提升备库,PostgreSQL 都会在新的时间线上继续生成 WAL,并记录一份时间线历史文件。查询当前时间线可以用 `pg_controldata` 或 SQL 中的 `pg_current_wal_lsn` 配合日志确认。节点回归时,如果旧主库曾经写过新主时间线之外的数据,就必须用 pg_rewind 或全量重克隆,这在 2.10 节会详细展开。


> 

> 网上搜索风哥教程可以学习全套数据库教程


此时 cn1 为主、cn2 为备。为了实现故障时的双向切换,需要在 cn1 上也预置反向连接串。这段配置在 cn1 处于主库角色时不会生效,只有 cn1 被降级为备库时才会被读取:


```

primary_conninfo = 'user=replicator password=Repl@2026 host=192.168.10.12 port=5432 application_name=fgedu-net-cn1'

primary_slot_name = 'slot_from_cn1'

```


风哥的工程建议:平时不要在 cn1 上保留 standby.signal 文件,由切换脚本在降级时统一生成,避免误判角色。另外把主备的 SSH 互信配好,后续自动切换脚本要跨机器执行命令:


```

su - fgedu

ssh-keygen -t rsa -b 4096 -N '' -f /home/fgedu/.ssh/id_rsa

ssh-copy-id fgedu@192.168.10.12

ssh-copy-id fgedu@192.168.10.11

```


验证复制真正追平还有一个实用技巧:在 cn1 上连续写入,然后立刻在 cn2 上查询同一张表,观察数据是否在几百毫秒内可见。风哥习惯用一张专门的时间戳表做这个验证:


```

-- cn1 主库

INSERT INTO t_sync_test(ts) VALUES (now());

-- cn2 备库

SELECT ts FROM t_sync_test ORDER BY ts DESC LIMIT 1;

```


如果备库总是读不到最新写入,就要回到延迟指标查原因:网络带宽、备库 CPU 是否被读流量打满、备库磁盘 fsync 是否过慢、以及归档进程是否抢占了 IO。复制延迟不是单一指标能解释的,风哥建议把 sent_lsn、write_lsn、flush_lsn、replay_lsn 四个位点全部采集下来,哪个位点落后就定位哪一段链路。


### 2.5 Pgpool-II 安装


Pgpool-II 同样以源码方式安装到 `/fgedudb/pgpool`,两台机器各装一份。编译时 `--enable-watchdog` 启用仲裁与 VIP 漂移能力,`--enable-memcached` 启用内存缓存支持:


> 

> 网上搜索风哥教程可以学习全套数据库教程


```

su - fgedu

cd /fgedudb/soft

tar xf pgpool-II-*.tar.gz

cd pgpool-II-*/


./configure --prefix=/fgedudb/pgpool \

    --with-pgsql=/fgedudb/pg \

    --sysconfdir=/fgedudb/pgpool/conf \

    --enable-watchdog \

    --enable-memcached


make -j8

make install

```


### 2.6 Pgpool-II 核心配置


主配置文件 `/fgedudb/pgpool/conf/pgpool.conf`,两台机器各一份,差异仅在 hostname、watchdog 优先级与对端地址。风哥逐段给出并解释关键项:


```

listen_addresses = '*'

port = 9999

socket_dir = '/fgedudb/pgpool/run'

pcp_listen_addresses = '*'

pcp_port = 9898


num_init_children = 320

max_pool = 4

child_max_connections = 100

child_life_time = 300

client_idle_limit = 0


backend_hostname0 = '192.168.10.11'

backend_port0 = 5432

backend_weight0 = 1

backend_data_directory0 = '/fgedudb/pgdata/fgedudb'

backend_flag0 = 'ALLOW_TO_FAILOVER'


backend_hostname1 = '192.168.10.12'

backend_port1 = 5432

backend_weight1 = 1

backend_data_directory1 = '/fgedudb/pgdata/fgedudb'

backend_flag1 = 'ALLOW_TO_FAILOVER'


enable_pool_hba = on

pool_passwd = 'pool_passwd'

authentication_timeout = 60


load_balance_mode = on

master_slave_sub_mode = 'stream'

sr_check_period = 5

sr_check_user = 'fgedu'

sr_check_password = 'Fgedu@2026'

health_check_period = 5

health_check_timeout = 30

health_check_user = 'fgedu'

health_check_password = 'Fgedu@2026'


failover_command = '/fgedudb/pgpool/scripts/failover.sh %d %h %D %P'

follow_primary_command = ''


use_watchdog = on

wd_hostname = '192.168.10.11'

wd_port = 9000

wd_priority = 1

delegate_IP = '192.168.10.100'

if_cmd_path = '/usr/sbin/ip'

arping_path = '/usr/sbin/arping'

wd_heartbeat_port = 9694

heartbeat_destination0 = '192.168.10.12'

heartbeat_destination_port0 = 9694

heartbeat_device0 = 'eth0'


other_pgpool_hostname0 = '192.168.10.12'

other_pgpool_port0 = 9999

other_wd_port0 = 9000

```


关键项解读:`num_init_children = 320` 是 Pgpool-II 常驻子进程数,`max_pool = 4` 是每个子进程缓存的后端连接数,两者相乘 1280 是理论并发上限,不能超过后端 PG 的 `max_connections`;`backend_weight0/1` 是负载均衡权重,1:1 表示两个后端各承担一半读流量;`load_balance_mode = on` 开启读写分离,`master_slave_sub_mode = 'stream'` 指定后端为流复制模式,这是读写分离正确工作的前提;`health_check_period = 5` 每 5 秒探测后端存活,`health_check_timeout = 30` 是探测超时;`sr_check_period = 5` 每 5 秒检查流复制状态,用于识别主备角色;`failover_command` 在故障时被调用,占位符 `%d` 是故障节点编号、`%h` 是故障节点主机名、`%D` 是数据目录、`%P` 是旧主节点编号。


> 

> 上51CTO搜索风哥可以学习全套数据库教程


watchdog 部分:`wd_priority` 决定谁优先当 leader,数值小者优先;`delegate_IP` 是漂移的虚拟 IP;`wd_heartbeat_port = 9694` 是节点间心跳端口;`heartbeat_destination0` 指向对端;`other_pgpool_hostname0` 声明对端 Pgpool-II 实例。cn2 上对应的差异项为:`wd_hostname = '192.168.10.12'`、`wd_priority = 2`、`heartbeat_destination0 = '192.168.10.11'`、`other_pgpool_hostname0 = '192.168.10.11'`。


风哥再讲透 watchdog 的工作机制,方便读者排障。watchdog 节点之间通过两条通道保持联络:一条是 9694 端口的 UDP 心跳,另一条是 9000 端口的 TCP 协商通道。每个节点每 3 秒广播一次心跳,超过一定周期收不到对端心跳就判定对端失联。节点间还会协商出一个 leader,只有 leader 有资格持有 delegate IP;普通节点如果发现 leader 失联且自己无法获得多数派支持,就会主动降级自身状态为 wd_dead,拒绝提供任何转发服务,而不是强行接管 VIP。这套机制的核心目的只有一个:**任何时刻只允许一个节点持有 VIP 并提供写服务。** 排障时如果发现 VIP 不在预期节点上,第一件事就是查看 `pcp_watchdog_info` 输出的各节点 wd_priority、status 与 master 标识。


pool_hba.conf 控制客户端到 Pgpool-II 的认证:


```

local   all         all                               trust

host    all         all         127.0.0.1/32          scram-sha-256

host    all         all         0.0.0.0/0             scram-sha-256

```


生成密码文件。Pgpool-II 客户端认证时,会拿 pool_passwd 里的密码去连后端 PG,所以密码格式必须与后端 pg_hba.conf 的认证方式一致,这里统一用 scram:


```

cp -a /fgedudb/pgpool/conf/pool_passwd /fgedudb/pgpool/conf/pool_passwd.bak

pg_md5 --md5auth --username=fgedu Fgedu@2026

```


故障切换脚本 `/fgedudb/pgpool/scripts/failover.sh`,风哥给出一个最小可用版本。脚本通过 SSH 到新主节点执行 promote,把备库提升为主库:


```

#!/bin/bash

FAILED_NODE=$1

NEW_PRIMARY_HOST=$2

NEW_PRIMARY_DATA_DIR=$3

NEW_PRIMARY_NODE=$4


LOG=/fgedudb/pgpool/log/failover.log

echo "$(date) failover: failed_node=$FAILED_NODE new_primary=$NEW_PRIMARY_HOST" >> $LOG


ssh -o StrictHostKeyChecking=no fgedu@$NEW_PRIMARY_HOST \

    "/fgedudb/pg/bin/pg_ctl promote -D $NEW_PRIMARY_DATA_DIR" >> $LOG 2>&1


exit 0

```


赋予执行权限并启动:


```

chmod +x /fgedudb/pgpool/scripts/failover.sh

pgpool -n -d > /fgedudb/pgpool/log/pgpool.log 2>&1 &

```


检查 watchdog 与后端节点状态:


```

pcp_check_watchdog 9898 fgedu Fgedu@2026

psql -h 192.168.10.100 -p 9999 -U fgedu fgedudb -c "SHOW pool_nodes;"

```


如果 `SHOW pool_nodes` 返回的 status 不是 up,或者连接 VIP 失败,按顺序排查:第一,后端 PG 是否真的在监听(`pg_isready -h 192.168.10.11`);第二,pool_hba.conf 与 pool_passwd 是否配置到位;第三,Pgpool-II 日志里是否有后端认证失败的记录。风哥的建议是先把两台机器的 Pgpool-II 都启动起来,再验证 VIP,因为 watchdog 需要看到对端在线才会把 delegate IP 挂出来。


### 2.7 VIP 漂移:watchdog 与 Keepalived 双方案


风哥本套风哥教程的主方案用 Pgpool-II watchdog 内置的 delegate IP 完成 VIP 漂移,这样不需要额外组件,watchdog 本身还能做节点仲裁。验证 VIP 是否已绑定:


```

ip addr show eth0

arping -c 3 -I eth0 192.168.10.100

```


如果你希望 VIP 由独立的 Linux HA 管理,则用 Keepalived(VRRP)。安装与启动非常简单,两台机器执行:


```

yum install -y keepalived

systemctl enable --now keepalived

```


此时 vrrp_script 探测 Pgpool-II 存活状态,探测失败则降低本机优先级,让对端接管 VIP:


> 

> 风哥 itpux-com


```

vrrp_script c k_pgpool {

    script "/usr/bin/pgpool -c /fgedudb/pgpool/conf/pgpool.conf status"

    interval 2

    weight -20

}


vrrp_instance VI_1 {

    state MASTER

    interface eth0

    virtual_router_id 51

    priority 101

    advert_int 1

    authentication {

        auth_type PASS

        auth_pass Fgedu@2026

    }

    virtual_ipaddress {

        192.168.10.100/24

    }

    track_script {

        c k_pgpool

    }

}

```


cn2 上 state 为 BACKUP、priority 为 100。两套方案二选一,不要同时启用两套 VIP 管理,否则切换时会互相打架。风哥在实战中见过同时启用 watchdog delegate IP 与 Keepalived 导致 VIP 抖动的事故,务必引以为戒。


### 2.8 读写分离验证与压测


连上 VIP,验证读写分离是否按预期工作:


```

psql -h 192.168.10.100 -p 9999 -U fgedu fgedudb

```


```

CREATE TABLE IF NOT EXISTS t_load (

    id int GENERATED ALWAYS AS IDENTITY PRIMARY KEY,

    node_name text,

    ts timestamptz DEFAULT now()

);


INSERT INTO t_load(node_name) VALUES ('write_test');


SELECT * FROM pg_stat_is_in_recovery();

SHOW pool_node_name;


SELECT pool_backend_id, database, username

FROM pg_stat_frontends;

```


`pg_stat_is_in_recovery()` 返回 t 表示当前连接落在备库,返回 f 表示落在主库;`SHOW pool_node_name` 直接显示后端节点名。多次执行 SELECT 查询,观察连接是否在两个后端之间轮询,这是负载均衡生效的直接证据。


风哥把 `SHOW pool_nodes` 的典型输出贴出来,读者对照理解。正常状态应该是:


```

node_id | hostname     | port | status | lb_weight | role

--------+--------------+------+--------+-----------+---------

0       | 192.168.10.11| 5432 | up     | 0.500000  | primary

1       | 192.168.10.12| 5432 | up     | 0.500000  | standby

```


status 为 up 表示节点健康,role 区分主备;lb_weight 各 0.5 表示读流量五五开。故障切换后,node 0 的 status 会变为 down,node 1 的 role 变为 primary。风哥要求运维同学把这个命令做成例行巡检项:`SHOW pool_nodes`、`SHOW pool_processes`、`pcp_watchdog_info` 三个命令组合起来,一分钟之内就能确认集群的整体健康度。


> 

> 风哥教程 113257174


压测验证吞吐能力。用 pgbench 初始化 200 倍基准规模的数据,分别做只读与读写混合压测:


```

cd /fgedudb/soft/postgresql-*/contrib/pgbench

make && make install


pgbench -h 192.168.10.100 -p 9999 -U fgedu -i -s 200 fgedudb


pgbench -h 192.168.10.100 -p 9999 -U fgedu -S -c 64 -j 8 -T 60 fgedudb


pgbench -h 192.168.10.100 -p 9999 -U fgedu -c 64 -j 8 -T 60 fgedudb

```


`-S` 表示只读模式,`-c 64` 是 64 个并发客户端,`-j 8` 是 8 个线程,`-T 60` 是压测 60 秒。风哥在 64G/8CPU 规格下的实测参考:纯只读场景两台后端各承担约一半读 QPS,整体吞吐约为单节点的 1.8 倍;读写混合场景写全部落主、读在两节点轮询,整体 TPS 提升四到六成,瓶颈落在主库 WAL 写入。这印证理论篇的结论:**负载均衡只能水平扩展读,写扩展必须靠业务分片或分布式扩展组件。**


压测期间可以同时开一个终端实时观察两台 PG 的负载:


```

top -d 2 -b | grep -E "postgres|pgbench"

```


### 2.9 故障切换演练


**演练一:主库进程被杀。** 在 cn1 执行:


```

pg_ctl -D /fgedudb/pgdata/fgedudb stop -m immediate

```


观察 Pgpool-II 日志,应能在 health_check 周期内(5 秒探测加 30 秒超时)检测到 cn1 失效,触发 failover 脚本,把 cn2 提升为新主并漂移 VIP:


```

tail -f /fgedudb/pgpool/log/pgpool.log

psql -h 192.168.10.100 -p 9999 -U fgedu fgedudb -c "SHOW pool_nodes;"

```


`SHOW pool_nodes` 的输出中,cn2 对应的节点 role 应从 standby 变为 primary,cn1 变为 down。注意:这个演练场景下 RPO 取决于切换瞬间主库 WAL 是否已全部送达备库,异步模式下存在丢失最后一点已提交事务的可能,这正是生产环境建议上同步复制的理由。


切换完成后,风哥建议立刻做三件事:一是从应用视角验证读写是否恢复正常,执行一条 INSERT 再执行一条 SELECT,确认写已落到新主、读已在两个后端间正常分发;二是对比切换前后的序列与时间,确认业务数据没有明显缺口;三是把 failover.log 里的切换耗时记录下来,作为后续调优的基线。整个切换过程理想状态下应该在 10 秒量级完成,如果超过 30 秒,就要排查是健康检查周期太长、SSH 免密失效,还是新主 promote 后 WAL 追平耗时过长。


**演练二:网络分区模拟。** 在 cn1 上丢弃与 cn2 的心跳包:


> 

> 上51CTO搜索风哥可以学习全套数据库教程


```

iptables -A INPUT -s 192.168.10.12 -p tcp --dport 9694 -j DROP

iptables -A OUTPUT -d 192.168.10.12 -p tcp --dport 9694 -j DROP

```


双节点互相失联且无第三方仲裁时,watchdog 无法达成多数派,VIP 保持原位不漂移,业务短暂受影响,但不会出现双主同时写——这正是仲裁的价值。恢复网络:


```

iptables -F

```


### 2.10 节点回归与回切


cn1 修复后不能直接启动成主,必须降级为 cn2 的备库再重新加入。最稳妥的方式是用 pg_basebackup 重新克隆:


```

# cn1 上

rm -rf /fgedudb/pgdata/fgedudb/*

pg_basebackup -h 192.168.10.12 -p 5432 -U replicator \

    -D /fgedudb/pgdata/fgedudb -Fp -Xs -P -R -n

pg_ctl -D /fgedudb/pgdata/fgedudb start

```


把节点重新挂回 Pgpool-II:


```

pcp_attach_node 9898 fgedu Fgedu@2026 0

```


验证新主备关系与复制延迟:


```

SELECT application_name, state, sync_state,

       pg_wal_lsn_diff(sent_lsn, replay_lsn) AS lag_bytes

FROM pg_stat_replication;

```


如果 cn1 故障时间很短、数据与 cn2 高度接近,还有一条更快的路径:用 pg_rewind 做时间线回退,把 cn1 快速倒回 cn2 的时间线末尾,再以备库身份启动,避免全量克隆。风哥把步骤给出,供读者在生产环境选用:


```

# cn1 上,停库后执行

pg_ctl -D /fgedudb/pgdata/fgedudb stop -m fast


/fgedudb/pg/bin/pg_rewind \

    -D /fgedudb/pgdata/fgedudb \

    --source-server="host=192.168.10.12 port=5432 user=fgedu dbname=fgedudb" \

    -P


touch /fgedudb/pgdata/fgedudb/standby.signal

pg_ctl -D /fgedudb/pgdata/fgedudb start

```


pg_rewind 的原理是比对两边的 WAL 时间线,把旧主库上那些新主没有的 WAL 变更回退掉,保留共同的部分。它是双主架构里节点回归的加速器,但要求旧主库必须能读取 WAL,且新主库上存在时间线历史文件。回归完成后同样用 `pcp_attach_node` 挂回 Pgpool-II。


风哥提醒三个 pg_rewind 的常见翻车点:第一,旧主库必须以 fast 或 immediate 方式停掉再执行,如果旧主库还在运行,数据文件可能正在变化,回退结果不可信;第二,`--source-server` 连接的用户需要具备 superuser 或 pg_rewind 权限,风哥在实战中习惯直接用超级用户;第三,回退完成后必须检查 `pg_controldata` 里的 Latest checkpoint location 与时间线是否与目标一致,再决定是否启动。如果 pg_rewind 因 WAL 缺失而失败,就不要纠结,直接退回 pg_basebackup 全量克隆,两条路都是生产可用的。


### 2.11 复制延迟监控与备份配合


监控脚本,挂 cron 每分钟执行。风哥给出的脚本同时输出当前角色与回放延迟字节数:


```

cat > /fgedudb/scripts/check_repl_lag.sh <<'EOF'

#!/bin/bash

PSQL=/fgedudb/pg/bin/psql

$PSQL -U fgedu -d fgedudb -tA <<SQL

SELECT now(), pg_is_in_recovery(),

       pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn)

FROM pg_stat_replication;

SQL

EOF

chmod +x /fgedudb/scripts/check_repl_lag.sh

echo "* * * * * /fgedudb/scripts/check_repl_lag.sh" >> /var/spool/cron/root

```


告警阈值建议:replay lag 超过 16MB 持续 3 分钟即告警,这个量级说明备库回放跟不上,故障切换时数据丢失风险变大。监控指标还要覆盖:复制槽数量与状态、pg_wal 目录大小、备库回放进程是否停滞、watchdog 心跳是否正常。


风哥把一套完整的巡检 SQL 给出来,读者可以做成定时任务采集:第一,`SELECT slot_name, active, restart_lsn FROM pg_replication_slots;` 检查复制槽是否处于 active 状态,长期 inactive 的槽要警惕 WAL 堆积;第二,`SELECT pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), pg_current_wal_flush_lsn()));` 观察 WAL 落盘差距;第三,`SELECT count(*) FROM pg_stat_activity WHERE state = 'active' AND now() - state_change > interval '5 minutes';` 统计超过 5 分钟的活跃长事务;第四,`SELECT datname, age(datfrozenxid) FROM pg_database;` 监控事务 ID 回卷风险,age 逼近 20 亿时必须强制 vacuum。这四个指标配合前面的延迟指标,基本覆盖了双主架构的全部风险面。


备份方面,每日基础备份加持续 WAL 归档:


```

cat > /fgedudb/scripts/base_backup.sh <<'EOF'

#!/bin/bash

BACKUP_DIR=/fgedudb/backup/base_$(date +%Y%m%d)

mkdir -p $BACKUP_DIR

pg_basebackup -h 192.168.10.11 -U replicator -D $BACKUP_DIR -Ft -z -P

find /fgedudb/backup -maxdepth 1 -type d -mtime +7 -exec rm -rf {} \;

EOF

chmod +x /fgedudb/scripts/base_backup.sh

echo "0 2 * * * /fgedudb/scripts/base_backup.sh" >> /var/spool/cron/root

```


WAL 归档在 2.3 节已通过 `archive_command` 配置,归档目录为 `/fgedudb/pg_archive`。基础备份加持续 WAL 归档构成完整的 PITR 体系。风GB须强调:**HA 解决机器挂了业务不停,备份解决数据被误删了能找回,两者不可互相替代。** 有团队只做 HA 不做备份,结果一次误删除操作把两个节点的数据一起清掉,教训极其深刻。


这里把 PITR 恢复演练的关键命令也给出,读者在测试环境验证一次就有底。假设要把数据库恢复到今天凌晨 1 点的状态:


```

# 用基础备份重建数据目录

rm -rf /fgedudb/pgdata/fgedudb

mkdir -p /fgedudb/pgdata/fgedudb

tar -xzf /fgedudb/backup/base_$(date +%Y%m%d)/base.tar.gz -C /fgedudb/pgdata/fgedudb


# 编辑 recovery.signal 与恢复目标

touch /fgedudb/pgdata/fgedudb/recovery.signal

echo "restore_command = 'cp /fgedudb/pg_archive/%f %p'" >> /fgedudb/pgdata/fgedudb/postgresql.auto.conf

echo "recovery_target_time = '2026-10-21 01:00:00'" >> /fgedudb/pgdata/fgedudb/postgresql.auto.conf


# 启动,回放到目标时间后自动停在一个可读状态

pg_ctl -D /fgedudb/pgdata/fgedudb start

```


恢复完成后实例会停在 recovery_target_time 指定时刻的一致性点,可以用只读方式验证数据,确认无误后再打开写权限。整个 PITR 体系的要点是:基础备份要周期性更新、WAL 归档不能断档、恢复目标要精确到秒级。


> 

> 风哥数据库教程 itpux-com


### 2.12 常见故障排查


风哥把实战中最常遇到的几类问题整理成排查手册。


**故障一:Pgpool 报 "connection is not accepting"。** 绝大多数是 pool_hba.conf 与后端 pg_hba.conf 认证方式不一致,或者 pool_passwd 里存的是 md5 而后端要求 scram-sha-256。解决方法:用 `pg_md5 -m -u fgedu Fgedu@2026` 重新生成 scram 格式的密码串,再 `pgpool reload`。


**故障二:切换后新主拒绝写入。** standby.signal 未清理导致实例仍以备库模式运行。手工场景执行 `rm /fgedudb/pgdata/fgedudb/standby.signal && pg_ctl reload`。


**故障三:备库报 "conflict with recovery"。** 备库长事务与主库 DDL 冲突导致查询被杀。长期方案开 `hot_standby_feedback = on`(已配),但要注意长事务会反压主库 vacuum,导致表膨胀与 WAL 堆积,所以还要配套长事务监控。


**故障四:VIP 漂移后业务连不上。** 检查新主 arping 是否发出、客户端侧 ARP 是否老化。可手动 `arping -c 3 -I eth0 192.168.10.100` 刷新。


**故障五:复制槽堆积导致磁盘写满。** 备库长期离线时主库为保留 WAL 而无限堆积。处理:确认备库无法恢复则删除对应复制槽 `SELECT pg_drop_replication_slot('slot_from_cn2');`,并尽快重建备库。


**故障六:watchdog 出现两个 leader。** 多为心跳网络中断且仲裁配置不完整。排查 heartbeat 端口连通性、`other_pgpool_hostname0` 是否配对正确、wd_priority 是否唯一。双节点架构下这是高危信号,必须靠仲裁机解决。


**故障七:应用间歇性报连接超时。** 多半不是数据库问题,而是连接池耗尽或 VIP 漂移期间的抖动。先看 `SHOW pool_processes` 确认 Pgpool-II 子进程使用率,再看 `netstat -anp | grep 9999` 确认 VIP 上的连接堆积,最后查应用侧连接池配置的 maxActive 是否超过了 num_init_children 的可用上限。


**故障八:切换后部分应用还连在旧主库上。** 这是因为应用侧缓存了后端真实 IP,没有走 VIP。排查应用数据源配置,强制统一指向 `192.168.10.100:9999`,并在切换演练后主动刷新应用连接池。风哥见过多次这类问题:数据库侧切换完美,应用侧十分钟连不上,最后发现是连接串里写死了后端 IP。


### 2.13 参数调优清单(64G/8CPU 规格)


风哥把实战中验证过的关键参数整理成清单,读者可以直接对照调整:


- `shared_buffers = 16GB`(物理内存的 25%)

- `effective_cache_size = 48GB`(物理内存的 75%)

- `maintenance_work_mem = 2GB`(autovacuum、建索引等维护操作使用)

- `work_mem = 16MB`(注意 1000 连接下多并发排序合计可达十几 GB,不宜开大)

- `wal_buffers = 64MB`

- `max_wal_size = 16GB`、`min_wal_size = 4GB`

- `checkpoint_completion_target = 0.9`

- `max_connections = 1000`

- `autovacuum = on`、`autovacuum_max_workers = 4`、`autovacuum_naptime = 30s`

- `bgwriter_lru_maxpages = 1000`、`bgwriter_lru_multiplier = 2.0`

- `random_page_cost = 1.1`(SSD 场景调低,利于索引扫描)

- `effective_io_concurrency = 200`(SSD 并发 IO)

- `pgpool num_init_children = 320`、`max_pool = 4`,理论并发 1280,不得超出后端 PG 连接上限

- 三节点生产架构下启用 `synchronous_commit = on`、`synchronous_standby_names = 'ANY 1 (node1,node2)'`


### 2.14 上线前检查清单


风哥结合多年交付经验,把这套架构上线前必须逐项确认的检查清单整理如下,读者可以打印出来对着打勾:


**复制层检查:** 第一,两个方向的 primary_conninfo 是否都配置正确,application_name 是否唯一;第二,复制槽是否都已创建且处于 active;第三,`SELECT * FROM pg_stat_replication;` 确认 state 为 streaming、lag 接近零;第四,手工模拟一次备库追平,确认 WAL 归档没有断档。


**切换层检查:** 第一,failover 脚本在两台机器上都具备可执行权限,脚本里的 SSH 免密已双向打通;第二,健康检查周期与超时是否符合业务 RTO 预期;第三,演练一次 kill 主库进程,验证自动切换时间与 failover.log 输出;第四,演练一次网络分区,确认 watchdog 不会把 VIP 漂到不可达节点。


**连接层检查:** 第一,pool_passwd 密码格式与后端 pg_hba.conf 的认证方式一致;第二,num_init_children 与 max_pool 的乘积低于后端 max_connections,留出至少两成余量;第三,应用连接串统一指向 VIP 与 9999 端口,不直接连后端 IP;第四,读写分离后写后读场景已按 1.6 节的方案处理。


**备份与监控检查:** 第一,基础备份与 WAL 归档已按计划执行并验证可恢复(风哥强烈建议每季度做一次真实的恢复演练,而不是只看备份文件存在);第二,延迟、复制槽、长事务、WAL 堆积四类告警已配置并测试触发;第三,关键日志路径已归档进日志系统,包括 PostgreSQL 主日志、startup.log、pgpool.log、failover.log。


**安全与合规检查:** 第一,数据库账号密码满足复杂度要求,复制账号与业务账号分离;第二,防火墙只放通业务与复制所需端口,VIP 段做好访问控制;第三,所有配置文件由专人维护,变更走评审流程。以上条目全部通过,这套架构才算达到风哥认为的可以上生产的标准。


---


## 第三部分 总结


风哥针对本文总结如下:PostgreSQL 的负载均衡分发与双主 HA 架构,本质是复制层、故障切换层、连接路由层、VIP 漂移层四层协作的产物,没有任何单一组件能通吃。选型顺序应该是:先定复制形态,再定故障切换与仲裁机制,最后补连接路由与 VIP 管理。


实战中最容易踩的三个坑:第一,双节点强行上同步复制且无第三方仲裁,网络分区必然脑裂,生产必须三节点或独立仲裁机;第二,读写分离做了一半,没有处理写后读一致性,应用写完立刻读备库拿到旧数据;第三,连接数规划失误,Pgpool-II 的 num_init_children 乘 max_pool 超过后端 max_connections,导致后端连接被打满,负载均衡反而变成瓶颈。


风哥本套风哥教程以两台 64G/8CPU 规格的机器(fgedu-net-cn1、fgedu-net-cn2)完整演示了从系统初始化、源码编译、双向流复制、Pgpool-II 连接池与读写分离、watchdog 防脑裂到故障切换、节点回归与 pg_rewind 回切的全流程。按本教程操作完成后,通过 VIP `192.168.10.100:9999` 对外提供的,就是一个具备自动故障切换、读写负载均衡、连接池复用能力的 PostgreSQL 高可用集群。


最后,风哥再强调一次架构取舍:双主 HA 解决的是"机器不靠谱"的问题,负载均衡解决的是"单机不够快"的问题,而备份解决的是"人不可靠"的问题。三者都要有,缺一不可。回到开头提出的三个问题:RPO 决定复制模式,RTO 决定切换链路与健康检查周期,读写比决定连接层与读副本的投入。把这三个答案写进架构评审表,让每一次选型都有据可依,而不是凭感觉堆组件——这是风哥十几年里最想传达的经验。后续风哥会在 FGMT 系列中继续展开 Patroni 加 etcd 的云原生方案、PITR 时间点恢复与 HA 的结合,以及跨城双活的网络与延迟权衡,敬请持续关注。


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/9664900/viewspace-3186253/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论
风哥Oracle,MySQL,PostgreSQL,Linux,大数据,国产数据库,中间件等教程

注册时间:2010-10-26

  • 博文量
    78
  • 访问量
    406650