PostgreSQL 主从流复制搭建与切换演练
PostgreSQL 的物理流复制是它最成熟的高可用底座:主库把 WAL 按记录流式发给备库回放,备库可分担只读查询,主库故障时提升备库即可对外服务。本文以 PostgreSQL 16、一主一备(db-01 主、db-02 备)为例,走完搭建、验证、延迟监控与计划切换的完整动作,并把生产里最常见的 WAL 堆积问题讲透。这套流程不依赖任何外部组件,先把原生复制跑稳,再考虑上 Patroni 等自动故障转移工具。
模型速览:先把概念说清
流复制复制的是 WAL(预写日志)的物理变更,所以主备库的大版本、操作系统与 CPU 架构必须一致,不支持跨版本与逻辑筛选——这是它与逻辑复制的根本区别。默认异步模式下主库提交不等备库确认,性能最好,但主库宕机可能丢最后一段未发送的事务;对丢数据零容忍的场景要用同步复制(synchronous_standby_names),代价是备库抖动会反压主库写入。先按异步跑通,再按业务的容忍度决定要不要上同步。
前置条件
- 两台节点 db-01、db-02 已装好同版本 PostgreSQL,数据目录 /data/pgdata,服务当前为空库或可停机初始化;
- 两台 5432 端口互通,主机名可解析,时间同步完成;
- 已规划复制专用账号 repl 与口令,并确认数据盘容量能容纳至少两天的 WAL 量。
第一步:主库参数调整
流复制需要主库改三处配置。编辑 postgresql.conf(只列与复制相关的关键项):
listen_addresses = '*'
# replica 是流复制的最低要求;logical 只在逻辑复制时才需要
wal_level = replica
# 备库数量 + 余量;每个备库的 walsender 占一个连接
max_wal_senders = 5
# 归档兜底:流复制断了靠归档补 WAL,目录放独立路径
archive_mode = on
archive_command = 'test ! -f /data/pgarchive/%f && cp %p /data/pgarchive/%f'
# WAL 保留下限,防止备库短暂掉线后找不到需要的 WAL(16 已支持按大小保留)
min_wal_size = 1GB
max_wal_size = 8GB
再在主库创建复制账号,并为 db-02 建一个物理复制槽。复制槽的意义是:主库按备库已确认的位置保留 WAL,备库重启后能续传;代价是备库长期不回来时 WAL 会把主库磁盘堆满(见常见问题)。
-- 创建复制专用账号(口令请替换为实际值)
CREATE ROLE repl WITH REPLICATION LOGIN PASSWORD '请替换为实际口令';
-- 为备库创建物理复制槽
SELECT pg_create_physical_replication_slot('db02_slot');
第二步:pg_hba 放行复制连接
复制连接走的是特殊的 replication 伪数据库条目,与普通业务连接分开写,来源只允许备库地址:
# TYPE DATABASE USER ADDRESS METHOD
host replication repl 192.0.2.12/32 scram-sha-256
# 主库改完配置后重启(wal_level 等参数改动必须重启才生效)
systemctl restart postgresql-16
第三步:pg_basebackup 拉备库
在 db-02 上停掉本机实例、清空数据目录,用 pg_basebackup 从主库拉一份基础备份。-R 参数会自动写好 standby.signal 与主库连接串,是 12 之后的标准做法,不要再手工维护 recovery.conf(该文件在 12 已取消):
systemctl stop postgresql-16
rm -rf /data/pgdata/*
# PGPASSWORD 仅为示例,生产建议用 .pgpass 文件(权限 0600)
PGPASSWORD='请替换为实际口令' pg_basebackup \
-h db-01 -p 5432 -U repl -D /data/pgdata \
-Fp -Xs -P -R -S db02_slot -C
chown -R postgres:postgres /data/pgdata
chmod 700 /data/pgdata
systemctl start postgresql-16
其中 -Xs 让基础备份自带启动所需的 WAL,避免备库刚启动就去等流;-S 与 -C 指定并创建复制槽(如已在主库建好则去掉 -C)。primary_conninfo 会被 -R 写进 postgresql.auto.conf,备库身份由数据目录里的 standby.signal 空文件标识——这个文件在,就是备库;在,就永远以回放模式启动。
验证:两边对状态
主备两侧各查一个视图,字段读法一次讲清:
-- 主库上执行:看备库连接与落后量
SELECT client_addr, state, sync_state,
pg_current_wal_lsn() - replay_lsn AS replay_lag_bytes
FROM pg_stat_replication;
-- state=streaming 才表示流复制正在跑;sync_state=async 是异步模式
-- replay_lag_bytes 是备库还没回放的字节数,正常应接近 0
-- 备库上执行:看接收进程状态
SELECT status, sender_host,
pg_last_wal_receive_lsn() - pg_last_wal_replay_lsn() AS pending_bytes
FROM pg_stat_wal_receiver;
-- status=streaming、pending_bytes 接近 0 即为健康
再补一个业务侧验证:在主库建一张测试表写几行,切到备库立刻能查到(备库默认可读),延迟应在秒内。用完删掉测试表。
延迟监控的口径
字节差适合机器告警,业务更关心时间差:备库执行 pg_last_xact_replay_timestamp() 与主库 now() 的差值即回放时间延迟。不落库的简易做法是心跳表法——主库每秒更新一张心跳表的时间戳,备库读出来与当前时间比。告警阈值按业务定,一般持续超过 30 秒就要有人介入看是主库写入突增还是备库 IO 瓶颈。
计划切换:标准动作顺序
计划内切换(比如主库要做系统升级)的顺序不能乱:
- 通知业务进入短暂停写窗口,确认主库已无活跃写事务;
- 在主库查 pg_stat_replication,确认备库 replay_lsn 已追平到主库当前 LSN(差值为 0);
- 停主库服务(正常停库会发出最后一个检查点与 WAL 结束信号,备库回放完后处于可提升的干净状态);
- 在 db-02 执行提升:
# 方式一:命令提升
sudo -u postgres /usr/pgsql-16/bin/pg_ctl promote -D /data/pgdata
# 方式二:SQL 提升
# SELECT pg_promote();
提升成功后 standby.signal 被自动移除,备库变为主库可写。随后处理旧主:原来的 db-01 不能直接以主库身份再启动(会与新主分叉成两条时间线),要么用 pg_rewind 把它倒回新主的时间线再改造成新备库,要么重新 pg_basebackup 拉一份。最后一步是把应用连接指向新主——无论走 VIP、DNS 还是连接串配置,都要在切换清单里写明由谁改、改完怎么验证。故障切换少了前两步的从容,其余动作相同,但提升前必须先确认旧主真的不可用,避免双主。
与 MySQL GTID 复制的一句对照
两者都是物理/日志流式的思路:PG 的 LSN 对应 MySQL 的 GTID 集合,都是「位点对齐」的语言;区别是 MySQL 复制可按库表过滤、支持跨版本,PG 物理流复制是整实例字节级一致、主备必须同版本——运维 MySQL 出身的人最容易在「想只同步一个库」这一点上对 PG 产生误判。
常见问题
1. WAL 堆积把主库磁盘打满
头号原因就是复制槽:备库下线或网络中断后,槽会无限保留 WAL。处置顺序:先查 pg_replication_slots 的 active 与 restart_lsn 落后量确认是哪个槽在堆;能恢复备库连接就让它追平消费;备库短期回不来的,应果断删掉失联的槽(pg_drop_replication_slot)先保主库活命,备库之后用 pg_basebackup 重拉。治本是给槽的保留量上监控与告警(16 可用 max_slot_wal_keep_size 设硬上限)。
2. 复制中断,备库日志报 WAL 已被回收
备库停太久,需要的 WAL 已被主库回收或归档缺失。先看 archive 目录里是否还有对应段文件可补;没有就只能重拉基础备份。预防办法是复制槽 + 归档双保险都开,不要只开其一。
3. 备库启动失败,提示需要 recovery
多为 basebackup 时漏了 -R 或 standby.signal 丢失,备库以普通模式启动后与主库时间线对不上。停库、补回 standby.signal 与 primary_conninfo 后重启;若已经以主库模式跑过并产生写入,只能重拉,别尝试手工拼接。
4. 主库改了 wal_level 没重启就拉备库
wal_level 属于重启生效参数,未重启时复制连接会被拒。按「改参数 → 重启主库 → 验证参数值 → 再拉备库」的顺序重来,别在半生效状态里排障。
小结
PG 流复制的要点可以收成四句:参数里 wal_level 与复制槽是骨架,pg_hba 的 replication 条目别漏;备库身份只认 standby.signal 一个文件;状态只看两个视图、延迟只认 LSN 差与回放时间戳;切换的难点从来不在 promote 命令,而在追平确认与旧主处理。把这四句落到巡检与切换清单里,一主一备就能稳稳撑起大多数业务的高可用需求。







