PostgreSQL 主备切换(Failover)是高可用架构中的关键操作,指在主库(Primary)发生故障时,将备库(Standby)提升为新的主库,以恢复数据库服务。根据实现方式不同,可分为手动切换自动切换。本文将从原理、准备、演练步骤、验证方法到注意事项,进行详细解析。


一、基本概念与前提

1.1 什么是 Failover?

Failover 是指当主库因硬件故障、网络中断、进程崩溃等原因不可用时,系统将写入流量切换至一个健康的备库,使其成为新的主库,从而保证业务连续性。

1.2 流复制基础

本文假设已搭建好基于物理流复制(Streaming Replication)的一主一备集群,且满足以下条件:

  • 主备 PostgreSQL 版本一致;
  • 已配置 WAL 流复制(wal_level = replica);
  • 备库处于 hot_standby = on 状态;
  • 网络互通,时间同步(NTP);
  • 使用操作系统用户 postgres 管理数据库。

若尚未搭建流复制集群,请先参考《PostgreSQL:万字详解如何搭建流复制集群》。

1.3 切换类型

类型 触发方式 是否需人工干预 适用场景
手动 Failover DBA 执行命令 测试、可控维护、无 HA 工具环境
自动 Failover HA 工具(如 Patroni、repmgr)检测并执行 生产高可用环境

二、手动 Failover 演练

2.1 演练目标

  • 模拟主库宕机;
  • 手动将备库提升为主库;
  • 验证新主库可读写;
  • (可选)原主库恢复后重新加入集群作为新备库。

2.2 环境信息

节点 IP 角色 数据目录
node1 192.168.1.10 Primary /var/lib/pgsql/14/data
node2 192.168.1.11 Standby /var/lib/pgsql/14/data

2.3 步骤 1:确认当前状态

在主库(node1)查看复制状态:

SELECT * FROM pg_stat_replication;
-- 应看到 node2 的连接,state = streaming

在备库(node2)确认处于恢复模式:

SELECT pg_is_in_recovery();  -- 返回 true
SHOW hot_standby;            -- on

2.4 步骤 2:模拟主库故障

在 node1 上停止 PostgreSQL 服务:

sudo systemctl stop postgresql-14

或直接 kill 进程(更极端):

sudo pkill -f postgres

此时应用连接主库将失败,需切换至备库。

2.5 步骤 3:在备库执行提升(Promote)

登录 node2,执行提升命令:

方法一:使用 pg_ctl promote(推荐)
sudo -u postgres pg_ctl promote -D /var/lib/pgsql/14/data

输出示例:

server promoting
方法二:创建 trigger 文件(适用于 PG 12+)

若在 postgresql.conf 中配置了 promote_trigger_file,可创建该文件触发提升:

# postgresql.conf
promote_trigger_file = '/tmp/promote.trigger'

然后:

sudo -u postgres touch /tmp/promote.trigger

若未配置,则默认不启用此方式。

2.6 步骤 4:验证新主库

在 node2 上执行:

SELECT pg_is_in_recovery();  -- 应返回 false

尝试写入数据:

CREATE TABLE test_failover(id int);
INSERT INTO test_failover VALUES (1);
SELECT * FROM test_failover;  -- 成功返回

说明 node2 已成功晋升为主库。

2.7 步骤 5:更新应用连接配置

将应用程序的数据库连接地址从 192.168.1.10 改为 192.168.1.11,重启应用服务。

在生产中,通常通过 VIP(虚拟 IP)、DNS 或中间件(如 PgBouncer + HAProxy)实现透明切换。

2.8 步骤 6:原主库恢复后重新加入(可选)

原主库(node1)修复后,不能直接启动作为主库(会导致脑裂)。需将其重建为新备库。

方案 A:使用 pg_basebackup 重新初始化(简单可靠)
  1. 停止 node1 上的 PostgreSQL(若已启动):

    sudo systemctl stop postgresql-14
    
  2. 清空数据目录:

    sudo rm -rf /var/lib/pgsql/14/data/*
    
  3. 从新主库(node2)拉取基础备份:

    sudo -u postgres pg_basebackup \
      -h 192.168.1.11 \
      -U repuser \
      -D /var/lib/pgsql/14/data \
      -P -v -R -X stream
    
  4. 启动 node1:

    sudo systemctl start postgresql-14
    
  5. 验证:

    SELECT pg_is_in_recovery();  -- true
    
方案 B:使用 pg_rewind(保留本地数据,更快)

前提:原主库启用了 data checksumswal_log_hints = on

  1. 在 node1 上安装 pg_rewind(通常随 PostgreSQL 一起安装)。

  2. 执行:

    sudo -u postgres pg_rewind \
      --target-pgdata=/var/lib/pgsql/14/data \
      --source-server="host=192.168.1.11 port=5432 user=repuser password=StrongPass123!"
    
  3. 创建 standby.signal:

    sudo -u postgres touch /var/lib/pgsql/14/data/standby.signal
    
  4. 启动服务。

pg_rewind 优势:无需全量拷贝,仅同步差异页,速度快。


三、自动 Failover 演练(基于 Patroni)

手动切换适用于测试或小规模环境,但生产环境需自动化。Patroni 是目前最流行的 PostgreSQL HA 解决方案,基于分布式协调器(如 etcd、Consul、ZooKeeper)实现自动选主。

3.1 架构说明

  • Patroni:运行在每个 PostgreSQL 节点上的守护进程,管理实例启停、角色切换。
  • etcd:分布式键值存储,用于存储集群状态、选举 Leader。
  • VIP / DNS / Load Balancer:对外提供统一入口。

3.2 环境准备

新增一台服务器部署 etcd(IP: 192.168.1.12)。

在 node1 和 node2 安装 Patroni:

pip3 install patroni[etcd]

3.3 配置 etcd(192.168.1.12)

# /etc/etcd/etcd.conf.yml
name: etcd1
data-dir: /var/lib/etcd
listen-client-urls: http://0.0.0.0:2379
advertise-client-urls: http://192.168.1.12:2379
listen-peer-urls: http://localhost:2380
initial-advertise-peer-urls: http://localhost:2380
initial-cluster: etcd1=http://localhost:2380
initial-cluster-state: new

启动 etcd:

etcd --config-file /etc/etcd/etcd.conf.yml

3.4 配置 Patroni(node1 和 node2)

node1 的 patroni.yml:

scope: pg-cluster
namespace: /service/
name: pg-node1

restapi:
  listen: 192.168.1.10:8008
  connect_address: 192.168.1.10:8008

etcd:
  hosts: 192.168.1.12:2379

bootstrap:
  dcs:
    ttl: 30
    loop_wait: 10
    retry_timeout: 10
    maximum_lag_on_failover: 1048576
    postgresql:
      use_pg_rewind: true
      use_slots: true
      parameters:
        wal_level: replica
        hot_standby: "on"
        max_wal_senders: 10
        wal_keep_size: 1GB
        archive_mode: "off"

  initdb:
    - encoding: UTF8
    - data-checksums

  users:
    admin:
      password: admin123
      options:
        - createrole
        - createdb

postgresql:
  listen: 192.168.1.10:5432
  connect_address: 192.168.1.10:5432
  data_dir: /var/lib/pgsql/14/data
  bin_dir: /usr/pgsql-14/bin
  authentication:
    replication:
      username: repuser
      password: StrongPass123!
    superuser:
      username: postgres
      password: postgres123!
  parameters:
    unix_socket_directories: '/tmp'

tags:
  nofailover: false
  noloadbalance: false

node2 的 patroni.yml:

  • 修改 name: pg-node2
  • 修改 listenconnect_address192.168.1.11

注意:首次启动时,Patroni 会自动初始化集群(initdb),若已有数据,需注释 initdb 部分,并确保数据目录为空或已由 Patroni 管理。

3.5 启动 Patroni

在 node1 和 node2 分别启动:

patroni patroni.yml

Patroni 会自动选举一个主库(通常是先启动的节点)。

查看集群状态:

curl http://192.168.1.10:8008/cluster
# 或
patronictl -c patroni.yml list

输出示例:

+ Cluster: pg-cluster (7073733813877921290) -+---------+----+-----------+
| Member    | Host           | Role    | State   | TL | Lag in MB |
+-----------+----------------+---------+---------+----+-----------+
| pg-node1  | 192.168.1.10   | Leader  | running |  1 |         0 |
| pg-node2  | 192.168.1.11   | Replica | running |  1 |         0 |
+-----------+----------------+---------+---------+----+-----------+

3.6 自动 Failover 演练

  1. 模拟主库宕机:在 node1 上 kill Patroni 进程:

    pkill -f patroni
    
  2. 等待自动切换

    • etcd 在 TTL(30秒)内未收到心跳;
    • Patroni 在 node2 上检测到主库失联;
    • 触发选举,node2 自动 promote 为主库。
  3. 验证

    patronictl -c patroni.yml list
    

    输出应显示 pg-node2 为 Leader。

  4. 应用连接:若配置了 HAProxy,流量自动切至新主库,应用无感知。

  5. 恢复 node1

    • 重启 Patroni:
      patroni patroni.yml
      
    • Patroni 自动将其注册为 Replica,并通过 pg_rewind 或 basebackup 同步数据。

Patroni 默认启用 use_pg_rewind: true,因此恢复速度快。


四、关键配置对比

功能 手动切换 Patroni 自动切换
故障检测 人工判断 心跳 + TTL 自动检测
提升操作 手动执行 promote 自动执行
脑裂防护 依赖人工 通过 DCS 锁机制避免
原主恢复 需手动重建 自动 rejoin
复制槽管理 手动 自动创建/删除
API 支持 提供 REST API(8008端口)

五、注意事项与最佳实践

5.1 避免脑裂(Split-Brain)

  • 绝不允许两个节点同时作为主库接受写入。
  • 手动切换前务必确认原主库已彻底停止。
  • 自动切换必须依赖可靠的 DCS(如 etcd)进行仲裁。

5.2 复制延迟处理

  • 设置 maximum_lag_on_failover(Patroni)防止提升严重滞后的备库。
  • 监控 pg_stat_replication 中的 write_lsnflush_lsnreplay_lsn

5.3 应用连接管理

  • 使用连接池(PgBouncer) + 负载均衡(HAProxy) + 健康检查。
  • HAProxy 配置示例:
    backend pg_backend
      option httpchk
      http-check expect status 200
      server pg1 192.168.1.10:5432 check port 8008
      server pg2 192.168.1.11:5432 check port 8008
    
    Patroni 的 8008 端口提供 /primary/replica 等健康检查接口。

5.4 日志与监控

  • 记录所有 promote 操作日志;
  • 使用 Prometheus + Exporter 监控复制延迟、连接数等;
  • 设置告警(如主库宕机、延迟 > 100MB)。

5.5 测试建议

  • 定期进行 Failover 演练(季度);
  • 演练后验证数据一致性;
  • 测试“主库短暂闪断后恢复”的场景(是否误切换)。

总结:PostgreSQL 主备切换是保障数据库高可用的核心能力:

  • 手动 Failover:适合学习、测试或无自动化工具的环境,操作直接但依赖人工,风险较高。
  • 自动 Failover(Patroni):生产首选,具备故障检测、自动选主、安全防护、快速恢复等能力。

无论采用哪种方式,都必须:

  1. 确保流复制正常;
  2. 防止脑裂;
  3. 验证数据一致性;
  4. 配合应用层实现无缝切换。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐