PostgreSQL高可用架构实战:Patroni与etcd实现自动故障转移

PostgreSQL 是当下最流行的开源关系型数据库之一,但当业务流量增长到单机无法承载时,高可用就成了必须跨过的门槛。本文将围绕 Patroni 与 etcd 方案,详解从主从复制到自动故障转移的完整搭建过程,并剖析其中的选举机制与脑裂防护策略。

一、为什么需要Patroni

原生的 PostgreSQL 流复制只提供数据同步能力,主库宕机后需要人工介入执行提升操作。Patroni 是由 Zalando 开源的 PostgreSQL 高可用管理器,它通过一个分布式配置存储(etcd、Consul 或 ZooKeeper)来选举主库,并自动完成提升、重定向、重新加入等动作,实现真正的 RTO 秒级故障转移。

二、环境拓扑与前置准备

典型的三节点集群拓扑如下:三台服务器各运行一个 PostgreSQL 实例和一个 Patroni 进程,另部署一组三节点 etcd 集群提供共识服务。节点规划示例如下:

# 三节点规划
pg1: 192.168.1.11  (初始主库)
pg2: 192.168.1.12  (副本)
pg3: 192.168.1.13  (副本)
# etcd 集群部署在 pg1/pg2/pg3 三节点上

首先确保每台机器安装好 PostgreSQL 16 和 etcd,并在三个节点初始化 etcd 集群:

# 在 pg1 上启动 etcd
etcd --name etcd1 \
  --initial-advertise-peer-urls http://192.168.1.11:2380 \
  --listen-peer-urls http://0.0.0.0:2380 \
  --listen-client-urls http://0.0.0.0:2379 \
  --advertise-client-urls http://192.168.1.11:2379 \
  --initial-cluster etcd1=http://192.168.1.11:2380,etcd2=http://192.168.1.12:2380,etcd3=http://192.168.1.13:2380 \
  --initial-cluster-token pg-ha-cluster \
  --initial-cluster-state new

三、Patroni配置详解

每个节点的 Patroni 配置文件 patroni.yml 中,最关键的几个段落是:

scope: pg-ha-cluster
name: pg1  # 每个节点不同

restapi:
  listen: 0.0.0.0:8008
  connect_address: 192.168.1.11:8008

etcd3:
  hosts: 192.168.1.11:2379,192.168.1.12:2379,192.168.1.13: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_replication_slots: 10
        wal_keep_size: 1024

postgresql:
  listen: 0.0.0.0:5432
  connect_address: 192.168.1.11:5432
  data_dir: /var/lib/postgresql/data
  bin_dir: /usr/lib/postgresql/16/bin
  authentication:
    replication:
      username: replicator
      password: Rep1!c@t0rPwd
    superuser:
      username: postgres
      password: Sup3rAdm1nPwd
  parameters:
    shared_buffers: 4GB
    effective_cache_size: 12GB

几个关键配置点:TTL 决定租约时长,loop_wait 是轮询间隔,maximum_lag_on_failover 确保延迟过大的副本不会被提升为主库,use_pg_rewind 让旧主库在故障后能快速重新加入而非全量重建。

四、初始化与故障转移演练

启动 Patroni 后,集群会自动完成主库初始化和副本搭建,可用 patronictl 查看状态:

patronictl -c patroni.yml list
+---------+-------------+---------+-----------+----+-----------+
| Member  | Host        | Role    | State     | TL | Lag in MB |
+---------+-------------+---------+-----------+----+-----------+
| pg1     | 192.168.1.11 | Leader  | running   |  1 |           |
| pg2     | 192.168.1.12 | Replica | streaming |  1 |         0 |
| pg3     | 192.168.1.13 | Replica | streaming |  1 |         0 |
+---------+-------------+---------+-----------+----+-----------+

模拟主库故障:在 pg1 上强制结束 postgres 主进程,Patroni 检测到后会在 1 到 2 个 TTL 周期内把延迟最小的副本提升为新主库,整个过程无需人工干预。

五、脑裂防护与读写分离

分布式共识是脑裂防护的核心:etcd 的 Raft 协议确保同一时刻只有一个节点持有 leader 租约。Patroni 在获取主库身份前必须先拿到 etcd 中的 leader key,租约过期前其他节点无法抢占,从而避免双主。

前端接入建议用 HAProxy 或 pgBouncer 做连接路由:通过 Patroni 的 8008 端口健康检查接口区分主库与副本,写流量打到主库,读流量可分发到副本。配置示例:

# HAProxy 后端配置
backend pg_write
  option httpchk GET /primary
  server pg1 192.168.1.11:5432 check port 8008
  server pg2 192.168.1.12:5432 check port 8008
  server pg3 192.168.1.13:5432 check port 8008

backend pg_read
  option httpchk GET /replica
  server pg2 192.168.1.12:5432 check port 8008
  server pg3 192.168.1.13:5432 check port 8008

需要强调的是,读副本存在复制延迟,对一致性敏感的读操作仍应走主库。此外定期演练故障转移过程、监控复制延迟与 etcd 集群健康度,才能真正让高可用方案在关键时刻靠得住。