为什么需要分布式 PostgreSQL

随着业务增长,单机 PostgreSQL 在数据量、写入吞吐、高可用方面会遇到瓶颈。虽然 PostgreSQL 本身已经是功能非常强大的关系型数据库,但单机限制决定了它无法无限水平扩展。

为了突破这些限制,我们需要引入分布式方案。

常见的 PostgreSQL 分布式/高可用方案:

方案 特点 适用场景
Patroni + etcd 自动 Failover,高可用 追求高可用,读写分离
Citus 水平分片,分布式查询 分析型、多租户场景
Pgpool-II 连接池 + 读写分离 简单的负载均衡
自建逻辑复制 灵活但运维成本高 跨地域数据同步

本文重点介绍 Patroni + etcd + PostgreSQL 流复制 这套高可用分布式方案。

架构设计

                        ┌─────────────┐
                        │    etcd     │
                        │  (集群共识)  │
                        └──────┬──────┘
                               │
              ┌────────────────┼────────────────┐
              │                │                │
        ┌─────▼──────┐  ┌─────▼──────┐  ┌─────▼──────┐
        │  Patroni   │  │  Patroni   │  │  Patroni   │
        │  + PG 主   │──▶│  + PG 从   │  │  + PG 从   │
        └────────────┘  └────────────┘  └────────────┘
               │
               │  ┌──────────────────┐
               └──▶│  HAProxy / 应用  │
                  └──────────────────┘
  • etcd(也可以是 Consul / Zookeeper):提供分布式锁和集群共识
  • Patroni:管理 PostgreSQL 实例的选主与自动切换
  • PostgreSQL 流复制:主从之间实时同步 WAL 日志
  • HAProxy(可选):对外提供统一的读写入口

环境准备

机器规划

主机名 IP 角色
node1 192.168.1.101 etcd + Patroni + PG
node2 192.168.1.102 etcd + Patroni + PG
node3 192.168.1.103 etcd + Patroni + PG

三节点可以均匀分布 etcd(奇数节点保证选主可用)和 PostgreSQL 实例。

安装 PostgreSQL

# Ubuntu / Debian
sudo apt update
sudo apt install -y postgresql-16 postgresql-client-16

# 查看版本
psql --version

安装 etcd

# 从官网下载最新版本
ETCD_VER=v3.5.17
wget https://github.com/etcd-io/etcd/releases/download/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz
tar xzf etcd-${ETCD_VER}-linux-amd64.tar.gz
sudo mv etcd-${ETCD_VER}-linux-amd64/etcd* /usr/local/bin/

安装 Patroni

# Patroni 推荐用 pip 安装
pip install patroni[etcd3]

建议在虚拟环境中安装,避免系统 Python 环境混乱:

python3 -m venv /opt/patroni-env
source /opt/patroni-env/bin/activate
pip install patroni[etcd3]

配置 etcd 集群

每个节点的 /etc/etcd/etcd.conf.yml

name: node1  # node1 / node2 / node3
data-dir: /var/lib/etcd

initial-advertise-peer-urls: http://192.168.1.101:2380
listen-peer-urls: http://0.0.0.0:2380
advertise-client-urls: http://192.168.1.101:2379
listen-client-urls: http://0.0.0.0:2379

initial-cluster: node1=http://192.168.1.101:2380,node2=http://192.168.1.102:2380,node3=http://192.168.1.103:2380
initial-cluster-state: new

启动 etcd:

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

验证集群状态:

etcdctl member list
etcdctl endpoint health

配置 Patroni

Patroni 配置文件 /etc/patroni/patroni.yml

scope: postgres-cluster
namespace: /db/
name: pg-node1  # 每个节点不同

restapi:
  listen: 0.0.0.0:8008
  connect_address: 192.168.1.101:8008

etcd:
  host: 192.168.1.101:2379  # 本机 etcd

bootstrap:
  dcs:
    ttl: 30
    loop_wait: 10
    retry_timeout: 10
    maximum_lag_on_failover: 1048576
    postgresql:
      use_pg_rewind: true
      parameters:
        max_connections: 100
        wal_level: replica
        hot_standby: "on"
        wal_keep_size: 1024
        max_wal_senders: 5
        max_replication_slots: 5
        wal_log_hints: "on"

  initdb:
    - encoding: UTF8
    - data-checksums

  pg_hba:
    - host replication replicator 192.168.1.0/24 md5
    - host all all 0.0.0.0/0 md5

  users:
    admin:
      password: secure_password_here
      options:
        - superuser
        - createdb
        - createrole

postgresql:
  listen: 0.0.0.0:5432
  connect_address: 192.168.1.101:5432
  data_dir: /data/postgresql/16/main
  bin_dir: /usr/lib/postgresql/16/bin
  pgpass: /tmp/pgpass
  authentication:
    replication:
      username: replicator
      password: repl_password
    superuser:
      username: postgres
      password: super_password
    rewind:
      username: rewind_user
      password: rewind_password

tags:
  nofailover: false
  noloadbalance: false
  clonefrom: false
  nosync: false

注意:每台机器的 nameconnect_address 需要替换为各自的节点名和 IP。

启动 Patroni:

patroni /etc/patroni/patroni.yml

验证集群状态

通过 Patroni 的 REST API 查看集群状态:

curl -s http://192.168.1.101:8008/cluster | jq .

输出示例:

{
  "members": [
    {
      "name": "pg-node1",
      "role": "leader",
      "state": "running",
      "api_url": "http://192.168.1.101:8008",
      "host": "192.168.1.101",
      "port": 5432
    },
    {
      "name": "pg-node2",
      "role": "replica",
      "state": "running",
      "api_url": "http://192.168.1.102:8008",
      "host": "192.168.1.102",
      "port": 5432
    },
    {
      "name": "pg-node3",
      "role": "replica",
      "state": "running",
      "api_url": "http://192.168.1.103:8008",
      "host": "192.168.1.103",
      "port": 5432
    }
  ]
}

登录 PostgreSQL 验证

psql -h 192.168.1.101 -U admin -d postgres
-- 查看当前角色
SELECT pg_is_in_recovery();

-- 查看复制槽
SELECT slot_name, slot_type, active FROM pg_replication_slots;

-- 查看 WAL 接收状态
SELECT application_name, state, sync_state FROM pg_stat_replication;

模拟故障切换

Patroni 最大的价值在于自动 Failover。我们来模拟主节点宕机:

# 在 node1(当前主)上停止 PostgreSQL
sudo systemctl stop postgresql

Patroni 检测到主节点失联后会自动触发选主:

# 查看新的主节点
curl -s http://192.168.1.102:8008/cluster | jq '.members[] | select(.role=="leader") | .name'

输出:pg-node2 成为新的主节点,整个过程约 10-30 秒,业务端可以通过 HAProxy 或连接池自动重连来屏蔽这一抖动。

恢复原主节点后,Patroni 会自动将其作为从节点加入集群:

# node1 恢复后
curl -s http://192.168.1.101:8008/cluster | jq '.members[] | .name + " - " + .role'

整合 HAProxy 做读写分离

为了让应用层不感知数据库切换,可以在每台机器上部署 HAProxy,提供统一的虚拟 IP 入口。

/etc/haproxy/haproxy.cfg

global
    maxconn 2000

defaults
    mode tcp
    timeout connect 5000ms
    timeout client 50000ms
    timeout server 50000ms

frontend pg_read
    bind *:5000
    default_backend pg_read_backend

frontend pg_write
    bind *:5001
    default_backend pg_write_backend

backend pg_read_backend
    option httpchk GET /replica?lag=10MB
    http-check expect status 200

    server node1 192.168.1.101:5432 check port 8008 inter 5000 fall 3
    server node2 192.168.1.102:5432 check port 8008 inter 5000 fall 3
    server node3 192.168.1.103:5432 check port 8008 inter 5000 fall 3

backend pg_write_backend
    option httpchk GET /primary
    http-check expect status 200

    server node1 192.168.1.101:5432 check port 8008 inter 5000 fall 3
    server node2 192.168.1.102:5432 check port 8008 inter 5000 fall 3
    server node3 192.168.1.103:5432 check port 8008 inter 5000 fall 3

HAProxy 通过 Patroni 的 REST API(端口 8008)健康检查,自动将写流量路由到主节点,读流量路由到从节点,主从切换时应用层无需感知。

总结

通过 Patroni + etcd 搭建的 PostgreSQL 集群,具备以下能力:

  • 自动故障切换 — 主节点宕机自动选主,无需人工干预
  • 一致性保证 — etcd 提供分布式锁,避免脑裂
  • 流复制 — 基于 WAL 的实时同步,数据不丢失
  • 优雅恢复 — 旧主恢复后自动以从节点身份加入集群

这套方案已经在生产环境经过广泛验证,无论是云原生环境还是自建机房,都是一个成熟可靠的 PostgreSQL 高可用分布式方案。

参考