PostgreSQL高可用架构实战:从流复制到Patroni
前言
PostgreSQL凭借其强大的功能、出色的扩展性和活跃的社区,在企业级应用中越来越受欢迎。然而,要保证数据库7×24小时不间断服务,仅仅部署一个单机实例是远远不够的。本文将系统性地介绍PostgreSQL高可用架构的搭建与运维实践。
一、PostgreSQL复制原理
1.1 WAL(Write-Ahead Logging)
事务流程:
Client → WAL Buffer → WAL File → 磁盘
↓
Shared Buffer → 磁盘 (Checkpoint)
↓
WAL Sender → Standby (流复制)
WAL是PostgreSQL复制的基础,记录了所有对数据库的修改操作。
1.2 流复制架构
┌─────────────────┐
│ Primary │
│ (读写) │
└────────┬─────────┘
│ WAL Stream (streaming)
┌────────┼─────────┐
▼ ▼ ▼
┌────────┐┌────────┐┌────────┐
│Standby ││Standby ││Standby │
│ (只读) ││ (只读) ││ (只读) │
└────────┘└────────┘└────────┘
二、搭建流复制主备集群
2.1 Primary配置
# postgresql.conf
listen_addresses = '*'
wal_level = replica
max_wal_senders = 10
wal_keep_size = 1024 # MB
hot_standby = on
archive_mode = on
archive_command = 'cp %p /data/pg_archive/%f'
# pg_hba.conf
host replication repl_user 192.168.1.0/24 md5
2.2 创建复制用户
CREATE ROLE repl_user WITH LOGIN REPLICATION PASSWORD 'repl_pass';
2.3 搭建Standby
# 从Primary做基础备份
pg_basebackup -h 192.168.1.10 -U repl_user \
-D /var/lib/pgsql/14/data -Fp -Xs -P -R
# Standby自动生成的配置
# standby.signal (空文件,表示这是standby)
# postgresql.auto.conf
primary_conninfo = 'host=192.168.1.10 port=5432 user=repl_user password=repl_pass'
2.4 验证复制状态
-- Primary上查看
SELECT
application_name,
client_addr,
state,
sync_state,
replay_lag
FROM pg_stat_replication;
-- Standby上查看
SELECT
pg_is_in_recovery(),
pg_last_wal_receive_lsn(),
pg_last_wal_replay_lsn();
三、同步复制配置
3.1 同步vs异步
| 模式 | 数据安全性 | 性能影响 | 适用场景 |
|---|---|---|---|
| 异步 | 可能丢数据 | 无影响 | 报表、分析 |
| 同步 | 零数据丢失 | 写入延迟增加 | 核心交易 |
| 半同步(ANY) | 至少一备确认 | 中等 | 平衡方案 |
3.2 配置同步复制
# Primary postgresql.conf
synchronous_commit = on
synchronous_standby_names = 'ANY 1 (standby1, standby2)'
# ANY 1: 至少一个同步备库确认
# FIRST 2: 列表中前两个按顺序确认
四、自动故障切换 — Patroni
4.1 Patroni架构
┌─────────────────────────────────────────┐
│ etcd/dcs │
│ (分布式一致性存储) │
└──┬──────────────┬──────────────┬────────┘
│ │ │
▼ ▼ ▼
┌──────┐ ┌──────┐ ┌──────┐
│Patroni│ │Patroni│ │Patroni│
│ pg1 │◄───▶│ pg2 │◄───▶│ pg3 │
└──┬───┘ └──┬───┘ └──┬───┘
│ │ │
▼ ▼ ▼
┌──────┐ ┌──────┐ ┌──────┐
│ PG │ │ PG │ │ PG │
│Primary│ │Standby│ │Standby│
└──────┘ └──────┘ └──────┘
4.2 Patroni配置
# /etc/patroni/patroni.yml
scope: pg_cluster
namespace: /service/
restapi:
listen: 0.0.0.0:8008
connect_address: 192.168.1.10:8008
etcd:
hosts: 192.168.1.11:2379,192.168.1.12:2379,192.168.1.13:2379
bootstrap:
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 1048576
postgresql:
use_pg_rewind: true
use_slots: true
parameters:
wal_level: replica
hot_standby: "on"
wal_keep_size: 1024
max_wal_senders: 10
max_replication_slots: 10
postgresql:
listen: 0.0.0.0:5432
connect_address: 192.168.1.10:5432
data_dir: /var/lib/pgsql/14/data
pgpass: /tmp/pgpass
authentication:
replication:
username: repl_user
password: repl_pass
superuser:
username: postgres
password: postgres_pass
4.3 日常运维命令
# 查看集群状态
patronictl -c /etc/patroni/patroni.yml list
# 手动切换
patronictl -c /etc/patroni/patroni.yml switchover
# 故障切换(紧急)
patronictl -c /etc/patroni/patroni.yml failover
# 重新加入集群
patronictl -c /etc/patroni/patroni.yml reinit pg_cluster <node>
五、连接管理 — PgBouncer
5.1 配置
# /etc/pgbouncer/pgbouncer.ini
[databases]
mydb = host=192.168.1.10 port=5432 dbname=mydb
[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 25
5.2 连接池模式对比
| 模式 | 特性 | 适用场景 |
|---|---|---|
| session | 连接绑定到会话 | 需要会话状态 |
| transaction | 事务结束后释放 | 推荐,通用场景 |
| statement | 语句结束后释放 | 无状态应用 |
六、监控与告警
6.1 关键监控指标
-- 复制延迟
SELECT
client_addr,
pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS lag_bytes,
EXTRACT(EPOCH FROM (now() - pg_last_xact_replay_timestamp())) AS lag_seconds
FROM pg_stat_replication;
-- 长事务
SELECT pid, now() - xact_start AS duration, query
FROM pg_stat_activity
WHERE state != 'idle' AND xact_start < now() - interval '5 minutes';
6.2 告警规则
- 复制延迟 > 10秒 → 紧急告警
- 复制延迟 > 1秒 → 警告
- Standby异常下线 → 紧急告警
- 连接数 > 80% → 警告
总结
PostgreSQL高可用是一个渐进式的过程:
- 起步:主备流复制(手动切换)
- 进阶:Patroni + etcd(自动切换)
- 完善:PgBouncer连接池 + 监控告警
- 终极:跨机房容灾 + 读写分离
选择适合业务阶段的高可用方案,避免过度设计。