背景
单机PostgreSQL数据库在面临服务器硬件故障、操作系统崩溃或网络中断时,容易导致服务中断,无法保证业务连续性。这种架构缺乏高可用机制,故障恢复依赖人工干预,不仅操作复杂,而且恢复时间目标(RTO)较长,难以满足现代业务对高可靠性、可扩展性及容灾能力的需求。
为确保数据库服务的高可用性,避免因宕机影响业务,构建健壮的PostgreSQL高可用(HA)架构至关重要。借助Patroni、HAProxy、Keepalived与etcd等组件的协同工作,可以有效地实现这一目标。
组件介绍
Patroni
Patroni是一个基于Python构建的PostgreSQL高可用性解决方案。它支持多种分布式配置存储后端,如ZooKeeper、etcd、Consul或Kubernetes,兼容PostgreSQL 9.3及以上版本,负责自动化故障转移、集群状态管理与PostgreSQL实例的生命周期管理。
HAProxy
HAProxy是一款高性能的开源负载均衡器与反向代理,支持TCP和HTTP应用。它能高效分配工作负载,从而提升应用性能,显著缩短响应时间并提高吞吐量。在本架构中,它负责将客户端读写请求智能路由到正确的PostgreSQL节点。
Keepalived
Keepalived是一个提供负载均衡和高可用性功能的框架。它通过实现一组健康检查器,能够根据后端服务器的健康状况动态地维护和管理服务器池。在本方案中,它主要用于创建和管理一个虚拟IP(VIP),实现客户端的无感知故障切换。
ETCD
ETCD是一个高度一致的分布式键值存储,为分布式系统或机器集群提供了可靠的数据存储访问方式。它能够优雅地处理网络分区期间的领导者选举,并容忍机器故障。在本架构中,etcd集群用于存储PostgreSQL高可用集群的配置与状态数据,供Patroni、HAProxy和Keepalived协调使用。
watchdog
watchdog进程负责监控PostgreSQL节点及其相关进程的健康状况。它确保节点正常运行并能处理请求。一旦检测到问题,watchdog可以采取措施恢复节点或触发到备用节点的故障转移,是防止“脑裂”情况的关键组件。
环境信息
| hostname |
IP 地址 |
CPU(Core) |
mem |
配置组件 |
| mdw |
10.0.12.9 |
4 |
8GB |
etcd, HAProxy, keepalived, Patroni and PostgreSQL, pgBouncer |
| standby |
10.0.12.4 |
4 |
8GB |
etcd, HAProxy, keepalived, Patroni and PostgreSQL, pgBouncer |
| standby1 |
10.0.16.11 |
4 |
8GB |
etcd, HAProxy, keepalived, Patroni and PostgreSQL, pgBouncer |
| ha-vip |
10.0.12.10 |
4 |
8GB |
HA-VIP 高可用虚拟IP |
防火墙设置
防火墙直接关闭
若环境允许,关闭防火墙是最简单的方式。
-- 关闭防火墙
sudo systemctl stop firewalld
-- 查看防火墙的状态
sudo systemctl status firewalld
开放指定的端口
若需保持防火墙开启,则需按需开放以下组件端口。
Patroni
HAProxy
etcd
Keepalived
Pgbouncer
PostgreSQL
Web server
安装 PostgreSQL 17
在所有节点上安装PostgreSQL 17(仅安装,不初始化),Patroni将在启动时自动初始化数据库实例。
-- 安装编译工具和依赖
sudo yum groupinstall "Development Tools" -y
sudo yum install readline-devel zlib-devel libicu-devel openssl-devel pam-devel libxml2-devel libxslt-devel systemd-devel -y
-- 创建用户和数据目录
sudo groupadd postgres
sudo useradd -g postgres postgres
sudo passwd postgres
sudo mkdir -p /opt/pgsql
sudo chown postgres:postgres /opt/pgsql
sudo mkdir -p /opt/pgdata17.4
sudo chown postgres:postgres /opt/pgdata17.4
-- 下载并编译安装PostgreSQL
cd /root
wget https://ftp.postgresql.org/pub/source/v17.4/postgresql-17.4.tar.gz
tar -zxvf postgresql-17.4.tar.gz
cd postgresql-17.4
./configure --prefix=/opt/pgsql --with-openssl --with-libxml --with-icu --with-systemd
make -j$(nproc)
sudo make install
部署 ETCD 集群
在所有三个节点上部署etcd,组成高可用的etcd集群,为Patroni提供可靠的配置存储服务。深入理解分布式键值存储与中间件的原理,对于维护此类高可用架构至关重要。
源码安装 ETCD 节点
安装 GO 程序
yum install -y go
编译 ETCD
git clone https://github.com/etcd-io/etcd.git
cd etcd
git checkout v3.5.12
./build.sh
sudo cp bin/etcd* /usr/local/bin/
配置 ETCD 服务
配置主机名与IP映射:
10.0.12.4 standby
10.0.12.9 mdw
10.0.16.11 standby1
在 mdw 节点配置 /etc/etcd/etcd.conf.yml:
name: mdw
data-dir: /var/lib/etcd
listen-peer-urls: http://10.0.12.9:2380
listen-client-urls: http://10.0.12.9:2379,http://127.0.0.1:2379
advertise-client-urls: http://10.0.12.9:2379
initial-advertise-peer-urls: http://10.0.12.9:2380
initial-cluster: standby=http://10.0.12.4:2380,mdw=http://10.0.12.9:2380,standby1=http://10.0.16.11:2380
initial-cluster-token: etcd-cluster-01
initial-cluster-state: new
在 standby 节点配置(仅 name 和涉及自身IP的字段不同):
name: standby
data-dir: /var/lib/etcd
listen-peer-urls: http://10.0.12.4:2380
listen-client-urls: http://10.0.12.4:2379,http://127.0.0.1:2379
advertise-client-urls: http://10.0.12.4:2379
initial-advertise-peer-urls: http://10.0.12.4:2380
initial-cluster: standby=http://10.0.12.4:2380,mdw=http://10.0.12.9:2380,standby1=http://10.0.16.11:2380
initial-cluster-token: etcd-cluster-01
initial-cluster-state: new
在 standby1 节点配置:
name: standby1
data-dir: /var/lib/etcd
listen-peer-urls: http://10.0.16.11:2380
listen-client-urls: http://10.0.16.11:2379,http://127.0.0.1:2379
advertise-client-urls: http://10.0.16.11:2379
initial-advertise-peer-urls: http://10.0.16.11:2380
initial-cluster: standby=http://10.0.12.4:2380,mdw=http://10.0.12.9:2380,standby1=http://10.0.16.11:2380
initial-cluster-token: etcd-cluster-01
initial-cluster-state: new
每个节点配置systemd服务文件 /etc/systemd/system/etcd.service:
[Unit]
Description=etcd
Documentation=https://github.com/etcd-io/etcd
After=network.target
[Service]
Type=notify
User=etcd
ExecStart=/usr/local/bin/etcd --config-file=/etc/etcd/etcd.conf.yml
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
启动并验证etcd集群:
sudo systemctl daemon-reload
sudo systemctl start etcd
sudo systemctl enable etcd
sudo systemctl status etcd
查看集群状态与健康:
etcdctl --endpoints=http://10.0.12.9:2379,http://10.0.12.4:2379,http://10.0.16.11:2379 endpoint status -w table
etcdctl --endpoints=http://10.0.12.9:2379 member list -w table
etcdctl --endpoints=http://10.0.12.9:2379 endpoint health --cluster
部署 keepalived
在每台服务器上部署Keepalived以创建VIP,实现客户端无感知故障切换。
yum install -y keepalived
echo "net.ipv4.ip_nonlocal_bind = 1" >> /etc/sysctl.conf
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
sudo sysctl --system
sudo sysctl -p
配置 keepalived.conf 文件
使用 ifconfig 获取网络接口名(如eth0),并在每台机器上配置。
配置 mdw (MASTER)
cp /etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf.back
编辑 /etc/keepalived/keepalived.conf:
vrrp_script check_haproxy {
script "pkill -0 haproxy"
interval 2
weight 2
}
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 101
advert_int 1
virtual_ipaddress {
192.168.231.140
}
track_script {
check_haproxy
}
}
配置 standby 和 standby1 (BACKUP)
配置内容类似,主要区别在于 state BACKUP 和 priority 100。
vrrp_script check_haproxy {
script "pkill -0 haproxy"
interval 2
weight 2
}
vrrp_instance VI_1 {
state BACKUP
interface eth0
virtual_router_id 51
priority 100
advert_int 1
virtual_ipaddress {
192.168.231.140
}
track_script {
check_haproxy
}
}
开启 keepalived 服务
每台机器上执行:
systemctl start keepalived
systemctl enable keepalived
systemctl status keepalived
使用 ip addr 命令检查各节点是否成功绑定了虚拟IP 192.168.231.140。
配置 HAProxy
安装 HAProxy
yum install -y haproxy
配置HAProxy
在mdw节点上配置 /etc/haproxy/haproxy.cfg,此配置是运维与DevOps实践中负载均衡的典型应用。
cp /etc/haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg.back
将以下配置写入文件:
# /etc/haproxy/haproxy.cfg
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin
stats timeout 30s
user haproxy
group haproxy
daemon
maxconn 1000
defaults
mode tcp
log global
option tcplog
retries 3
timeout queue 1m
timeout connect 4s
timeout client 60m
timeout server 60m
timeout check 5s
maxconn 900
# 监控页面(HTTP)
listen stats
mode http
bind *:7000
stats enable
stats uri /
stats refresh 10s
stats admin if TRUE
# 写流量:只路由到主库(Patroni /master 返回 200)
listen primary
bind 192.168.231.140:5000
mode tcp
balance first
option httpchk OPTIONS /master
http-check expect status 200
default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
server mdw 10.0.12.9:5432 maxconn 100 check port 8008
server standby 10.0.12.4:5432 maxconn 100 check port 8008
server standby1 10.0.16.11:5432 maxconn 100 check port 8008
# 读流量:可路由到任意副本(/replica 或 /read-only 返回 200)
listen standby
bind 192.168.231.140:5001
mode tcp
balance roundrobin
option httpchk OPTIONS /replica
http-check expect status 200
default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
server mdw 10.0.12.9:5432 maxconn 100 check port 8008
server standby 10.0.12.4:5432 maxconn 100 check port 8008
server standby1 10.0.16.11:5432 maxconn 100 check port 8008
校验并分发配置
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
scp /etc/haproxy/haproxy.cfg standby:/etc/haproxy/haproxy.cfg
scp /etc/haproxy/haproxy.cfg standby1:/etc/haproxy/haproxy.cfg
创建目录并赋权(每台服务器)
sudo mkdir -p /var/lib/haproxy
sudo chown root:root /var/lib/haproxy
sudo chmod 755 /var/lib/haproxy
sudo mkdir -p /run/haproxy
sudo chown haproxy:haproxy /run/haproxy
sudo chmod 755 /run/haproxy
启动服务(每台服务器)
sudo systemctl daemon-reload
systemctl start haproxy
systemctl enable haproxy
systemctl status haproxy
启用 watchdog
Watchdog用于防止Patroni进程假死(脑裂),当Patroni无法正常发送心跳时,系统将在超时后自动重启节点。
watchdog 安装部署(每台节点)
sudo yum install -y watchdog
sudo modprobe softdog
echo "softdog" | sudo tee /etc/modules-load.d/softdog.conf
lsmod | grep softdog
sudo cat /etc/watchdog.conf | grep -v "^#" | grep -v "^$"
# 确保包含以下行(或类似):
# watchdog-device = /dev/watchdog
# realtime = yes
# priority = 1
echo 'KERNEL=="watchdog", MODE="0600", OWNER="postgres"' | sudo tee /etc/udev/rules.d/99-watchdog.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --subsystem-match=watchdog
sudo chown -R postgres:postgres /dev/watchdog
使用 Patroni 安装 PostgreSQL 17
在每个节点部署Patroni以管理PostgreSQL实例。
安装 Patroni
sudo yum install -y python3 python3-pip
sudo pip3 install patroni
patroni --version
配置 Patroni
每个节点的配置文件 (/etc/patroni.yml) 需单独配置,主要区别在于 name、restapi 的 listen/connect_address 以及 postgresql 的 listen/connect_address。
mdw 节点配置示例
scope: watchdogmycluster # 集群名,所有节点一致
namespace: /db # etcd 命名空间(可选)
name: mdw # 本节点名称(每台机器不同)
restapi:
listen: 10.0.12.9:8008
connect_address: 10.0.12.9:8008
etcd3:
hosts: # etcd 集群地址
- 10.0.12.9:2379
- 10.0.12.4:2379
- 10.0.16.11:2379
bootstrap:
allow_bootstrap: true
dcs:
ttl: 30 # leader 租约时间(秒)
loop_wait: 10 # 主节点状态上报间隔
retry_timeout: 10 # 故障重试超时
maximum_lag_on_failover: 1048576 # 最大允许 lag(1MB)
postgresql:
use_pg_rewind: true
parameters:
wal_level: replica
hot_standby: on
max_wal_senders: 10
wal_keep_size: 128MB
max_replication_slots: 5
password_encryption: md5
pg_hba:
- host replication replicator 0.0.0.0/24 md5
- host replication replicator 10.0.12.4/32 md5
- host replication replicator 10.0.12.9/32 md5
- host replication replicator 10.0.16.11/32 md5
- host postgres postgres 10.0.12.4/32 trust
- host postgres postgres 10.0.12.9/32 trust
- host postgres postgres 10.0.16.11/32 trust
initdb:
- encoding: UTF8
- data-checksums
- "auth-host=md5"
- "auth-local=trust"
postgresql:
listen: 10.0.12.9:5432
connect_address: 10.0.12.9:5432
data_dir: /opt/pgdata17.4
bin_dir: /opt/17.4/bin/
pgpass: /tmp/pgpass
authentication:
replication:
username: replicator
password: rep_pass
superuser:
username: postgres
password: postgres_pass
parameters:
password_encryption: md5
unix_socket_directories: '/tmp'
watchdog:
mode: required
device: /dev/watchdog
safety_margin: 5
tags:
nofailover: false
noloadbalance: false
clonefrom: false
nosync: false
standby 和 standby1 节点的配置与之类似,需相应修改 name 和涉及本机IP的字段。
配置 patroni.service 服务(每台机器)
创建或修改 /etc/systemd/system/patroni.service:
[Unit]
Description=Runners to orchestrate a high-availability PostgreSQL
After=syslog.target network.target
[Service]
Type=simple
User=postgres
Group=postgres
ExecStart=/usr/local/bin/patroni /etc/patroni.yml
KillMode=process
TimeoutSec=30
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
创建PG数据目录并赋权(每台机器)
mkdir -p /opt/pgdata17.4
chown -R postgres:postgres /opt/pgdata17.4
启动 patroni 服务(每台机器)
sudo systemctl daemon-reload
sudo systemctl start patroni
sudo systemctl status patroni
若部署异常,可使用 etcdctl --endpoints=* del "/db/mycluster/" --prefix 删除key后重新初始化。
查看集群状态
patronictl -c /etc/patroni.yml list watchdogmycluster
输出列说明:
- Member: 节点名称(patroni.yml中的
name)
- Host: PostgreSQL监听IP
- Role: Leader(主库)或 Replica(备库)
- State: running(运行中)、streaming(流复制中)等
- TL: WAL时间线编号
- Receive LSN/Lag: 备库接收WAL的位置及延迟
- Replay LSN/Lag: 备库重放WAL的位置及延迟(关键故障转移指标)
通过HAProxy连接测试
在各节点上测试通过VIP连接数据库:
psql -h 192.168.231.140 -p 5000
模拟故障与测试
手动切换Leader(Switchover)
使用 patronictl switchover 命令可将主库角色从当前节点安全地切换到指定的备库,观察TL(时间线)递增。
模拟主库故障(Failover)
直接停止当前Leader节点的patroni服务 (systemctl stop patroni),观察集群如何自动选举新的Leader。原Leader节点恢复服务后,会自动以Replica角色重新加入集群。
核心参数说明
控制Patroni行为的关键参数(位于bootstrap.dcs):
- ttl (30): Leader锁的存活时间。主库须在此时间内续租,否则触发故障转移。
- loop_wait (10): Patroni主循环检查间隔。
- retry_timeout (10): 单次操作(如连接DB、获取锁)超时时间。
- maximum_lag_on_failover (1MB): 故障转移时允许的备库最大复制延迟。
故障恢复时间估算 ≈ ttl + loop_wait + promote_time(通常40-60秒)。
业务连续性测试
在主库创建测试表后,使用脚本模拟业务持续写入(通过HAProxy的VIP)。此时停止主库patroni服务,观察脚本日志。在短暂的自动故障转移期间,可能会有个别连接错误,但转移完成后写入应立即恢复,验证了高可用架构的有效性。这种架构是构建稳健云原生与基础设施服务的数据层基石。