PostgreSQL 复制与高可用系列(二):物理流复制实战——从零搭建主从集群
第一期我们搭建了知识框架,认识了 WAL 基石、物理复制与逻辑复制的差异、同步与异步的血肉权衡。
第二期不再纸上谈兵——我们将动手搭建一个完整的流复制主从集群,逐行验证每个配置参数的实际效果。
一、搭建前的脑内基建
物理流复制的核心机制是将数据变更按字节级别复制到备库,这背后涉及三个核心进程的协作。主库上的 walsender 持续向外发送 WAL 流,并为每个备库分配一个独立进程;备库上的 walreceiver 接收传来的 WAL 数据;后台的 startup 进程将这些数据重放到数据文件中。当备库配置为 hot_standby = on 时,重放期间即可处理只读查询,这便是"热备"的技术实质。
在这一原理下,搭建一套主从集群只需完成三件事:全量基础备份初始化备库数据、配置连接与复制参数、启动备库并维持持续同步。
此外,PostgreSQL 12 是一个关键分界岭——recovery.conf 文件被正式废弃,所有配置统一纳入 postgresql.conf,备库模式通过创建 standby.signal 标记文件来声明。操作时务必确认版本差异。
二、环境准备
2.1 节点规划
角色IP 地址主机名PostgreSQL 版本主库192.168.1.10pg-primary16+备库192.168.1.20pg-standby16+两节点均建议使用同一 Linux 发行版,已安装相同版本的 PostgreSQL,并能相互网络可达。若两节点不同时初始化,需确保备库的 data 目录为空。
2.2 复制用户的创建
在主库上创建专用复制用户。REPLICATION 权限与 LOGIN 必须同时授予:
CREATE USER replicator WITH REPLICATION LOGIN ENCRYPTED PASSWORD 'SecureP@ssw0rd'; 主库的 pg_hba.conf 需要显式允许该用户从备库 IP 发起复制连接:
# 考虑主备角色互换,建议主备 pg_hba.conf 保持一致
host replication replicator 192.168.1.20/32 md5 修改完 pg_hba.conf 后,执行 pg_ctl reload 使配置生效。
三、主库配置:开启复制的"开关"
编辑主库的 postgresql.conf,至少配置以下核心参数:
# 基本复制配置
wal_level = replica # 生成足够 WAL 信息,物理复制必需
max_wal_senders = 10 # 最大 walsender 数量,每个复制消费者占用一个
wal_keep_size = 1GB # 主库保留的 WAL 总量,备库离线时的缓冲
synchronous_commit = off # 关闭同步提交(先行配置异步模式)
# 热备支持(备库端生效,主库可选但建议保留)
hot_standby = on 参数说明:wal_level 决定了 WAL 中包含的信息量,物理复制最低需要 replica 级别;max_wal_senders 需要至少大于备库数量,以便后续增加备库或执行 pg_basebackup 备份时留有裕量。wal_keep_size 可用于限制主库保留 WAL 的总量,避免磁盘被 WAL 撑满。
配置修改完成后,执行 sudo systemctl restart postgresql 重启让主库生效。
四、备库初始化:从主库拉取基础数据
4.1 备份主库数据并自动生成备库配置
PostgreSQL 提供了 pg_basebackup 工具,可对正在运行的主库进行一致性热备份,同时在线生成备库所需的连接与配置标记。这一工具不要求访问数据库底层文件系统,只需通过流复制协议连接即可安全完成备份。
由于 pg_basebackup 会覆盖目标目录,如备库的 data 目录已有数据,需先停止备库服务并清理目录:
sudo systemctl stop postgresql
sudo rm -rf /var/lib/postgresql/*/main/* 然后执行 pg_basebackup 从主库拉取完整数据:
sudo -u postgres pg_basebackup -h 192.168.1.10 -D /var/lib/postgresql/16/main
-U replicator -P -v -R -X stream -C -S pgstandby1 各参数含义如下:
参数说明-h / -U指定主库地址与复制用户名-D指定备库数据存放路径-P -v显示备份进度与详细输出-R自动在数据目录中生成 standby.signal 标记文件和 postgresql.auto.conf 中的 primary_conninfo 参数-X stream备份期间流式传输 WAL,确保数据在备份时保持最新-C -S在主库自动创建指定名称的物理复制槽,防止备库尚未接收时 WAL 日志被回收-R 是完成集群构建最关键的开关,它将备库启动所需的一切准备就绪,省去了手动创建标记文件与填充连接信息的后续步骤。
五、启动备库并验证
5.1 备库启动准备
确认备库 postgresql.conf 中已包含 hot_standby = on,确保备库在恢复期间可接受只读查询。pg_basebackup 已将主库的 postgresql.conf 拷贝到备库,但不同硬件环境下可能需要独立调整,切勿盲目沿用主库参数。
检查数据目录中的关键文件:standby.signal 标记文件非空,postgresql.auto.conf 中自动生成了 primary_conninfo 连接字符串。确认无误后启动备库:
sudo systemctl start postgresql 5.2 连通性验证
在主库执行以下 SQL,观察备库的连接状态:
SELECT application_name, state, sync_state,
pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS lag_bytes
FROM pg_stat_replication; 若 state 列为 streaming,说明备库正在持续接收 WAL 流;lag_bytes 可通过 pg_wal_lsn_diff 计算当前主库 WAL 位置与实际已回放 LSN 的差值,反映复制延迟的大小。
5.3 验证备库的只读性
直接在备库上执行查询:
SELECT pg_is_in_recovery(); -- 返回 true 表示备库处于恢复模式
-- 尝试执行写操作时,备库应报错: ERROR: cannot execute INSERT in a read-only transaction 至此,一套最基本的异步流复制集群搭建完成。接下来,我们将深入理解两种工作模式之间的本质差异,并根据业务场景配置合适的同步策略。
六、同步复制:用性能换零数据丢失
6.1 异步模式的局限
目前集群采用异步复制模式:主库在确认事务提交成功时,只保证本地 WAL 已落盘,并不等待备库确认接收。这种模式对主库性能几乎无影响,但主库宕机时可能丢失尚未完成网络传输的 WAL 段数据,RPO > 0。
6.2 切换至同步模式
同步复制模式下,主库提交事务时必须等待至少一个备库确认 WAL 已正确接收并持久化后,才向客户端反馈成功,从而实现 RPO = 0 的数据可靠性。主库配置调整如下:
synchronous_commit = on
synchronous_standby_names = 'FIRST 1 (pgstandby1)' 注意:synchronous_standby_names 中指定的备库名称必须与备库 postgresql.auto.conf 中的 primary_conninfo 里 application_name 字段严格一致。
6.3 同步复制的现实权衡
同步复制在带来高可靠性的同时,至少带来两个问题:
1. 写入延迟增加
由于事务提交需等待网络往返时间(RTT)与备库 fsync 落盘的时间,写入延迟会明显上升,跨地域部署时影响尤其显著。部分业务可将本地同步备库与跨地域异步备库相结合,以缓解该问题。
2. 同步备库故障可能导致主库写入阻塞
当同步备库因宕机或网络中断而不可用时,主库在一段时间内将阻塞所有写操作。
此时可在备库配置中增加 synchronous_standby_names 的"冗余见证":
synchronous_standby_names = 'ANY 1 (s1, s2, s3)' 表示三个备库中任一个确认后即视为同步成功,避免单点依赖导致的全集群阻塞。但请注意,ANY 语法在低于 9.6 的版本中不受支持。
七、复制槽:防止 WAL 被过早回收
7.1 为什么需要复制槽
在备库因网络中断或故障而暂时离线时,主库可能在备库重连前已将备库尚未接收的 WAL 段回收(参数 wal_keep_segments 或 wal_keep_size 限制)。一旦发生这种情况,备库需要从头重建,在数据量巨大的场景下极其耗时。复制槽正是为了解决这一痛点而诞生的机制。
复制槽通过持久化记录每个备库在 WAL 流中的消费位置,强制主库保留所有尚未被其确认的 WAL 日志,从而保证备库即使离线后也能从断开位置继续接收,无需重新全量拷贝。物理复制槽的创建方式有两种:
方式一:动态创建
SELECT pg_create_physical_replication_slot('pgstandby1'); 方式二:在 pg_basebackup 中加入 -C -S slotname该方法可在备份过程中同时创建槽位,便于多备库批量初始化。
7.2 复制槽的管理与风险
复制槽也带来显著风险:如果一个备库彻底损坏或永久离线而忘记删除其对应的复制槽,主库将继续保留该备库所需的所有 WAL 日志,直到磁盘被 WAL 日志撑满。建议设置 max_slot_wal_keep_size 参数,限制单个槽长期离线时保留的 WAL 总量。同时持续监控 pg_replication_slots 视图中非活跃状态槽位的存在时间。
删除不再需要的复制槽:
SELECT pg_drop_replication_slot('slot_name'); 八、进阶场景的配置演进
8.1 延迟复制:人为制造恢复窗口
延迟复制指备库人为延迟应用 WAL 日志,可为主库上的误操作(如误删表)提前预留恢复窗口:
ALTER SYSTEM SET recovery_min_apply_delay = '30min'; 8.2 级联复制:减轻主库的压力
级联复制允许备库将接收到的 WAL 数据再次传递给下游备库,形成"中继站"结构,有效降低主库的连接压力。启用级联备库时,中间层备库需要将 max_wal_senders 调大,并将自身配置为允许接收来自下游备库的 REPLICATION 连接请求。
九、日常监控与运维
9.1 查看复制链路状态
通过 pg_stat_replication 视图可获得以下信息:
- pid:walsender 进程 ID
- application_name:备库标识(应提前在备库
primary_conninfo中定义) - state:复制链路状态,正常值为
streaming - sync_state:同步模式状态(sync / async / potential)
- sent_lsn / write_lsn / flush_lsn / replay_lsn:备库对各阶段处理的确认进度
- reply_time:最后一次收到备库确认的时间,用于判断备库是否还处于"活跃"状态
9.2 复制延迟排查的常用止损
SELECT client_addr, state, sync_state,
pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS replication_lag_bytes
FROM pg_stat_replication; - 高网络延迟:影响
write_lsn与flush_lsn进度 - 备库磁盘或 I/O 瓶颈:影响从
flush_lsn到replay_lsn - 备库资源争用(如长事务):影响 WAL 重放进度,这种情况可检查
max_standby_streaming_delay与hot_standby_feedback设置是否合理
十、故障切换:pg_ctl promote 与 pg_rewind
当主库发生故障时,触发的切换称为 Failover。
10.1 备库升级为新主库
在旧主库确认不可用后,在待升级的备库上执行:
sudo -u postgres pg_ctl promote -D /var/lib/postgresql/16/main 执行后,备库将立即脱离恢复模式,转为可读写的主库。pg_stat_replication 中自身的 pg_is_in_recovery() 将从 true 变为 false。
10.2 老旧主库的快速重建
Failover 完成后,如果旧主库数据可恢复,通常会将其转为新主库的备库。如果旧主库在故障前有部分数据未同步到新主库,直接用 pg_basebackup 重建的成本(尤其是数据量在 TB 级时)会极高。从 PostgreSQL 9.5 起引入的 pg_rewind 解决了这一痛点——它可将旧主库"回滚"到与新主库激活时一致的 WAL 位置,然后差额同步,整个过程远远快于全量拷贝。但使用前提是数据库初始化时需启用 --data-checksums 或开启 wal_log_hints = on。
写在最后
搭建流复制集群的门槛并不高,三五个步骤就能见证主从数据的实时同步。但在生产环境中,还需要进行更细致的权衡:同步复制裁切的零点数据丢失与写入延迟之间的平衡、复制槽占用空间与链路的稳定性之间的平衡、多重故障时选主的逻辑。
第二期完成了物理流复制的完整搭建,验证了异步复制与同步复制之间的差异。第三期将系统总结 PostgreSQL 异步与同步复制的选择策略,深入分析 RPO 与 RTO 的量化模型,并通过压测数据对比不同模式下的写入吞吐量差异,帮助您在一致性、可用性与性能之间找到适合自己的平衡点。
推荐工具
- pg_stat_replication:系统自带复制监控视图
- pg_stat_wal_receiver:备库端 WAL 接收统计
- pg_stat_replication_slots:监控复制槽占用情况
No comments yet