数据库教程FGMT62-PostgreSQL高可用集群项目实战之Patroni
## 第一章 前言与本章大纲
风哥教程本文是《企业级 PostgreSQL 主从复制与高可用集群》篇章体系中的一讲,承接 repmgr 半自动切换方案,重点引入工业界公认最成熟的 PostgreSQL 高可用编排框架——Patroni。本套风哥教程面向已经掌握 PostgreSQL 流复制、WAL 机制、pg_basebackup、keepalived 与 haproxy 基本用法的数据库工程师与运维工程师,目标是在两台 64G 内存、8 颗 vCPU 规格的 Linux 主机上,从零搭建一套具备自动选主、自动故障转移、自动重建备库、读写分离入口、VIP 漂移能力的生产级 PostgreSQL 高可用集群。
风哥在十多年的数据库交付与排障经历中观察到一个普遍现象:不少团队把"流复制 + 自写脚本 + keepalived"当作最终方案,结果在主库宕机、网络分区、磁盘损坏、备库重建、配置漂移等真实场景下反复踩坑,人工干预动辄半小时以上。Patroni 的真正价值在于,它把"谁是主、谁是备、谁应被提升、备库如何被重新拉起"这些动作全部收敛为一份由分布式一致存储(DCS)驱动的状态机,DBA 不再需要手写切换脚本,也不再需要靠经验判断脑裂与谁先接管。
本章大纲如下:
1. Patroni 运行架构与核心概念(理论)
2. 分布式一致存储 etcd 原理与集群部署(理论 + 实战)
3. PostgreSQL + Patroni 集群项目环境准备(实战)
4. Patroni 软件安装与初始化建库(实战)
5. patroni.yml 配置文件逐段精讲(理论 + 实战)
6. Patroni 服务启动、开机自启与集群状态验证(实战)
7. Patroni 关键参数调整与生产固化(实战)
8. 计划内 switchover 与异常 failover 演练(实战)
9. 集群启停顺序与日常运维命令(实战)
10. HAProxy + Keepalived 读写分离入口与 VIP 漂移(实战)
11. 风哥针对本文总结
## 第二章 Patroni 运行架构与核心概念(理论部分)
### 2.1 为什么需要 Patroni
PostgreSQL 原生提供了流复制(Streaming Replication)与 pg_basebackup,但它本身并不知道自己当前是主库还是备库——数据库进程不会自动比较谁的时间线更新,也不会在主库宕机后主动把自己提升为主库。这部分"集群编排"工作必须交给外部组件。常见的开源方案有 repmgr、PAF(Pacemaker 系列)、orchestrator、Patroni 等。风哥之所以把 Patroni 作为本套风哥教程的重点,是因为它同时满足以下生产硬指标:
- 主备角色由分布式一致存储裁决,不依赖任何单点脚本;
- 主库宕机后能在 20~40 秒内自动提升新主;
- 新备库接入时能自动基于 pg_basebackup 拉起,不需要 DBA 手工备份与搬运;
- 主备切换、备库重建、参数漂移收敛都能通过 patronictl 一条命令完成;
- 原生对接 HAProxy / PgBouncer / etcd / Consul / ZooKeeper / Kubernetes。
### 2.2 Patroni 的三层架构
Patroni 整体可分为三层,风哥用一句话概括:底层是被管理的 PostgreSQL 实例,中间是每个节点上常驻的 patroni 守护进程,上层是分布式一致存储 DCS,最外侧是流量入口 HAProxy 与 VIP。
```
+----------------------+
| App / 业务连接 |
+----------+-----------+
|
+----------v-----------+
| HAProxy + Keepalived VIP
+----+------------+---+
| |
+----------v--+ +--v-----------+
| patroni@cn1| | patroni@cn2 |
| postgres |<====>| postgres |
+-----+-------+ 流复制 +-----+-------+
| |
+--------+-----------+
|
+--------v-----------+
| etcd DCS 集群 |
+---------------------+
```
Patroni 进程本身是一个 Python 编写的守护服务,核心职责包括:
1. 周期性(默认 loop_wait=10s)把本节点 PostgreSQL 的角色、LSN、时间线等状态写入 DCS 的 KV 路径 `/service/<scope>/members/<name>`;
2. 周期性从 DCS 读取 `/service/<scope>/leader`,判断"当前声称的主"到底是不是自己;
3. 如果自己是主,则向备库授予复制权限,并把 postgresql.base.conf、pg_hba.conf 等配置统一下发到所有节点;
4. 如果自己是备,则按 DCS 中的 leader 信息搭建并维护流复制;
5. 当 leader 心跳超时(TTL 到期未续约),触发重新选主,通过 DCS 的分布式锁 leader 进行抢锁,抢到者被提升为新主;
6. 对外暴露一个 REST API(默认 8008 端口),供 HAProxy / 监控探活判断节点当前角色。
DCS(Distributed Configuration Store)是 Patroni 的"裁判",它必须在节点之间提供强一致性。Patroni 支持 etcd、Consul、ZooKeeper 与 Kubernetes 的 Lease/ConfigMap,本套风哥教程统一采用 etcd,原因是部署简单、Raft 协议成熟、中文资料丰富、和 PostgreSQL 生态集成顺畅。
HAProxy 与 Keepalived 是流量入口。Patroni 自己并不监听 5432 直接给业务——被它管理的还是 PostgreSQL 本身。业务方连接哪个 IP、哪个端口,写流量打到主、读流量打到备,由 HAProxy 通过 patroni 的 REST API(`/master`、`/replica`、`/read-only`)动态判断后端节点健康度,再借 Keepalived 把一个 VIP 漂到存活的 HAProxy 节点上。
### 2.3 选主与故障切换的时间窗
风哥把 Patroni 选主过程拆成如下几个时间窗,每个都由参数控制:
- `loop_wait`:Patroni 主循环周期,默认 10 秒;
- `ttl`:leader 租约有效期,默认 30 秒;
- `retry_timeout`:单次 etcd/网络操作的重试上限,默认 10 秒;
- `master_start_timeout`:拉起主库的最大等待时间,默认 300 秒;
- `synchronous_node_count`:同步复制需达成的备库数量。
主库正常时,每个 loop_wait 周期 Patroni 都会在 etcd 中续约 leader key,相当于"心跳"。主库宕机后心跳停止,etcd 中该 key 超过 ttl 自动失效。此时所有备库开始竞争 leader key,第一个通过 CAS(compare-and-swap)成功写入者被提升为新主,其余备库继续以它为上游搭建流复制。
整个过程的 RTO 量级约等于 `ttl + loop_wait + promote 时间`,即 20~40 秒。这也是 Patroni 更适用于同城双节点/三节点、而非跨机房低 RPO 场景的原因。
### 2.4 脑裂防护
脑裂(split-brain)指两个节点同时认为自己是主。Patroni 的防护机制非常干脆:**任何节点想写入 WAL,必须先成功在 DCS 中拿到 leader 锁**。由于 DCS 基于 Raft/Paxos 只能让一个客户端持有锁,即便网络分区,被隔离的原主也无法续约锁。它的 PostgreSQL 进程会被 Patroni 主动降级为只读备库,或被强制停机,从而收敛为单一主库。
>
> 风哥 itpux-com
需要特别说明的是:Patroni 并不会自动把"脑裂老主"的数据抹掉,它会在新主选出、老主恢复连通后,借助 pg_rewind 把老主重新拉回为备库。这也是为什么本套风哥教程在配置部分必须显式开启 `use_pg_rewind: true`。
### 2.5 同步复制与异步复制
Patroni 原生支持同步复制。在 patroni.yml 中配置 `synchronous_mode: true` 与 `synchronous_node_count: 1` 后,Patroni 会动态维护 `synchronous_standby_names` 参数,保证至少 N 个备库收到 WAL 才返回 commit,RPO 趋近于零,但写事务的响应时间受主备网络 RTT 影响。本套风哥教程默认先以异步复制跑通整体流程,最后一节再切换到同步模式,让学员对比两种模式的差异。
## 第三章 集群项目环境准备(实战)
### 3.1 集群规划
本套风哥教程所有配置参数均按 64G 内存、8 颗 vCPU 的物理机/虚机规格给出,生产部署时按实际机器倍数等比调整。
| 角色 | 主机名 | 业务 IP | 组件 |
| --- | --- | --- | --- |
| 数据库节点一 | fgedu-net-cn1 | 192.168.56.101 | PostgreSQL + patroni + etcd + haproxy + keepalived |
| 数据库节点二 | fgedu-net-cn2 | 192.168.56.102 | PostgreSQL + patroni + etcd + haproxy + keepalived |
| 虚拟 IP | - | 192.168.56.150 | Keepalived 漂移地址 |
说明:etcd 生产环境建议至少 3 个节点,以满足 Raft 多数派(2/3)要求。本套风哥教程为了在两台主机上完整复现流程,采用 2 节点 etcd 的实验配置;生产落地时务必在第三台机器上追加第三个 etcd 成员,否则任一节点宕机都会导致 DCS 不可用、Patroni 无法选主。
数据库名统一为 `fgedudb`,实例名(数据目录别名)为 `fgedudb`,业务操作用户为 `fgedu`,超级用户沿用 postgres。所有二进制与数据目录统一放在 `/fgedudb` 下,目录规划如下:
```
/fgedudb/
├── pgsql/ # PostgreSQL 软件安装目录
├── etcd/ # etcd 二进制与数据
├── patroni/ # Patroni 配置与日志
├── arch/ # WAL 归档目录
├── pg_log/ # 数据库运行日志
└── data/fgedudb/ # PostgreSQL 数据目录
```
### 3.2 操作系统基础配置(两台机器均执行)
```
# 创建专用用户
useradd -m -s /bin/bash pgadmin
mkdir -p /fgedudb/{pgsql,etcd,patroni,arch,pg_log,data}
chown -R pgadmin:pgadmin /fgedudb
# 关闭防火墙(生产环境按需放开 5432/8008/2379/2380/5000/5001/7000)
systemctl stop firewalld && systemctl disable firewalld
setenforce 0
sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config
# 配置主机名解析
cat >> /etc/hosts <<'EOF'
192.168.56.101 fgedu-net-cn1
192.168.56.102 fgedu-net-cn2
EOF
# 时间同步
yum install -y chrony
systemctl enable --now chronyd
chronyc sources
# 内核参数(64G 内存 / 8C 规格)
cat >> /etc/sysctl.conf <<'EOF'
fs.aio-max-nr = 1048576
fs.file-max = 6815744
kernel.shmall = 4194304
kernel.shmmax = 68719476736
kernel.shmmni = 4096
kernel.sem = 250 32000 100 128
net.core.rmem_default = 262144
net.core.rmem_max = 4194304
net.core.wmem_default = 262144
net.core.wmem_max = 1048576
net.ipv4.tcp_rmem = 4096 87380 4194304
net.ipv4.tcp_wmem = 4096 65536 4194304
net.ipv4.tcp_tw_reuse = 1
vm.overcommit_memory = 2
vm.swappiness = 10
vm.dirty_ratio = 5
vm.dirty_background_ratio = 2
EOF
sysctl -p
# 资源限制
cat >> /etc/security/limits.conf <<'EOF'
pgadmin soft nofile 65536
pgadmin hard nofile 65536
pgadmin soft nproc 65536
pgadmin hard nproc 65536
pgadmin soft memlock unlimited
pgadmin hard memlock unlimited
EOF
```
### 3.3 免密 SSH(备库自动 pg_basebackup 依赖)
```
# 两台机器分别以 pgadmin 执行
su - pgadmin
ssh-keygen -t rsa -N '' -f ~/.ssh/id_rsa
ssh-copy-id pgadmin@fgedu-net-cn1
ssh-copy-id pgadmin@fgedu-net-cn2
```
>
> 风哥教程 113257174
## 第四章 etcd 分布式一致存储部署(实战)
### 4.1 etcd 二进制安装
```
# 两台机器都执行
cd /fgedudb/etcd
wget [https://github.com/etcd-io/etcd/releases/download/v3.5.16/etcd-v3.5.16-linux-amd64.tar.gz](https://github.com/etcd-io/etcd/releases/download/v3.5.16/etcd-v3.5.16-linux-amd64.tar.gz)
tar xf etcd-v3.5.16-linux-amd64.tar.gz
cp etcd-v3.5.16-linux-amd64/etcd etcd-v3.5.16-linux-amd64/etcdctl /fgedudb/etcd/
chown -R pgadmin:pgadmin /fgedudb/etcd
```
### 4.2 fgedu-net-cn1 上的 etcd 配置
```
mkdir -p /fgedudb/etcd/data
cat > /fgedudb/etcd/etcd.conf.yml <<'EOF'
name: cn1
data-dir: /fgedudb/etcd/data
listen-client-urls: [http://192.168.56.101:2379](http://192.168.56.101:2379),[http://127.0.0.1:2379](http://127.0.0.1:2379)
advertise-client-urls: [http://192.168.56.101:2379](http://192.168.56.101:2379)
listen-peer-urls: [http://192.168.56.101:2380](http://192.168.56.101:2380)
initial-advertise-peer-urls: [http://192.168.56.101:2380](http://192.168.56.101:2380)
initial-cluster: cn1=[http://192.168.56.101:2380](http://192.168.56.101:2380),cn2=[http://192.168.56.102:2380](http://192.168.56.102:2380)
initial-cluster-token: fgedu-pg-ha
initial-cluster-state: new
quota-backend-bytes: 8589934592
EOF
```
### 4.3 fgedu-net-cn2 上的 etcd 配置
```
mkdir -p /fgedudb/etcd/data
cat > /fgedudb/etcd/etcd.conf.yml <<'EOF'
name: cn2
data-dir: /fgedudb/etcd/data
listen-client-urls: [http://192.168.56.102:2379](http://192.168.56.102:2379),[http://127.0.0.1:2379](http://127.0.0.1:2379)
advertise-client-urls: [http://192.168.56.102:2379](http://192.168.56.102:2379)
listen-peer-urls: [http://192.168.56.102:2380](http://192.168.56.102:2380)
initial-advertise-peer-urls: [http://192.168.56.102:2380](http://192.168.56.102:2380)
initial-cluster: cn1=[http://192.168.56.101:2380](http://192.168.56.101:2380),cn2=[http://192.168.56.102:2380](http://192.168.56.102:2380)
initial-cluster-token: fgedu-pg-ha
initial-cluster-state: new
quota-backend-bytes: 8589934592
EOF
```
### 4.4 注册 systemd 服务并启动
```
# 两台机器都执行
cat > /etc/systemd/system/etcd.service <<'EOF'
[Unit]
Description=etcd
After=network.target
[Service]
Type=notify
User=pgadmin
ExecStart=/fgedudb/etcd/etcd --config-file /fgedudb/etcd/etcd.conf.yml
Restart=always
RestartSec=5
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now etcd
systemctl status etcd
```
### 4.5 验证 etcd 集群健康
```
export ETCDCTL_API=3
/fgedudb/etcd/etcdctl --endpoints=[http://192.168.56.101:2379](http://192.168.56.101:2379),[http://192.168.56.102:2379](http://192.168.56.102:2379) endpoint health
/fgedudb/etcd/etcdctl --endpoints=[http://192.168.56.101:2379](http://192.168.56.101:2379),[http://192.168.56.102:2379](http://192.168.56.102:2379) endpoint status --write-out=table
```
>
> 风哥数据库教程 itpux-com
预期两个 endpoint 均返回 `is healthy: true`,成员列表包含 cn1、cn2。生产环境追加第 3 个节点时,先在现有任一节点执行 `etcdctl member add cn3 --peer-urls=[http://192.168.56.103:2380](http://192.168.56.103:2380)`,再在新机器上以 `initial-cluster-state: existing` 启动,即可完成扩容。
## 第五章 PostgreSQL 与 Patroni 安装(实战)
### 5.1 PostgreSQL 软件安装
以源码方式编译安装到 `/fgedudb/pgsql`,编译参数按 64G/8C 规格对齐:
```
# 安装依赖
yum install -y gcc readline-devel zlib-devel openssl-devel libxml2-devel \
perl-IPC-Run python3 python3-devel python3-pip epel-release
# 解压源码(以社区稳定版为例,本套风哥教程不锁定具体版本号)
cd /usr/local/src
tar xf postgresql-latest.tar.gz
cd postgresql-*
./configure --prefix=/fgedudb/pgsql \
--with-openssl --with-libxml --with-python \
--enable-thread-safety --enable-nls
make -j 8
make install
# 安装 contrib(pg_rewind、pg_stat_statements、pg_prewarm)
cd contrib
make -j 8 && make install
```
配置环境变量:
```
cat >> /home/pgadmin/.bash_profile <<'EOF'
export PGHOME=/fgedudb/pgsql
export PGDATA=/fgedudb/data/fgedudb
export PATH=$PGHOME/bin:$PATH:/fgedudb/etcd
export LD_LIBRARY_PATH=$PGHOME/lib:$LD_LIBRARY_PATH
EOF
source /home/pgadmin/.bash_profile
```
### 5.2 Patroni 安装
```
# 两台机器都用 pip 安装(离线环境请在同版本机器上 pip download 后拷贝)
pip3 install -U pip
pip3 install patroni[etcd] psycopg2-binary
# 验证
patroni --version
which patroni
which patronictl
```
>
> 网上搜索风哥教程可以学习全套数据库教程
## 第六章 patroni.yml 配置文件精讲(理论 + 实战)
Patroni 的灵魂是 `/fgedudb/patroni/patroni.yml`。风哥把这份配置逐段拆开讲解,方便学员在生产环境按需裁剪。
### 6.1 fgedu-net-cn1 配置
```
scope: fgedudb
name: cn1
restapi:
listen: 192.168.56.101:8008
connect_address: 192.168.56.101:8008
authentication:
username: patroni
password: 'Patroni@2026'
etcd:
hosts: 192.168.56.101:2379,192.168.56.102:2379
bootstrap:
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 1048576
master_start_timeout: 300
synchronous_mode: false
postgresql:
use_pg_rewind: true
use_slots: true
parameters:
max_connections: 500
superuser_reserved_connections: 10
shared_buffers: 16GB
effective_cache_size: 48GB
maintenance_work_mem: 2GB
work_mem: 16MB
wal_level: replica
max_wal_senders: 10
max_replication_slots: 10
wal_keep_size: 4GB
checkpoint_timeout: 15min
max_wal_size: 4GB
min_wal_size: 1GB
archive_mode: on
archive_command: 'test ! -f /fgedudb/arch/%f && cp %p /fgedudb/arch/%f'
archive_timeout: 300
log_destination: csvlog
logging_collector: on
log_directory: /fgedudb/pg_log
log_filename: postgresql-%a.log
log_rotation_age: 1d
log_line_prefix: '%t [%p]: [%l-1] user=%u,db=%d,app=%a,client=%h '
log_min_duration_statement: 1000
pg_stat_statements.max: 10000
pg_stat_statements.track: all
recovery_conf:
restore_command: 'cp /fgedudb/arch/%f %p'
initdb:
- auth-host: md5
- auth-local: trust
- encoding: UTF8
- data-checksums
pg_hba:
- local all all trust
- host all all 127.0.0.1/32 md5
- host all all 0.0.0.0/0 md5
- host replication fgedu 192.168.56.101/32 md5
- host replication fgedu 192.168.56.102/32 md5
- host all fgedu 0.0.0.0/0 md5
users:
fgedu:
password: 'Fgedu@2026'
options: createrole createdb superuser
post_bootstrap: init_extensions.sh
postgresql:
listen: 192.168.56.101:5432
connect_address: 192.168.56.101:5432
data_dir: /fgedudb/data/fgedudb
config_dir: /fgedudb/data/fgedudb
pgpass: /fgedudb/patroni/.pgpass
bin_dir: /fgedudb/pgsql/bin
authentication:
superuser:
username: postgres
password: 'Postgres@2026'
replication:
username: fgedu
password: 'Replica@2026'
parameters:
unix_socket_directories: /fgedudb/data/fgedudb,/tmp
tags:
nofailover: false
noloadbalance: false
clonefrom: false
nosync: false
```
### 6.2 fgedu-net-cn2 配置差异点
cn2 上的 patroni.yml 与 cn1 几乎一致,仅修改以下字段:
```
scope: fgedudb
name: cn2
restapi:
listen: 192.168.56.102:8008
connect_address: 192.168.56.102:8008
postgresql:
listen: 192.168.56.102:5432
connect_address: 192.168.56.102:5432
```
### 6.3 关键参数解释
- `scope`:集群名,决定 etcd 中的 key 前缀,两台必须一致;
- `name`:本节点名,必须唯一;
- `ttl / loop_wait / retry_timeout`:即第二章讲过的选主时间窗;
- `use_pg_rewind: true`:脑裂老主恢复后自动回退到备库;
- `use_slots: true`:用复制槽防止 WAL 被提前回收导致备库断流;
- `shared_buffers = 16GB`:64G 内存取 1/4,是官方建议起点;
- `effective_cache_size = 48GB`:约 75% 物理内存,供优化器估算缓存;
- `archive_mode + archive_command`:本地归档到 `/fgedudb/arch`,配合 restore_command 实现 PITR;
- `pg_hba` 中必须允许 `fgedu` 用户从两台主机做 replication,否则 Patroni 拉起备库时认证会失败。
## 第七章 启动 Patroni 并初始化主库(实战)
### 7.1 准备目录与初始化脚本
```
# 两台机器
mkdir -p /fgedudb/arch /fgedudb/pg_log /fgedudb/data
chown -R pgadmin:pgadmin /fgedudb
# post_bootstrap 钩子,初始化完成后创建扩展
cat > /fgedudb/patroni/init_extensions.sh <<'EOF'
#!/bin/bash
psql -U postgres -d fgedudb -c "CREATE EXTENSION IF NOT EXISTS pg_stat_statements;"
psql -U postgres -d fgedudb -c "CREATE EXTENSION IF NOT EXISTS pg_prewarm;"
EOF
chmod +x /fgedudb/patroni/init_extensions.sh
```
### 7.2 先启动 cn1,使其成为第一个主库
```
su - pgadmin
patroni /fgedudb/patroni/patroni.yml &
```
Patroni 第一次启动时会在空 data_dir 上调用 initdb 创建数据目录,写 postgresql.conf 与 pg_hba.conf,然后在 etcd 中把自己注册为 leader。观察日志:
```
2026-xx-xx INFO: bootstrapping postgres cluster
2026-xx-xx INFO: initialized a new cluster
2026-xx-xx INFO: Lock owner: cn1; I am cn1
2026-xx-xx INFO: promoted to leader
```
验证:
```
psql -U postgres -h 192.168.56.101 -c "select pg_is_in_recovery();"
# 预期返回 f(false),表示当前为可写主库
psql -U postgres -h 192.168.56.101 -c "\l"
# 应能看到 fgedudb 数据库已自动创建
```
### 7.3 启动 cn2,自动成为备库
```
su - pgadmin
patroni /fgedudb/patroni/patroni.yml &
```
cn2 启动时发现 etcd 中已有 leader(cn1),它会:
1. 用复制用户 `fgedu` 通过 pg_basebackup 把 cn1 的数据拉到本地 `/fgedudb/data/fgedudb`;
2. 写入 standby.signal 与 primary_conninfo;
3. 在 etcd 中把自己注册为 `/service/fgedudb/members/cn2`,角色 replica。
验证流复制:
```
# 在主库 cn1 上
psql -U postgres -h 192.168.56.101 -c "select client_addr, state, sync_state from pg_stat_replication;"
# 应看到 192.168.56.102,state=streaming,sync_state=async
```
### 7.4 用 patronictl 查看集群
```
export PATRONICTL_CONFIG_FILE=/fgedudb/patroni/patroni.yml
patronictl list
```
>
> 上51CTO搜索风哥可以学习全套数据库教程
预期输出:
```
+ Cluster: fgedudb (xxxxxxxxx) ------+----+-----------+
| Member | Host | Role | State | TL |
| cn1 | 192.168.56.101| Leader | running | 1 |
| cn2 | 192.168.56.102| Replica | running | 1 |
+--------+-------------+---------+---------+----+
```
### 7.5 注册 systemd 服务(两台机器)
```
cat > /etc/systemd/system/patroni.service <<'EOF'
[Unit]
Description=Patroni
After=network.target etcd.service
[Service]
Type=simple
User=pgadmin
Environment=PGHOME=/fgedudb/pgsql
Environment=PATH=/fgedudb/pgsql/bin:/fgedudb/etcd:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
ExecStart=/usr/local/bin/patroni /fgedudb/patroni/patroni.yml
Restart=always
RestartSec=10
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now patroni
systemctl status patroni
```
注意:如果之前是用 `patroni ... &` 前台启动的,需要先 `pkill patroni` 再用 systemd 接管,避免双实例抢同一 data_dir。
## 第八章 关键参数调整与生产固化(实战)
### 8.1 在线修改 PostgreSQL 参数
许多 DBA 习惯直接改 postgresql.conf 然后 reload,但在 Patroni 集群里,**直接改文件会被 Patroni 在下一个 loop 周期覆盖回 DCS 中的值**。正确做法是把参数写进 DCS:
```
patronictl edit-config
```
打开 YAML 编辑器,在 `postgresql.parameters` 段下追加:
```
postgresql:
parameters:
log_min_duration_statement: 500
log_checkpoints: on
log_lock_waits: on
log_statement: ddl
idle_in_transaction_session_timeout: 300000
tcp_keepalives_idle: 60
tcp_keepalives_interval: 10
tcp_keepalives_count: 6
```
保存退出后,Patroni 会自动把配置下发到所有节点并 reload。需要 restart 才生效的参数(如 shared_buffers)由 Patroni 在切换时统一处理。
### 8.2 复制槽
Patroni 配置 `use_slots: true` 后,会自动为每个备库创建 replication slot(名字与节点 name 一致)。查看:
```
SELECT slot_name, slot_type, active, restart_lsn FROM pg_replication_slots;
```
复制槽的作用是保证主库在备库临时断连期间不回收该备库尚未接收的 WAL,避免恢复时因 WAL 缺失而必须全量重拉。生产环境务必保留。
### 8.3 同步复制开关
在 `patronictl edit-config` 中:
```
synchronous_mode: true
synchronous_mode_strict: true
synchronous_node_count: 1
```
保存后 Patroni 会把 `synchronous_standby_names` 自动设置为 `FIRST 1 (cn2)`。此时主库 commit 必须等 cn2 收到 WAL。如果 cn2 宕机且 `synchronous_mode_strict=true`,主库会拒绝写事务,防止数据丢失——这是最稳妥但可用性最低的模式。风哥的建议是:金融核心库开 strict,普通业务库只开 `synchronous_mode=true` 不开 strict。
>
> 风哥 itpux-com
## 第九章 高可用切换演练(实战)
### 9.1 计划内 switchover
```
patronictl switchover
```
交互提示:
```
Master [cn1]: cn1
Candidate ['cn2']: cn2
When [now]: now
```
确认后 Patroni 会:
1. 在 cn1 上执行 `pg_ctl promote` 的反向操作——先停止客户端写入;
2. 把 leader 锁从 etcd 中释放;
3. cn2 抢锁成功,被提升为新主;
4. cn1 自动降级为备库,并通过 pg_rewind 回退时间线,重新以 cn2 为上游搭建流复制。
再次 `patronictl list`,应当看到 cn2 变成 Leader、cn1 变成 Replica,且 `pg_stat_replication` 方向反转。
### 9.2 异常 failover 演练
先在 cn1 上强行停止 PostgreSQL 进程(模拟主库宕机):
```
# 假设当前 leader 是 cn1
pg_ctl stop -D /fgedudb/data/fgedudb -m immediate
# 或 pkill -9 postgres
```
等待 `ttl`(30 秒)+ `loop_wait`(10 秒)约 40 秒,在 cn2 上观察:
```
patronictl list
```
预期 cn2 自动被提升为 Leader,cn1 状态变为 stopped/unknown。此时业务通过 HAProxy 入口(下一章配置)应当已切到 cn2,RTO 约 40 秒。
修复 cn1:
```
# 在 cn1 上修复硬件/磁盘后
systemctl start patroni
```
cn1 的 patroni 进程启动后会发现 DCS 中 leader 已经是 cn2,本地 data_dir 时间线落后于新主,Patroni 会自动调用 pg_rewind,把 data_dir 回退到与 cn2 一致的时间线,然后以备库身份重新接入。整个过程无需人工 pg_basebackup。
### 9.3 网络分区演练
把 cn1(原主)从网络隔离(DROP etcd 端口):
```
iptables -A OUTPUT -p tcp --dport 2379 -j DROP
```
cn1 的 patroni 无法续约 leader 锁,etcd 在 ttl 到期后释放锁,cn2 被提升。cn1 上的 PostgreSQL 虽然仍以可写模式运行,但 Patroni 会检测到"自己不再是 leader"并主动把 PostgreSQL 降级为只读。恢复网络后:
```
iptables -F
```
cn1 通过 pg_rewind 自动回到备库身份。这一步就是脑裂防护的完整闭环。
>
> 网上搜索风哥教程可以学习全套数据库教程
## 第十章 集群启停(实战)
### 10.1 正常停机顺序
先停备、再停主:
```
# 1. 停 cn2(备)
systemctl stop patroni
# 2. 在 cn1 上确认无复制连接后停主
systemctl stop patroni
# 3. 最后停 etcd(如需整机维护)
systemctl stop etcd
```
### 10.2 正常启动顺序
先启 etcd,再启主,最后启备:
```
# 1. 两台都启 etcd
systemctl start etcd
# 2. 先启预期主节点(cn1)
systemctl start patroni
# 3. 观察 cn1 成为 leader 后,再启 cn2
systemctl start patroni
```
如果启动顺序反了(先启 cn2),cn2 会因 etcd 中无 leader 而被选为新主,cn1 启动后再降级为备。这在灾难恢复时没问题,但会打乱既定主备位置,因此日常操作建议按顺序。
### 10.3 常用运维命令
```
# 查看集群
patronictl list
# 查看配置
patronictl show-config
# 编辑配置(写 DCS)
patronictl edit-config
# 手动切换
patronictl switchover
# 强制 failover(不指定 candidate,由 Patroni 选)
patronictl failover
# 重启某节点 PostgreSQL(不影响集群)
patronictl restart fgedudb cn1
# 重新加载配置
patronictl reload fgedudb
# 查看历史
patronictl history fgedudb
# 查看集群拓扑
patronictl topology fgedudb
```
>
> 风哥教程 113257174
## 第十一章 HAProxy + Keepalived 读写分离入口(实战)
### 11.1 安装 HAProxy
```
yum install -y haproxy
```
>
> 上51CTO搜索风哥可以学习全套数据库教程
### 11.2 /etc/haproxy/haproxy.cfg(两台机器配置相同)
```
global
log /dev/log local0 info
maxconn 4096
user haproxy
group haproxy
daemon
defaults
log global
mode tcp
option tcplog
option tcpka
timeout connect 5s
timeout client 1h
timeout server 1h
timeout queue 10s
frontend fgedudb_write
bind 0.0.0.0:5000
default_backend fgedudb_write_backend
backend fgedudb_write_backend
option httpc-h-k GET /master
http-check expect status 200
server cn1 192.168.56.101:5432 check port 8008 inter 2000 fall 2 rise 2
server cn2 192.168.56.102:5432 check port 8008 inter 2000 fall 2 rise 2
frontend fgedudb_read
bind 0.0.0.0:5001
default_backend fgedudb_read_backend
backend fgedudb_read_backend
option httpc-h-k GET /replica
http-check expect status 200
server cn1 192.168.56.101:5432 check port 8008 inter 2000 fall 2 rise 2
server cn2 192.168.56.102:5432 check port 8008 inter 2000 fall 2 rise 2
listen stats
bind 0.0.0.0:7000
stats enable
stats uri /dbs
stats auth admin:Stats@2026
```
启动:
```
systemctl enable --now haproxy
```
业务方连接规则:
- 写连接:`host=192.168.56.150 port=5000`,HAProxy 通过 patroni 的 `/master` 接口把流量打到当前主库;
- 读连接:`host=192.168.56.150 port=5001`,通过 `/replica` 接口把流量打到备库;
- 健康检查:HAProxy 每 2 秒请求一次 8008 端口,Patroni 对非主节点的 `/master` 返回 503,对非备节点的 `/replica` 返回 503,HAProxy 自动剔除。
### 11.3 Keepalived 配置 VIP
主节点 fgedu-net-cn1 的 `/etc/keepalived/keepalived.conf`:
```
global_defs {
router_id NG_Master
}
vrrp_script c-h-k_haproxy {
script "/usr/bin/pgrep -x haproxy"
interval 2
weight -20
}
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass FgeduVIP
}
virtual_ipaddress {
192.168.56.150/24 dev eth0
}
track_script {
c-h-k_haproxy
}
}
```
备节点 fgedu-net-cn2 配置差异:`state BACKUP`、`priority 90`。
启动:
```
systemctl enable --now keepalived
ip addr show eth0
```
验证 VIP 漂移:在 cn1 上 `systemctl stop haproxy`,2 秒内 VIP 漂到 cn2。业务连接通过 VIP 无感切换。
>
> 风哥数据库教程 itpux-com
## 第十二章 风哥针对本文总结
风哥针对本文总结如下:本套风哥教程用两台 64G/8C 的 Linux 主机,完整跑通了 PostgreSQL + Patroni + etcd + HAProxy + Keepalived 的高可用栈。核心要点回顾:
1. 架构上,Patroni 是状态机、etcd 是裁判、HAProxy 是入口、Keepalived 是 VIP 漂移工具,四者缺一不可;
2. 配置上,所有 PostgreSQL 参数必须通过 `patronictl edit-config` 写到 DCS,直接改 postgresql.conf 会被覆盖;
3. 时间窗上,`ttl=30s + loop_wait=10s` 决定了 RTO 约 40 秒,调小 ttl 可加快切换但会增加误判概率,生产不建议低于 15 秒;
4. 脑裂防护靠 `use_pg_rewind: true` 与 DCS 锁,老主恢复网络后自动回退为备库,不需要人工 pg_basebackup;
5. 同步复制上,金融核心库开 `synchronous_mode + synchronous_mode_strict`,普通业务库只开异步;
6. 运维命令上,`patronictl list / switchover / failover / restart / edit-config` 是日常最高频的五条;
7. 流量入口上,业务永远连 VIP:5000(写)与 VIP:5001(读),不要直连数据库 IP,否则切换对业务不透明;
8. 生产加固上,etcd 必须 3 节点、patroni REST API 必须加认证、pg_hba 严格限制来源 IP、归档目录必须异地拷贝、pg_stat_statements 必须开启用于慢 SQL 分析。
风哥在实际项目中还会叠加 PgBouncer 做连接池、叠加 Prometheus + patroni_exporter 做指标监控、叠加 WAL-G 做异地 WAL 归档,这些属于本套风哥教程后续篇章,本篇不再展开。希望学员按本文步骤在测试环境完整走一遍 switchover、failover、网络分区三类演练,再把这套栈搬进生产——没有在测试环境被你杀过十次主库的 Patroni 集群,不应该上线。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/9664900/viewspace-3186256/ ,如需转载,请注明出处,否则将追究法律责任。