PostgreSQL流复制架构:WAL日志与同步机制
PostgreSQL数据库高可用架构的核心是流复制(Streaming Replication)。主库将WAL(Write-Ahead Log)日志实时传输到备库,备库接收后回放日志保持数据一致。与MySQL基于binlog的复制不同,PostgreSQL流复制传输的是物理WAL记录,备库是主库的完整物理副本,包含所有数据文件、索引和控制文件。
流复制支持两种模式:异步复制和同步复制。异步模式下主库写入WAL后立即返回客户端确认,不等备库接收,性能最优但存在数据丢失窗口。同步模式下主库等待至少一个备库确认接收WAL后才返回,数据零丢失但写入延迟增加。生产环境通常采用同步复制+自动故障转移(Patroni)的组合方案。
主库配置:WAL归档与复制权限
流复制的前提是正确配置WAL相关参数。以下为生产环境推荐的postgresql.conf配置:
# postgresql.conf (主库)
listen_addresses = '*'
port = 5432
max_connections = 200
shared_buffers = 8GB
effective_cache_size = 24GB
work_mem = 64MB
# WAL配置
wal_level = replica # 流复制最低要求
max_wal_senders = 10 # 最大WAL发送进程数
wal_keep_size = 2048 # 保留2GB WAL供备库追赶
hot_standby = on # 允许备库查询(虽然主库不需要,但后续可能切换角色)
archive_mode = on # 开启归档
archive_command = 'cp %p /data/pg_archive/%f'
max_replication_slots = 10 # 复制槽数量
# 同步复制配置
synchronous_standby_names = 'FIRST 1 (standby1)' # 至少1个备库同步
# 或使用 '*' 表示任意备库
# 性能相关
checkpoint_timeout = 15min
max_wal_size = 4GB
min_wal_size = 1GB
wal_compression = on # WAL压缩传输,节省带宽
wal_sender_timeout = 60s # WAL发送超时
创建专用复制用户并赋予复制权限:
-- 在主库执行
CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'ReplSecure@2026';
-- pg_hba.conf 添加复制连接规则
-- host replication replicator 192.168.1.0/24 scram-sha-256
-- 创建复制槽(防止备库断连后WAL被清理)
SELECT pg_create_physical_replication_slot('standby1_slot');
复制槽的作用是确保主库不清理备库尚未接收的WAL。无复制槽时,若备库断连时间过长,主库可能清理掉旧WAL导致备库无法追上,需要重建。复制槽的代价是WAL堆积——备库长期断连会导致主库磁盘撑满。需监控复制槽的活跃状态,及时清理过期槽。
备库搭建:pg_basebackup初始化与流复制启动
使用pg_basebackup从主库拉取基础备份,是搭建流复制备库的标准方式:
# 1. 停止备库PostgreSQL服务
systemctl stop postgresql
# 2. 清空备库数据目录
rm -rf /var/lib/postgresql/15/main/*
# 3. 从主库拉取基础备份
pg_basebackup \
-h 192.168.1.10 \
-p 5432 \
-U replicator \
-D /var/lib/postgresql/15/main \
-Fp -Xs -P -R \
-S standby1_slot
# 参数说明:
# -Fp: plain格式,直接写入文件系统
# -Xs: 使用stream方式传输WAL
# -P: 显示进度
# -R: 自动创建standby.signal和配置连接信息
# -S: 指定复制槽
-R参数会在数据目录下生成standby.signal文件和postgresql.auto.conf,后者包含primary_conninfo连接信息。手动配置方式:
# postgresql.auto.conf (备库自动生成)
primary_conninfo = 'host=192.168.1.10 port=5432 user=replicator password=ReplSecure@2026 application_name=standby1'
primary_slot_name = 'standby1_slot'
# 备库postgresql.conf
hot_standby = on # 允许备库只读查询
max_standby_streaming_delay = 30s # 备库查询与主库冲突时的等待时间
max_standby_archive_delay = 30s
# 4. 启动备库
systemctl start postgresql
# 5. 验证流复制状态(在主库执行)
SELECT
client_addr,
application_name,
state,
sync_state,
sent_lsn,
write_lsn,
flush_lsn,
replay_lsn,
write_lag,
flush_lag,
replay_lag
FROM pg_stat_replication;
-- 正常输出示例:
-- client_addr: 192.168.1.11
-- application_name: standby1
-- state: streaming
-- sync_state: sync
-- write_lag: 00:00:00.001
-- flush_lag: 00:00:00.003
-- replay_lag: 00:00:00.005
四个LSN字段反映复制进度:sent_lsn是主库已发送的WAL位置,write_lsn是备库已写入的,flush_lsn是已刷盘的,replay_lsn是已回放完成的。理想状态下四者应该接近,差距过大说明备库存在延迟。三个lag字段以秒为单位量化延迟,write_lag是写入延迟,flush_lag是刷盘延迟,replay_lag是回放延迟。
Patroni自动故障转移:集群管理与主从切换
PostgreSQL原生不提供自动故障转移。Patroni是主流的高可用管理工具,基于etcd/ZooKeeper/Consul作为分布式配置存储,实现自动健康检查和主从切换。Patroni架构中每个节点运行Patroni守护进程,定期向DCS上报状态,主库持有leader锁。主库故障时,备库竞争leader锁完成切换。
Patroni配置文件patroni.yml:
scope: pg_cluster # 集群名称
namespace: /service/ # DCS命名空间
name: node1 # 当前节点名
restapi:
listen: 0.0.0.0:8008
connect_address: 192.168.1.10:8008
etcd:
hosts: 192.168.1.20:2379,192.168.1.21:2379,192.168.1.22:2379
bootstrap:
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 20
maximum_lag_on_failover: 1048576 # 1MB,故障转移时最大允许延迟
synchronous_mode: true # 同步模式
postgresql:
use_pg_rewind: true # 允许pg_rewind修复旧主库
parameters:
wal_level: replica
max_wal_senders: 10
hot_standby: on
synchronous_commit: on
postgresql:
listen: 0.0.0.0:5432
connect_address: 192.168.1.10:5432
data_dir: /var/lib/postgresql/15/main
bin_dir: /usr/lib/postgresql/15/bin
authentication:
superuser:
username: postgres
password: PgAdmin@2026
replication:
username: replicator
password: ReplSecure@2026
parameters:
shared_buffers: 8GB
max_connections: 200
tags:
nofailover: false
noloadbalance: false
clonefrom: false
replicatefrom: false
启动Patroni并检查集群状态:
# 启动Patroni
patroni /etc/patroni/patroni.yml &
# 查看集群状态
patronictl -c /etc/patroni/patroni.yml list
# 输出示例:
# + Cluster: pg_cluster (7000...) --+---------+----+-----------+
# | Member | Host | Role | State | TL | Lag in MB |
# +--------+---------------+---------+---------+----+-----------+
# | node1 | 192.168.1.10 | Leader | running | 12 | |
# | node2 | 192.168.1.11 | Replica | streaming| 12 | 0 |
# | node3 | 192.168.1.12 | Replica | streaming| 12 | 0 |
# +--------+---------------+---------+---------+----+-----------+
# 手动切换主库(维护场景)
patronictl switchover /etc/patroni/patroni.yml
# 自动故障转移验证(模拟主库宕机)
systemctl stop postgresql # Patroni会在loop_wait+retry_timeout后触发选举
故障转移后旧主库修复:pg_rewind操作
故障转移后,旧主库上的WAL与新主库可能分叉。直接将旧主库作为备库启动会报错。pg_rewind工具可以从新主库拉取差异WAL,将旧主库数据回退到分叉点,使其可以作为新主库的备库重新加入集群:
# 1. 停止旧主库PostgreSQL
systemctl stop postgresql
# 2. 执行pg_rewind
pg_rewind \
--target-pgdata=/var/lib/postgresql/15/main \
--source-server='host=192.168.1.11 port=5432 user=postgres password=PgAdmin@2026'
# 3. 更新primary_conninfo指向新主库
cat >> /var/lib/postgresql/15/main/postgresql.auto.conf << 'EOF'
primary_conninfo = 'host=192.168.1.11 port=5432 user=replicator password=ReplSecure@2026 application_name=node1'
primary_slot_name = 'node1_slot'
EOF
touch /var/lib/postgresql/15/main/standby.signal
# 4. 启动PostgreSQL(作为备库)
systemctl start postgresql
# 5. 确认Patroni接管
patronictl -c /etc/patroni/patroni.yml list
Patroni配置了use_pg_rewind: true时,故障恢复会自动执行pg_rewind。手动操作时需确保旧主库已完全停止,且源库(新主库)可通过复制连接访问。pg_rewind要求旧主库的postgresql.conf中启用了full_page_writes(默认开启),否则可能无法正确计算差异。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/postgresql-liu-fu-zhi-pei-zhi-shi-zhan-zhu-cong-tong-bu-yu/