跳至主要內容
Skip to content

複寫與高可用

單節點資料庫是單點故障風險。本篇介紹 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-256
sql
-- 建立複寫用戶
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 = logical
sql
-- 建立 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連線負載均衡

高可用架構需要根據業務需求選擇適當的複寫策略和故障轉移機制。


進階挑戰

  1. 設定一主一從的串流複寫架構,並測試故障轉移
  2. 在應用程式中實作讀寫分離,寫入走主庫、讀取走從庫
  3. 使用 Patroni 建立一個自動故障轉移的高可用叢集

延伸閱讀與資源


← 上一章:備份與還原 | 返回專題首頁 | 下一章:安全性與權限 →