PostgreSQL通过流复制(Streaming Replication)实现主从同步,是数据库高可用架构的基础组件。流复制将主库的WAL日志实时传输到备库回放,保持数据一致。结合Patroni集群管理工具和etcd分布式配置存储,可实现自动故障检测、主库切换和连接重路由,构建生产级的高可用PostgreSQL集群。数据库高可用架构的核心目标是减少单点故障导致的停机时间,从分钟级人工介入缩短到秒级自动切换。
PostgreSQL流复制配置与原理
PostgreSQL流复制的核心是WAL(Write-Ahead Logging)日志传输。主库执行的事务产生WAL日志,通过replication协议将日志流推送到备库。备库接收WAL日志后回放,实现数据同步。
流复制分为同步模式和异步模式。异步模式下主库写入WAL后立即返回,不等备库确认,延迟低但可能丢数据;同步模式下主库等待至少一个备库确认收到WAL后才返回,保证数据不丢失但有性能开销。
手动配置流复制的步骤:
# 主库配置 postgresql.conf
wal_level = replica
max_wal_senders = 10
wal_keep_size = 1024 # 保留1GB的WAL日志
hot_standby = on # 备库允许只读查询
# 主库配置 pg_hba.conf(允许复制连接)
host replication replicator 192.168.1.0/24 md5
# 创建复制用户
psql -c "CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'secret';"
# 备库使用pg_basebackup初始化
pg_basebackup \
-h 192.168.1.10 \
-U replicator \
-D /var/lib/postgresql/data \
-Fp -Xs -P -R
# 备库配置 standby.signal(PostgreSQL 12+)
touch /var/lib/postgresql/data/standby.signal
# 备库 postgresql.conf
primary_conninfo = 'host=192.168.1.10 port=5432 user=replicator password=secret'
hot_standby = on
配置完成后启动备库,通过以下SQL验证复制状态:
-- 主库查看复制连接
SELECT * FROM pg_stat_replication;
-- 关键字段:state(streaming)、sync_state(async/sync)、write_lag/flush_lag/replay_lag
-- 备库查看接收状态
SELECT * FROM pg_stat_wal_receiver;
-- 关键字段:status(streaming)、received_lsn、last_msg_send_time
-- 查看复制延迟(主库执行)
SELECT
application_name,
pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS lag_bytes,
write_lag,
flush_lag,
replay_lag
FROM pg_stat_replication;
Patroni集群管理与自动故障转移
手动流复制在主库宕机时需要人工介入执行故障转移(promote备库为主库),恢复时间长且容易出错。Patroni是Zalando开源的PostgreSQL高可用管理工具,通过etcd/Consul存储集群状态,自动执行故障检测和主库切换。
Patroni集群架构:3个PostgreSQL节点各运行一个Patroni进程,Patroni进程通过etcd集群进行选主。当主库的Patroni失去etcd租约时,集群自动发起选举,选出新的主库并将其他节点重新配置为新主的备库。
安装Patroni和etcd的Docker Compose部署方案:
# docker-compose-patroni.yml
version: '3.8'
services:
etcd:
image: bitnami/etcd:3.5
environment:
- ALLOW_NONE_AUTHENTICATION=yes
- ETCD_NAME=etcd1
- ETCD_INITIAL_ADVERTISE_PEER_URLS=http://etcd:2380
- ETCD_ADVERTISE_CLIENT_URLS=http://etcd:2379
- ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380
- ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379
ports:
- "2379:2379"
- "2380:2380"
patroni1:
image: ghcr.io/zalando/spilo-15:2.1-p9
hostname: pg1
environment:
- PATRONI_NAME=pg1
- PATRONI_SCOPE=pg_cluster
- PATRONI_ETCD3_HOSTS=etcd:2379
- PATRONI_POSTGRESQL_DATA_DIR=/home/postgres/pgdata
- PATRONI_RESTAPI_CONNECT_ADDRESS=patroni1:8008
- PATRONI_RESTAPI_LISTEN=0.0.0.0:8008
- PATRONI_POSTGRESQL_CONNECT_ADDRESS=patroni1:5432
- PATRONI_POSTGRESQL_LISTEN=0.0.0.0:5432
- PATRONI_SUPERUSER_USERNAME=postgres
- PATRONI_SUPERUSER_PASSWORD=secret123
- PATRONI_REPLICATION_USERNAME=replicator
- PATRONI_REPLICATION_PASSWORD=rep_secret
- PATRONI_POSTGRESQL_PARAMETERS=max_connections=200
- PATRONI_POSTGRESQL_USE_PG_REWIND=true
ports:
- "5432:5432"
- "8008:8008"
depends_on:
- etcd
patroni2:
image: ghcr.io/zalando/spilo-15:2.1-p9
hostname: pg2
environment:
- PATRONI_NAME=pg2
- PATRONI_SCOPE=pg_cluster
- PATRONI_ETCD3_HOSTS=etcd:2379
- PATRONI_POSTGRESQL_DATA_DIR=/home/postgres/pgdata
- PATRONI_RESTAPI_CONNECT_ADDRESS=patroni2:8008
- PATRONI_RESTAPI_LISTEN=0.0.0.0:8008
- PATRONI_POSTGRESQL_CONNECT_ADDRESS=patroni2:5432
- PATRONI_POSTGRESQL_LISTEN=0.0.0.0:5432
- PATRONI_SUPERUSER_USERNAME=postgres
- PATRONI_SUPERUSER_PASSWORD=secret123
- PATRONI_REPLICATION_USERNAME=replicator
- PATRONI_REPLICATION_PASSWORD=rep_secret
- PATRONI_POSTGRESQL_USE_PG_REWIND=true
ports:
- "5433:5432"
depends_on:
- etcd
patroni3:
image: ghcr.io/zalando/spilo-15:2.1-p9
hostname: pg3
environment:
- PATRONI_NAME=pg3
- PATRONI_SCOPE=pg_cluster
- PATRONI_ETCD3_HOSTS=etcd:2379
- PATRONI_POSTGRESQL_DATA_DIR=/home/postgres/pgdata
- PATRONI_RESTAPI_CONNECT_ADDRESS=patroni3:8008
- PATRONI_RESTAPI_LISTEN=0.0.0.0:8008
- PATRONI_POSTGRESQL_CONNECT_ADDRESS=patroni3:5432
- PATRONI_POSTGRESQL_LISTEN=0.0.0.0:5432
- PATRONI_SUPERUSER_USERNAME=postgres
- PATRONI_SUPERUSER_PASSWORD=secret123
- PATRONI_REPLICATION_USERNAME=replicator
- PATRONI_REPLICATION_PASSWORD=rep_secret
- PATRONI_POSTGRESQL_USE_PG_REWIND=true
ports:
- "5434:5432"
depends_on:
- etcd
集群启动后通过Patroni REST API管理集群状态:
# 查看集群拓扑状态
patronictl -c patroni.yml list
# 输出示例:
# + Cluster: pg_cluster ---+----+-----------+
# | Member | Host | Role | State | TL | Lag in MB |
# | pg1 | pg1 | Leader| running | 1 | |
# | pg2 | pg2 | | replica | 1 | 0 |
# | pg3 | pg3 | | replica | 1 | 0 |
# 手动执行计划内切换(主备互换)
patronictl switchover pg_cluster
# 手动故障转移(指定新主)
patronictl failover pg_cluster --candidate pg2
# 查看集群配置
curl http://patroni1:8008/config | python -m json.tool
# 查看节点健康状态
curl http://patroni1:8008/health
HAProxy连接路由与读写分离
Patroni集群本身不提供连接路由功能,客户端需要通过HAProxy或PgBouncer实现连接的自动路由。HAProxy通过Patroni的REST API健康检查端点区分主库和备库:
# haproxy.cfg
frontend postgres_write
bind *:5432
mode tcp
default_backend postgres_primary
frontend postgres_read
bind *:5433
mode tcp
default_backend postgres_replicas
backend postgres_primary
mode tcp
option httpchk
http-check expect status 200
default-server inter 3s fall 3 rise 2
server pg1 patroni1:5432 check port 8008
server pg2 patroni2:5432 check port 8008
server pg3 patroni3:5432 check port 8008
backend postgres_replicas
mode tcp
balance roundrobin
option httpchk
http-check expect status 200
default-server inter 3s fall 3 rise 2
server pg1 patroni1:5432 check port 8008
server pg2 patroni2:5432 check port 8008
server pg3 patroni3:5432 check port 8008
Patroni的/health端点在不同角色下返回不同HTTP状态码:主库返回200,备库返回503(通过添加?lag=<max_delay>参数可调整)。HAProxy利用这个特性自动将写请求路由到主库,读请求分发到备库,实现读写分离。
数据备份与恢复策略
高可用集群不等于数据备份。硬件故障、误操作(DROP TABLE)或数据损坏仍需要备份来恢复。PostgreSQL的物理备份工具pgBackRest支持全量备份、增量备份和PITR(Point-In-Time Recovery):
# pgBackRest配置
[global]
repo1-path=/var/lib/pgbackrest
repo1-retention-full=4
repo1-retention-diff=4
log-level-console=info
process-max=4
compress-level=3
[pg_cluster]
pg1-host=patroni1
pg1-path=/var/lib/postgresql/data
pg2-host=patroni2
pg2-path=/var/lib/postgresql/data
# 执行全量备份
pgbackrest --stanza=pg_cluster backup --type=full
# 增量备份
pgbackrest --stanza=pg_cluster backup --type=incr
# 差异备份
pgbackrest --stanza=pg_cluster backup --type=diff
# 查看备份集
pgbackrest info
# PITR恢复到指定时间点
pgbackrest --stanza=pg_cluster \
--type=time \
--target="2026-09-07 14:30:00" \
--target-action=promote \
restore
定期备份通过cron定时执行,建议策略:每周一次全量备份,每天一次增量备份,WAL日志持续归档。备份文件异地存储(如对象存储),防止单机房数据丢失。数据迁移实战中,pgBackRest的增量备份能力大幅缩短迁移窗口,配合PITR可实现接近零停机的数据迁移。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/postgresql-liu-fu-zhi-gao-ke-yong-bu-shu-shi-zhan-patroni/