PostgreSQL流复制高可用部署实战:Patroni自动故障转移

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/

(0)
小编小编
上一篇 11小时前
下一篇 11小时前

相关推荐