PostgreSQL的逻辑复制(Logical Replication)基于WAL日志的解码层实现,只复制数据变更而非整个数据块,支持跨版本复制、选择性表复制和双向复制。Patroni在物理复制基础上提供自动故障检测和主从切换,是PostgreSQL高可用方案的事实标准。本文完整演示逻辑复制配置流程和Patroni高可用集群搭建,覆盖从单机到三节点高可用的完整路径。
逻辑复制与物理复制的区别
物理复制在数据块级别同步主库WAL日志到备库,备库与主库完全一致,是字节级复制。优势是延迟低、配置简单,劣势是备库必须与主库版本和架构一致,不能跨大版本,不能选择性复制表。
逻辑复制通过WAL解码插件(如pgoutput)将WAL日志解析为逻辑变更事件(INSERT/UPDATE/DELETE),再应用到目标库。优势是支持跨版本复制、选择性表复制、发布-订阅模型灵活。劣势是配置较复杂,不支持DDL复制(表结构变更需手动同步),大事务的复制延迟高于物理复制。
逻辑复制发布-订阅配置实战
以下在源库(publisher,192.168.1.10)和目标库(subscriber,192.168.1.20)之间配置逻辑复制,同步orders和customers两张表。
源库配置postgresql.conf:
# 必须设为logical才能支持逻辑复制
wal_level = logical
# 保留足够WAL日志,防止复制延迟时WAL被回收
max_wal_senders = 10
max_replication_slots = 10
wal_keep_size = 1024MB
源库创建复制用户并授权:
CREATE ROLE replication_user WITH LOGIN REPLICATION PASSWORD 'StrongPass123!';
-- 授予待复制表的读权限
GRANT SELECT ON orders, customers TO replication_user;
源库创建发布(Publication):
-- 发布指定表
CREATE PUBLICATION pub_orders FOR TABLE orders, customers;
-- 发布所有表(谨慎使用)
-- CREATE PUBLICATION pub_all FOR ALL TABLES;
目标库pg_hba.conf允许源库连接:
# 允许源库IP通过复制用户连接
host all replication_user 192.168.1.10/32 md5
目标库创建订阅(Subscription):
CREATE SUBSCRIPTION sub_orders
CONNECTION 'host=192.168.1.10 port=5432 dbname=prod user=replication_user password=StrongPass123!'
PUBLICATION pub_orders
WITH (
copy_data = true, -- 初始数据同步
create_slot = true, -- 自动创建复制槽
enabled = true,
slot_name = 'sub_orders_slot'
);
copy_data设为true时,订阅创建后自动执行一次全表COPY将源库现有数据同步到目标库,之后持续通过WAL流复制增量变更。
验证复制状态:
-- 源库查看发布状态
SELECT * FROM pg_publication;
-- 目标库查看订阅状态
SELECT * FROM pg_subscription;
-- 查看复制进度
SELECT * FROM pg_stat_subscription;
逻辑复制冲突处理与跳过事务
逻辑复制常见的冲突场景:目标库存在主键冲突(INSERT重复)、外键约束冲突、CHECK约束冲突。冲突发生时复制会暂停,直到手动解决。
-- 查看订阅worker日志中的冲突信息
SELECT * FROM pg_stat_subscription
-- 跳过冲突事务(PostgreSQL 16+)
ALTER SUBSCRIPTION sub_orders SKIP (lsn = '0/1631748');
-- 重启订阅
ALTER SUBSCRIPTION sub_orders DISABLE;
ALTER SUBSCRIPTION sub_orders ENABLE;
生产环境建议在目标库表上设置主键,这是逻辑复制的最佳实践。无主键表的UPDATE和DELETE操作无法精确识别行,会导致复制异常。
Patroni高可用集群搭建
Patroni基于etcd实现分布式一致性,管理PostgreSQL主从切换。以下搭建3节点Patroni集群(1主2从)。
安装etcd集群(3节点):
# /etc/etcd/etcd.conf (节点1)
name: etcd1
data-dir: /var/lib/etcd
listen-peer-urls: http://0.0.0.0:2380
listen-client-urls: http://0.0.0.0:2379
initial-advertise-peer-urls: http://192.168.1.10:2380
advertise-client-urls: http://192.168.1.10:2379
initial-cluster: etcd1=http://192.168.1.10:2380,etcd2=http://192.168.1.20:2380,etcd3=http://192.168.1.30:2380
initial-cluster-token: etcd-cluster
initial-cluster-state: new
Patroni配置文件patroni.yml:
scope: pg-cluster
name: node1
restapi:
listen: 0.0.0.0:8008
connect_address: 192.168.1.10:8008
etcd:
hosts: 192.168.1.10:2379,192.168.1.20:2379,192.168.1.30:2379
bootstrap:
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 20
maximum_lag_on_failover: 1048576
postgresql:
use_pg_rewind: true
parameters:
wal_level: replica
hot_standby: "on"
max_wal_senders: 10
max_connections: 200
initdb:
- encoding: UTF8
- data-checksums
postgresql:
listen: 0.0.0.0:5432
connect_address: 192.168.1.10:5432
data_dir: /var/lib/postgresql/data
bin_dir: /usr/pgsql-16/bin
authentication:
replication:
username: replicator
password: ReplPass456!
superuser:
username: postgres
password: AdminPass789!
parameters:
archive_mode: "on"
archive_command: "cp %p /backup/archive/%f"
tags:
nofailover: false
noloadbalance: false
clonefrom: false
启动Patroni服务(3节点依次启动):
systemctl start patroni
systemctl enable patroni
# 查看集群状态
patronictl -c /etc/patroni/patroni.yml list
输出示例:
+ Cluster: pg-cluster (7017606987496831835) --+---------+----+-----------+
| Member | Host | Role | State | TL | Lag in MB |
+--------+----------------+---------+---------+----+-----------+
| node1 | 192.168.1.10 | Leader | running | 1 | |
| node2 | 192.168.1.20 | Replica | running | 1 | 0 |
| node3 | 192.168.1.30 | Replica | running | 1 | 0 |
+--------+----------------+---------+---------+----+-----------+
自动故障切换与脑裂防护
当主库故障时,Patroni通过etcd的分布式锁机制选举新主库。选举条件:候选节点必须是同步备库(synchronous standby),且复制延迟小于maximum_lag_on_failover配置值。
手动执行switchover(计划内切换):
patronictl switchover -c /etc/patroni/patroni.yml
脑裂防护机制:Patroni Leader节点每ttl秒向etcd续约租约。如果Leader与etcd失联超过ttl秒,租约过期,其他节点发起新选举。旧Leader感知到失去Leader身份后,立即将自己降级为备库并停止接受写入,防止脑裂。
使用pg_rewind修复分裂脑后的数据不一致。当旧主库在脑裂期间接收了写入,重新加入集群时需要pg_rewind回退差异:
# 旧主库上执行
pg_rewind --target-data=/var/lib/postgresql/data --source-server="host=192.168.1.20 user=postgres password=AdminPass789!"
# 然后重新启动Patroni
systemctl restart patroni
use_pg_rewind设为true时,Patroni在节点重新加入集群前自动执行pg_rewind,无需人工介入。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/postgresql-luo-ji-fu-zhi-pei-zhi-yu-patroni-gao-ke-yong-ji/