为什么需要分布式 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
注意:每台机器的
name、connect_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 高可用分布式方案。