複寫與高可用
單節點資料庫是單點故障風險。本篇介紹 PostgreSQL 的複寫技術與高可用架構。
一、 複寫類型
| 類型 | 方式 | 特點 | 使用場景 |
|---|---|---|---|
| 串流複寫 | WAL 傳輸 | 整個資料庫,即時 | 災難恢復、讀寫分離 |
| 邏輯複寫 | 邏輯變更 | 可選表,跨版本 | 遷移、資料同步 |
二、 串流複寫 (Streaming Replication)
架構
設定 Primary
conf
# postgresql.conf
wal_level = replica
max_wal_senders = 10
wal_keep_size = 1GB# pg_hba.conf
host replication replicator 10.0.0.0/24 scram-sha-256sql
-- 建立複寫用戶
CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'secret';設定 Standby
bash
# 1. 使用 pg_basebackup 取得基礎備份
pg_basebackup -h primary-host -U replicator -D /var/lib/postgresql/16/main -R -P
# -R 會自動產生 standby.signal 和 postgresql.auto.conf檢查自動產生的設定:
conf
# postgresql.auto.conf
primary_conninfo = 'host=primary-host user=replicator password=secret'bash
# 2. 啟動 Standby
systemctl start postgresql確認複寫狀態
sql
-- 在 Primary 上
SELECT client_addr, state, sent_lsn, write_lsn, replay_lsn
FROM pg_stat_replication;
-- 在 Standby 上
SELECT pg_is_in_recovery(); -- 應該返回 true三、 同步 vs 非同步複寫
非同步(預設)
Primary 不等待 Standby 確認,可能丟失最近的交易。
同步複寫
Primary 等待至少一個 Standby 確認:
conf
# postgresql.conf (Primary)
synchronous_commit = on
synchronous_standby_names = 'standby1' # 或 'FIRST 1 (standby1, standby2)'conf
# postgresql.conf (Standby)
cluster_name = 'standby1'| 模式 | 資料安全 | 效能 |
|---|---|---|
| 非同步 | 可能丟失 | 快 |
| 同步 | 零丟失 | 較慢 |
四、 讀寫分離
Standby 可以處理 SELECT 查詢:
conf
# postgresql.conf (Standby)
hot_standby = on應用層設定
javascript
const { Pool } = require("pg");
// 寫入用 Primary
const writePool = new Pool({
host: "primary.db.example.com",
port: 5432,
database: "mydb",
});
// 讀取用 Standby
const readPool = new Pool({
host: "standby.db.example.com",
port: 5432,
database: "mydb",
});
// 讀取
const result = await readPool.query("SELECT * FROM users");
// 寫入
await writePool.query("INSERT INTO users ...");五、 邏輯複寫 (Logical Replication)
優點
- 可選擇性複寫特定表
- 跨 PostgreSQL 版本
- 雙向複寫可能
設定 Publisher
conf
# postgresql.conf
wal_level = logicalsql
-- 建立 Publication
CREATE PUBLICATION my_pub FOR TABLE users, orders;
-- 或複寫所有表
CREATE PUBLICATION my_pub FOR ALL TABLES;設定 Subscriber
sql
-- 建立 Subscription
CREATE SUBSCRIPTION my_sub
CONNECTION 'host=publisher-host dbname=mydb user=replicator password=secret'
PUBLICATION my_pub;監控邏輯複寫
sql
-- Publisher 端
SELECT * FROM pg_stat_replication;
-- Subscriber 端
SELECT * FROM pg_stat_subscription;六、 故障轉移
手動切換
bash
# 在 Standby 上執行
pg_ctl promote -D /var/lib/postgresql/16/main
# 或
SELECT pg_promote();自動切換 - Patroni
Patroni 是流行的 PostgreSQL HA 解決方案:
yaml
# patroni.yml
scope: postgres-cluster
name: node1
restapi:
listen: 0.0.0.0:8008
etcd:
hosts: etcd1:2379,etcd2:2379,etcd3:2379
bootstrap:
dcs:
postgresql:
parameters:
max_connections: 100
postgresql:
listen: 0.0.0.0:5432
data_dir: /var/lib/postgresql/16/main
authentication:
replication:
username: replicator
password: secret七、 HAProxy 負載均衡
# haproxy.cfg
frontend postgres_front
bind *:5432
default_backend postgres_back
backend postgres_back
option httpchk GET /master
http-check expect status 200
server node1 10.0.0.1:5432 check port 8008
server node2 10.0.0.2:5432 check port 8008
server node3 10.0.0.3:5432 check port 8008
# 讀取負載均衡
frontend postgres_read
bind *:5433
default_backend postgres_read_back
backend postgres_read_back
option httpchk GET /replica
http-check expect status 200
balance roundrobin
server node1 10.0.0.1:5432 check port 8008
server node2 10.0.0.2:5432 check port 8008
server node3 10.0.0.3:5432 check port 8008八、 複寫延遲監控
檢查延遲
sql
-- 在 Primary
SELECT client_addr,
pg_wal_lsn_diff(sent_lsn, replay_lsn) AS lag_bytes
FROM pg_stat_replication;
-- 在 Standby
SELECT EXTRACT(EPOCH FROM (now() - pg_last_xact_replay_timestamp())) AS lag_seconds;Grafana 監控
sql
-- Prometheus exporter 查詢
SELECT pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn)
FROM pg_stat_replication;九、 常見架構
一主一從
Primary → Standby (Failover)- 最簡單
- 手動或自動故障轉移
一主多從
Primary → Standby 1 (Read)
→ Standby 2 (Read)
→ Standby 3 (Failover)- 讀寫分離
- 一個專門用於故障轉移
級聯複寫
Primary → Standby 1 → Standby 2
→ Standby 3- 減少 Primary 負載
- 適合跨地域部署
十、 故障恢復程序
Primary 當機
bash
# 1. 確認 Primary 無法恢復
# 2. 選擇最新的 Standby 升級
pg_ctl promote -D /var/lib/postgresql/16/main
# 3. 其他 Standby 重新指向新 Primary
# 編輯 postgresql.auto.conf
primary_conninfo = 'host=new-primary ...'
# 4. 重啟其他 Standby
systemctl restart postgresql舊 Primary 恢復
bash
# 使用 pg_rewind 快速同步(如果 WAL 保留足夠)
pg_rewind --target-pgdata=/var/lib/postgresql/16/main \
--source-server="host=new-primary user=postgres"
# 或重新建立 Standby
pg_basebackup -h new-primary -U replicator -D /var/lib/postgresql/16/main -R總結
| 概念 | 說明 |
|---|---|
| 串流複寫 | WAL 即時傳輸,整個資料庫 |
| 邏輯複寫 | 邏輯變更,可選表 |
| 同步複寫 | 零資料丟失,效能較低 |
| Patroni | 自動故障轉移 |
| HAProxy | 連線負載均衡 |
高可用架構需要根據業務需求選擇適當的複寫策略和故障轉移機制。
進階挑戰
- 設定一主一從的串流複寫架構,並測試故障轉移
- 在應用程式中實作讀寫分離,寫入走主庫、讀取走從庫
- 使用 Patroni 建立一個自動故障轉移的高可用叢集