高可用架構(High Availability):Patroni、repmgr 與自動 Failover | PostgreSQL
PostgreSQL 高可用(HA)架構旨在確保資料庫服務在節點故障、網路中斷時仍能持續運作。本文涵蓋主流 HA 方案——Patroni + etcd + HAProxy、repmgr、pg_auto_failover——的完整部署配置、選型指南,以及 Kubernetes 原生部署策略。
HA 基本概念
在深入各方案之前,先理解四個核心概念:
- Failover(自動故障切換)——Primary 意外故障時,HA 系統自動將 Standby 晉升為新 Primary,不需人工介入
- Switchover(計畫性切換)——管理者主動發起的切換,等待 Replica 同步完成,資料零遺失
- Split-Brain(腦裂)——網路分區導致兩個節點各自認為自己是 Primary 並接受寫入,是最危險的故障情境
- Fencing(隔離機制)——強制終止舊 Primary(關機、封鎖網路等),確保任何時刻只有一個寫入節點
RTO/RPO 與 HA 架構
| 指標 | 定義 | 典型目標 | 影響因素 |
|---|---|---|---|
| RTO | 故障到服務恢復的最長時間 | < 30 秒 ~ 5 分鐘 | 故障偵測速度、Failover 自動化 |
| RPO | 最大可接受資料遺失時間 | 0 秒 ~ 數分鐘 | 複寫模式(同步/非同步) |
設計原則:
- 同步複寫可達 RPO ≈ 0,但寫入延遲增加
- 非同步複寫延遲更低,但 Failover 可能遺失未確認的 WAL
- Patroni 等工具可將 RTO 壓縮至 30 秒內
PostgreSQL HA 方案分類
PostgreSQL HA 方案
├── 基於流複寫
│ ├── Patroni + DCS(etcd / Consul)
│ ├── repmgr
│ └── pg_auto_failover
├── 基於共享儲存
│ └── Corosync + Pacemaker + DRBD
└── Kubernetes 原生
├── CloudNativePG
└── Zalando PostgreSQL Operator
Patroni — 業界標準 HA 框架
Patroni 是目前最廣泛採用的 PostgreSQL HA 框架,由 Zalando 開源,將 PostgreSQL 生命週期管理、Leader Election、Failover 自動化整合到單一程式。
架構圖
┌─────────────────┐
│ Client / App │
└────────┬────────┘
│
┌────────▼────────┐
│ HAProxy │
│ :5432 → Primary │
│ :5433 → Replica │
└───┬─────────┬───┘
│ │
┌───────────▼──┐ ┌───▼───────────┐
│ Patroni │ │ Patroni │
│ PostgreSQL │ │ PostgreSQL │
│ (Primary) │◄─│ (Standby) │
└──────┬───────┘ └──────┬────────┘
│ │
└────────┬────────┘
│
┌────────────▼────────────┐
│ etcd 叢集(3 節點) │
└─────────────────────────┘
元件職責:
- Patroni:管理 PostgreSQL 啟停、監控健康、參與 Leader Election
- etcd:分散式鍵值儲存,儲存叢集狀態與 Leader 資訊
- HAProxy:根據 Patroni HTTP 端點動態路由讀寫請求
patroni.yml 核心配置
scope: postgres-ha # 叢集名稱(所有節點必須一致)
namespace: /service/
name: node1 # 本節點唯一名稱
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 # Leader 租約 TTL(秒)
loop_wait: 10 # 監控迴圈間隔
retry_timeout: 10
maximum_lag_on_failover: 1048576 # Failover 允許的最大 WAL 落差
postgresql:
use_pg_rewind: true
use_slots: true
parameters:
wal_level: replica
hot_standby: "on"
max_connections: 200
max_wal_senders: 10
max_replication_slots: 10
wal_log_hints: "on"
initdb:
- encoding: UTF8
- data-checksums
pg_hba:
- host replication replicator 192.168.1.0/24 scram-sha-256
- host all all 0.0.0.0/0 scram-sha-256
users:
admin:
password: "StrongPass123!"
options:
- createrole
- createdb
replicator:
password: "ReplPass456!"
options:
- replication
postgresql:
listen: 0.0.0.0:5432
connect_address: 192.168.1.11:5432
data_dir: /var/lib/postgresql/16/main
bin_dir: /usr/lib/postgresql/16/bin
authentication:
replication:
username: replicator
password: "ReplPass456!"
superuser:
username: postgres
password: "SuperPass789!"
Leader Election 機制
Patroni 採用基於 TTL 的分散式鎖實現 Leader Election:
- 所有節點競爭在 etcd 中寫入
/service/postgres-ha/leader鍵 - 成功寫入的節點成為 Primary,持續更新租約
- Leader 鍵過期時,WAL 落差在閾值內的候選節點參與新選舉
# 查看叢集狀態
patronictl -c /etc/patroni/patroni.yml list
# 輸出範例:
# + Cluster: postgres-ha +---------+----+-----------+
# | Member | Role | State | TL | Lag in MB |
# +--------+---------+---------+----+-----------+
# | node1 | Leader | running | 1 | |
# | node2 | Replica | running | 1 | 0.0 |
# | node3 | Replica | running | 1 | 0.0 |
# +--------+---------+---------+----+-----------+
Failover / Switchover 操作
# 計畫性 Switchover(零資料遺失)
patronictl -c /etc/patroni/patroni.yml switchover postgres-ha \
--master node1 \
--candidate node2 \
--scheduled now
# 強制 Failover(緊急情況)
patronictl -c /etc/patroni/patroni.yml failover postgres-ha \
--master node1 \
--candidate node2 \
--force
# 暫停自動 Failover(維護視窗)
patronictl -c /etc/patroni/patroni.yml pause postgres-ha
# 恢復自動 Failover
patronictl -c /etc/patroni/patroni.yml resume postgres-ha
REST API 健康端點
# Primary 節點回傳 200,Standby 回傳 503
curl http://192.168.1.11:8008/primary
# Replica 節點回傳 200,Primary 回傳 503
curl http://192.168.1.11:8008/replica
# 所有健康節點皆回傳 200
curl http://192.168.1.11:8008/health
repmgr — 複寫管理工具
repmgr 由 EDB 維護,提供複寫監控、Failover 管理和叢集拓撲可視化。
核心配置
# /etc/repmgr.conf
node_id=1
node_name=node1
conninfo='host=192.168.1.11 user=repmgr dbname=repmgr connect_timeout=2'
data_directory='/var/lib/postgresql/16/main'
failover=automatic
promote_command='/usr/bin/repmgr standby promote -f /etc/repmgr.conf --log-to-file'
follow_command='/usr/bin/repmgr standby follow -f /etc/repmgr.conf --log-to-file --upstream-node-id=%n'
monitoring_history=yes
monitor_interval_secs=5
reconnect_attempts=3
reconnect_interval=5
常用操作
# 註冊 Primary
repmgr -f /etc/repmgr.conf primary register
# 克隆並註冊 Standby
repmgr -h 192.168.1.11 -U repmgr -d repmgr \
-f /etc/repmgr.conf standby clone
repmgr -f /etc/repmgr.conf standby register
# 查看叢集狀態
repmgr -f /etc/repmgr.conf cluster show
# 計畫性 Switchover(自動跟隨)
repmgr -f /etc/repmgr.conf standby switchover --siblings-follow
# 啟動自動 Failover 守護程序
repmgrd -f /etc/repmgr.conf --daemonize
pg_auto_failover — 輕量方案
由 Microsoft 開源,採用 Monitor 節點集中管理狀態機,架構最簡單:
┌───────────────────┐
│ Monitor Node │
│ :5000 │
└───────┬───────────┘
│
┌───────────▼───┐ ┌────▼──────────┐
│ Primary │───►│ Secondary │
│ node1:5432 │ │ node2:5432 │
└───────────────┘ └───────────────┘
# Monitor 節點
pg_autoctl create monitor \
--pgdata /var/lib/postgresql/monitor \
--pgport 5000 \
--hostname monitor.example.com
# Primary 節點
pg_autoctl create postgres \
--pgdata /var/lib/postgresql/16/main \
--monitor postgresql://autoctl@monitor:5000/pg_auto_failover
# Secondary 節點(自動從 Primary 克隆)
pg_autoctl create postgres \
--pgdata /var/lib/postgresql/16/main \
--monitor postgresql://autoctl@monitor:5000/pg_auto_failover
# 查看狀態
pg_autoctl show state --monitor postgresql://autoctl@monitor:5000/pg_auto_failover
# 執行 Failover
pg_autoctl perform failover --monitor postgresql://autoctl@monitor:5000/pg_auto_failover
HAProxy 讀寫分離
HAProxy 透過 Patroni HTTP 端點感知節點角色,實現動態讀寫分離:
# /etc/haproxy/haproxy.cfg
global
maxconn 100
defaults
mode tcp
timeout client 30m
timeout connect 4s
timeout server 30m
timeout check 5s
# 讀寫端口(只路由到 Primary)
listen postgres-primary
bind *:5432
option httpchk GET /primary
http-check expect status 200
default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
server node1 192.168.1.11:5432 maxconn 100 check port 8008
server node2 192.168.1.12:5432 maxconn 100 check port 8008
server node3 192.168.1.13:5432 maxconn 100 check port 8008
# 唯讀端口(路由到 Replica)
listen postgres-replicas
bind *:5433
option httpchk GET /replica
http-check expect status 200
balance roundrobin
default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
server node1 192.168.1.11:5432 maxconn 100 check port 8008
server node2 192.168.1.12:5432 maxconn 100 check port 8008
server node3 192.168.1.13:5432 maxconn 100 check port 8008
PgBouncer 搭配 HA
PgBouncer 置於 HAProxy 與 PostgreSQL 之間,提供連線池化:
# /etc/pgbouncer/pgbouncer.ini
[databases]
mydb = host=haproxy.example.com port=5432 dbname=mydb
mydb_ro = host=haproxy.example.com port=5433 dbname=mydb
[pgbouncer]
pool_mode = transaction # transaction 模式支援 HA 切換
max_client_conn = 1000
default_pool_size = 20
server_fast_close = 1 # Failover 時立即關閉舊連線
listen_port = 6432
Split-Brain 防護
Fencing 機制
1. 基於 DCS 的 TTL 隔離
舊 Primary 無法更新 etcd Leader 租約時,Patroni 讓它進入 read-only 模式。租約到期後新 Primary 才被允許提升。
2. 自訂 Fencing Hook
# patroni.yml
postgresql:
pre_promote: /usr/local/bin/fence_old_primary.sh
#!/bin/bash
# 透過雲端 API 強制停止舊 Primary
OLD_PRIMARY_INSTANCE_ID="i-0123456789abcdef0"
aws ec2 stop-instances --instance-ids $OLD_PRIMARY_INSTANCE_ID --force
sleep 10
3. Witness Node
在 repmgr 中,Witness Node 不運行 PostgreSQL,僅參與仲裁投票。在 Patroni 中,etcd 叢集本身即扮演 Witness 角色。
三方案比較與選型
| 功能 | Patroni | repmgr | pg_auto_failover |
|---|---|---|---|
| 架構複雜度 | 高(需 DCS 叢集) | 中 | 低 |
| 自動 Failover | 原生支援 | 需 repmgrd | 原生支援 |
| RTO | < 30 秒 | 30~120 秒 | 30~60 秒 |
| REST API | 內建 | 無 | 無 |
| HAProxy 整合 | 原生 | 需自行配置 | 需自行配置 |
| K8s 支援 | Zalando Operator | 有限 | 有限 |
| 適合規模 | 中大型 | 中型 | 中小型 |
選型決策
是否在 Kubernetes 上部署?
├── 是 → CloudNativePG 或 Zalando Operator
└── 否 →
├── 有能力維運 etcd? → Patroni(最佳 RTO)
├── 需要最簡架構? → pg_auto_failover
└── 使用 EDB 企業版? → repmgr
Kubernetes 原生部署
CloudNativePG
CloudNativePG(CNPG)是 CNCF 沙盒項目,功能最完整的 PostgreSQL K8s Operator:
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: pg-cluster
spec:
instances: 3 # 1 Primary + 2 Replica
postgresql:
parameters:
max_connections: "200"
shared_buffers: "256MB"
storage:
size: 100Gi
storageClass: fast-ssd
backup:
barmanObjectStore:
destinationPath: s3://my-pg-backups/
s3Credentials:
accessKeyId:
name: aws-creds
key: ACCESS_KEY_ID
secretAccessKey:
name: aws-creds
key: SECRET_ACCESS_KEY
monitoring:
enablePodMonitor: true
CNPG 自動建立的 Service:
pg-cluster-rw→ Primary(讀寫)pg-cluster-ro→ Replica(唯讀)pg-cluster-r→ 所有健康節點
Zalando PostgreSQL Operator
底層使用 Patroni + Spilo 鏡像,適合已熟悉 Patroni 的團隊:
apiVersion: "acid.zalan.do/v1"
kind: postgresql
metadata:
name: pg-cluster
spec:
teamId: "myteam"
numberOfInstances: 3
volume:
size: 100Gi
postgresql:
version: "16"
users:
myapp_user:
- superuser
- createdb
databases:
myapp: myapp_user
常見陷阱
1. etcd 叢集未達法定人數
# 錯誤:2 節點 etcd,一節點故障後叢集停擺
# 正確:至少 3 節點(容忍 1 故障),5 節點(容忍 2 故障)
2. pg_rewind 未正確配置
# patroni.yml 必須設定
use_pg_rewind: true
# PostgreSQL 必須啟用
wal_log_hints = on # 或使用 data checksums
3. 同步複寫 + 單一 Standby 的可用性陷阱
# 錯誤:synchronous_standby_names = '*' 在唯一 Standby 故障時主節點無法寫入
# 正確:使用 ANY 語法
synchronous_standby_names = 'ANY 1 (node2, node3)'
4. 應用程式未處理連線中斷
Failover 期間(10~30 秒)連線會中斷,應用程式必須實作重試邏輯。
5. Load Balancer 健康檢查間隔過長
HAProxy 的 inter 應設定為 3~5 秒,確保 Failover 後快速切換。
版本演進
| 版本 | HA 相關特性 |
|---|---|
| PG 9.4 | Replication Slots 引入 |
| PG 10 | Quorum-based 同步複寫 |
| PG 11 | pg_rewind 不再需要 wal_log_hints(需 data checksums) |
| PG 12 | pg_promote()、standby.signal |
| PG 13 | max_slot_wal_keep_size |
| PG 15 | 兩階段邏輯複寫 |
| PG 16 | Logical Replication from Standby |
總結
高可用架構是生產環境 PostgreSQL 的必備能力:
- Patroni 是業界標準,RTO < 30 秒,功能最完整,但需要維運 etcd 叢集
- repmgr 適合中型規模,架構較簡單,與 EDB 企業版整合良好
- pg_auto_failover 最輕量,適合快速搭建 HA 原型
- Kubernetes 環境優先選擇 CloudNativePG,新建項目首選
- Split-Brain 防護(Fencing + DCS + Witness)是 HA 架構的最後防線
- 無論選擇哪個方案,都必須定期進行 Failover 演練
下一篇,我們將深入探討 連線池化——掌握 PgBouncer 與 pgpool-II 的配置、調校與效能優化,讓你的 PostgreSQL 連線管理更加高效。