PostgreSQL 高可用集群部署指南
概述
本指南基于 Patroni + etcd 方案,提供完整的 PostgreSQL 高可用集群部署流程。该方案支持自动故障转移、读写分离、负载均衡等功能,确保数据库的高可用性和可扩展性。
架构组件
- PostgreSQL 12.17: 核心数据库
- Patroni: 集群管理和自动故障转移
- etcd: 分布式键值存储,作为集群状态存储
- HAProxy: 负载均衡器和读写分离
- Keepalived: 虚拟 IP 漂移和高可用
本文档详细介绍了基于 Patroni、etcd、HAProxy 和 Keepalived 构建 PostgreSQL 高可用集群的完整部署流程。内容涵盖环境准备、组件安装配置、读写分离、故障转移测试及监控维护。方案支持自动故障切换、负载均衡和虚拟 IP 漂移,旨在为生产环境提供稳定可靠的数据库高可用架构解决方案。
本指南基于 Patroni + etcd 方案,提供完整的 PostgreSQL 高可用集群部署流程。该方案支持自动故障转移、读写分离、负载均衡等功能,确保数据库的高可用性和可扩展性。
本部署使用 3 节点架构:
# 安装 Python 3.6
yum install -y python3 python3-pip
# 设置默认 Python 版本
alternatives --install /usr/bin/python python /usr/bin/python3 60
# 在所有节点执行
mkdir -p /u1/pgsql/data # PostgreSQL 数据目录
mkdir -p /etc/patroni/ # Patroni 配置目录
mkdir -p /tmp/patroni02 # Patroni 日志目录
# 在所有节点执行
yum install etcd -y
节点 1 配置 (/etc/etcd/etcd.conf):
ETCD_DATA_DIR="/var/lib/etcd/codis.etcd"
ETCD_LISTEN_PEER_URLS="http://0.0.0.0:2380"
ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379"
ETCD_NAME="node1"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.95.139:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://192.168.95.139:2379"
ETCD_INITIAL_CLUSTER="node1=http://192.168.95.139:2380,node2=http://192.168.95.140:2380,node3=http://192.168.95.141:2380"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
ETCD_INITIAL_CLUSTER_STATE="new"
节点 2 配置 (/etc/etcd/etcd.conf):
ETCD_DATA_DIR="/var/lib/etcd/codis.etcd"
ETCD_LISTEN_PEER_URLS="http://0.0.0.0:2380"
ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379"
ETCD_NAME="node2"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.95.140:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://192.168.95.140:2379"
ETCD_INITIAL_CLUSTER="node1=http://192.168.95.139:2380,node2=http://192.168.95.140:2380,node3=http://192.168.95.141:2380"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
ETCD_INITIAL_CLUSTER_STATE="new"
节点 3 配置 (/etc/etcd/etcd.conf):
ETCD_DATA_DIR="/var/lib/etcd/codis.etcd"
ETCD_LISTEN_PEER_URLS="http://0.0.0.0:2380"
ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379"
ETCD_NAME="node3"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.95.141:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://192.168.95.141:2379"
ETCD_INITIAL_CLUSTER="node1=http://192.168.95.139:2380,node2=http://192.168.95.140:2380,node3=http://192.168.95.141:2380"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
ETCD_INITIAL_CLUSTER_STATE="new"
# 在所有节点执行
systemctl daemon-reload
systemctl enable etcd
systemctl start etcd
# 验证 etcd 成员列表
etcdctl member list
# 检查集群健康状态
etcdctl --endpoints http://192.168.95.141:2379 cluster-health
docker pull dontpanic57/etcd-viewer
docker run -d -p 8080:8080 dontpanic57/etcd-viewer
下载安装包(注意版本号一致性):
按顺序安装:
# 在所有节点执行,按依赖顺序安装
rpm -ivh postgresql12-libs-12.17-1PGDG.rhel7.x86_64.rpm
rpm -ivh postgresql12-12.17-1PGDG.rhel7.x86_64.rpm
rpm -ivh postgresql12-server-12.17-1PGDG.rhel7.x86_64.rpm
rpm -ivh postgresql12-contrib-12.17-1PGDG.rhel7.x86_64.rpm
解决依赖问题:
# 如遇依赖问题,先在外网下载依赖包
yum install --downloadonly --downloaddir=/pg12_rpm libicu
# 上传至内网服务器后安装
yum install -y libicu-50.2-4.el7_7.x86_64.rpm
# 在所有节点执行
chown postgres:postgres /u1/pgsql
chmod -R 700 /u1/pgsql
# 安装 Python 依赖
pip install psycopg2-binary -i https://mirrors.aliyun.com/pypi/simple/ --trusted-host=mirrors.aliyun.com
pip3 install patroni[etcd] -i https://mirrors.aliyun.com/pypi/simple/ --trusted-host=mirrors.aliyun.com
# 验证安装
which patroni
常见问题解决:
# 如遇 "python setup.py egg_info" 错误
yum remove python-pip
# 到官网重新下载安装 python-pip
# 创建 pip 软链接
ln -sf /usr/local/python3/bin/pip /usr/bin/pip
# 安装缺失组件
yum install python3-pip python3 python2 python2-pip
# 在所有节点执行,切换到 postgres 用户
su - postgres
echo "192.168.95.139:5432:postgres:postgres:postgres123"> ~/.pgpass
echo "192.168.95.140:5432:postgres:postgres:postgres123">> ~/.pgpass
echo "192.168.95.141:5432:postgres:postgres:postgres123">> ~/.pgpass
chmod 600 ~/.pgpass
exit
节点 1 配置 (/etc/patroni/patroni_postgresql.yml):
scope: postgresql12
namespace: /postgresql/
name: node1
# 日志配置
log:
level: INFO
traceback_level: ERROR
dir: /tmp/patroni02
file_num: 10
file_size: 104857600
# REST API 配置
restapi:
listen: 192.168.95.139:8008
connect_address: 192.168.95.139:8008
# etcd 配置
etcd:
hosts:
- 192.168.95.139:2379
- 192.168.95.140:2379
- 192.168.95.141: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:
listen_addresses: "*"
wal_level: logical
hot_standby: "on"
wal_keep_segments: 10
max_wal_senders: 10
max_replication_slots: 10
wal_log_hints: "on"
initdb:
- encoding: UTF8
- locale: C
- lc-ctype: zh_CN.UTF-8
- data-checksums
pg_hba:
- host replication postgres 0.0.0.0/0 md5
- host all all 0.0.0.0/0 md5
# PostgreSQL 配置
postgresql:
listen: 0.0.0.0:5432
connect_address: 192.168.95.139:5432
data_dir: /u1/pgsql/data
bin_dir: /usr/pgsql-12/bin
config_dir: /u1/pgsql/data
pgpass: /var/lib/pgsql/.pgpass
authentication:
replication:
username: postgres
password: postgres123
superuser:
username: postgres
password: postgres123
rewind:
username: postgres
password: postgres123
# 标签配置
tags:
nofailover: false
noloadbalance: false
clonefrom: false
nosync: false
节点 2 配置(修改对应 IP 为 192.168.95.140)和节点 3 配置(修改对应 IP 为 192.168.95.141)同理,只需更改 name、listen、connect_address 和 pgpass 配置。
# 在所有节点执行
chown -R postgres.postgres /etc/patroni/
chmod 600 /etc/patroni/patroni_postgresql.yml
chown -R postgres.postgres /tmp/patroni02
创建服务文件 (/usr/lib/systemd/system/patroni.service):
[Unit]
Description=High availability PostgreSQL Cluster
After=syslog.target network.target
[Service]
Type=simple
User=postgres
Group=postgres
ExecStart=/usr/local/bin/patroni /etc/patroni/patroni_postgresql.yml
KillMode=process
TimeoutSec=30
Restart=no
[Install]
WantedBy=multi-user.target
启动 Patroni 服务:
# 在所有节点执行
systemctl daemon-reload
systemctl enable patroni
systemctl start patroni
# 查看集群状态
patronictl -c /etc/patroni/patroni_postgresql.yml list
# 如遇 Python 依赖错误
yum install python3-psycopg2
成功状态示例:
+ Cluster: postgresql12 (6965741508141313244) ---------+----+-----------+ | Member | Host | Role | State | TL | Lag in MB | +-----------+------------------+---------+---------+----+-----------+ | node1 | 192.168.95.139 | Leader | running | 1 | | | node2 | 192.168.95.140 | Replica | running | 1 | 0.0 | | node3 | 192.168.95.141 | Replica | running | 1 | 0.0 | +-----------+------------------+---------+---------+----+-----------+
# 手动切换 Leader(测试用)
patronictl -c /etc/patroni/patroni_postgresql.yml switchover
# 在所有节点执行
yum install -y haproxy
配置文件 (/etc/haproxy/haproxy.cfg):
global
maxconn 100
log 127.0.0.1 local2
defaults
log global
mode tcp
retries 2
timeout client 30m
timeout connect 4s
timeout server 30m
timeout check 5s
# 统计页面
listen stats
mode http
bind *:7000
stats enable
stats uri /
# 主库读写服务
listen pgsql
mode tcp
bind *:5000
option httpchk http-check expect status 200
default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
server postgresql_192.168.95.139_5432 192.168.95.139:5432 maxconn 100 check port 8008
server postgresql_192.168.95.140_5432 192.168.95.140:5432 maxconn 100 check port 8008
server postgresql_192.168.95.141_5432 192.168.95.141:5432 maxconn 100 check port 8008
# 备库只读服务
listen pgsql_read
mode tcp
bind *:6000
option httpchk GET /replica
http-check expect status 200
default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
server postgresql_192.168.95.139_5432 192.168.95.139:5432 maxconn 100 check port 8008
server postgresql_192.168.95.140_5432 192.168.95.140:5432 maxconn 100 check port 8008
server postgresql_192.168.95.141_5432 192.168.95.141:5432 maxconn 100 check port 8008
systemctl enable haproxy
systemctl start haproxy
# 解决端口绑定问题(SELinux)
setsebool -P haproxy_connect_any=1
# 在 node1 和 node2 上安装
yum install -y keepalived
node1 配置 (/etc/keepalived/keepalived.conf):
global_defs {
router_id LVS_DEVEL
}
vrrp_script check_haproxy {
script "pgrep -x haproxy"
interval 2
weight 10
}
vrrp_instance VI_1 {
state BACKUP
interface ens33
virtual_router_id 210
priority 90
advert_int 1
track_script {
check_haproxy
}
virtual_ipaddress {
192.168.95.142
}
}
node2 配置(将 priority 改为 80,其他相同)
systemctl enable keepalived
systemctl start keepalived
# 验证虚拟 IP 配置
ip addr show dev ens33
根据您的项目 application-dev.yml,当前使用 Oracle 数据库。要切换到 PostgreSQL 高可用集群,需要修改配置:
spring:
datasource:
# HAProxy 代理的读写分离配置
url: jdbc:postgresql://192.168.95.142:5000/postgres
username: postgres
password: postgres123
# 多主机 URL 配置(备选方案)
# 主库连接
# url: jdbc:postgresql://192.168.95.139:5432,192.168.95.140:5432,192.168.95.141:5432/postgres?targetServerType=primary
# 读写分离配置
# url: jdbc:postgresql://192.168.95.139:5432,192.168.95.140:5432,192.168.95.141:5432/postgres?targetServerType=preferSecondary&loadBalanceHosts=true
连接主库(可写节点):
jdbc:postgresql://192.168.95.139:5432,192.168.95.140:5432,192.168.95.141:5432/postgres?targetServerType=primary
优先连接备库:
jdbc:postgresql://192.168.95.139:5432,192.168.95.140:5432,192.168.95.141:5432/postgres?targetServerType=preferSecondary&loadBalanceHosts=true
随机连接任意节点:
jdbc:postgresql://192.168.95.139:5432,192.168.95.140:5432,192.168.95.141:5432/postgres?targetServerType=any&loadBalanceHosts=true
读写分离访问:
192.168.95.142:5000192.168.95.142:6000http://192.168.95.142:7000在备库节点添加以下配置:
tags:
replicatefrom: node2 # 指定从 node2 复制数据
创建灾备集群配置:
bootstrap:
dcs:
standby_cluster:
host: 192.168.234.210
port: 5432
primary_slot_name: slot1
create_replica_methods:
- basebackup
在主集群配置中添加复制槽:
bootstrap:
dcs:
slots:
slot1:
type: physical
# 查看集群详细信息
patronictl -c /etc/patroni/patroni_postgresql.yml list
# 查看成员详情
patronictl -c /etc/patroni/patroni_postgresql.yml query
# 查看配置
patronictl -c /etc/patroni/patroni_postgresql.yml show
# 连接数据库查看复制状态
psql -h 192.168.95.139 -U postgres -c "SELECT * FROM pg_stat_replication;"
# Patroni 日志
tail -f /tmp/patroni02/patroni.log
# PostgreSQL 日志
tail -f /u1/pgsql/data/log/postgresql-*.log
# etcd 日志
journalctl -u etcd -f
在 Patroni 配置的 parameters 部分添加优化参数:
postgresql:
parameters:
# 内存优化
shared_buffers: 256MB
effective_cache_size: 1GB
work_mem: 4MB
maintenance_work_mem: 64MB
# 连接优化
max_connections: 100
# WAL 配置
wal_buffers: 16MB
checkpoint_completion_target: 0.9
# 查询优化
random_page_cost: 1.1
effective_io_concurrency: 200
问题: .pgpass 文件路径错误
# 检查配置文件中的路径设置
pgpass: /var/lib/pgsql/.pgpass
# 正确路径
# 清理数据目录重新初始化
rm -rf /u1/pgsql/data/*
systemctl restart patroni
问题: 端口绑定失败
# 检查 SELinux 设置
setsebool -P haproxy_connect_any=1
# 或关闭 SELinux
setenforce 0
# 检查网络接口配置
ip addr show dev ens33
# 检查防火墙设置
firewall-cmd --add-rich-rule='rule family="ipv4" destination address="192.168.95.142" accept'
手动故障转移(如需要):
patronictl -c /etc/patroni/patroni_postgresql.yml switchover
确认故障: 检查集群状态
patronictl -c /etc/patroni/patroni_postgresql.yml list
重启 Patroni:
systemctl restart patroni
# 从备份恢复数据
sudo -u postgres pg_ctl -D /u1/pgsql/data stop
rm -rf /u1/pgsql/data/*
# 恢复备份文件
systemctl start patroni
创建备份脚本 (/usr/local/bin/pg_backup.sh):
#!/bin/bash
# PostgreSQL 高可用集群备份脚本
# 备份配置
BACKUP_DIR="/backup/pg"
DATE=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=7
# 创建备份目录
mkdir -p $BACKUP_DIR
# 连接到主库进行备份
psql -h 192.168.95.139 -U postgres -c "SELECT pg_start_backup('backup_$DATE');"
# 备份数据文件
tar -czf $BACKUP_DIR/pg_data_$DATE.tar.gz -C /u1/pgsql/data .
# 结束备份
psql -h 192.168.95.139 -U postgres -c "SELECT pg_stop_backup();"
# 清理旧备份
find $BACKUP_DIR -name "pg_data_*.tar.gz" -mtime +$RETENTION_DAYS -delete
echo "Backup completed: pg_data_$DATE.tar.gz"
设置定时任务:
chmod +x /usr/local/bin/pg_backup.sh
echo "0 2 * * * /usr/local/bin/pg_backup.sh" | crontab -
# 停止 Patroni
systemctl stop patroni
# 备份当前数据
mv /u1/pgsql/data /u1/pgsql/data.backup
# 恢复数据
mkdir -p /u1/pgsql/data
tar -xzf /backup/pg/pg_data_YYYYMMDD_HHMMSS.tar.gz -C /u1/pgsql/data
# 设置权限
chown -R postgres:postgres /u1/pgsql/data
chmod 700 /u1/pgsql/data
# 启动 Patroni
systemctl start patroni
本部署指南提供了完整的 PostgreSQL 高可用集群部署方案,包含以下核心功能:
通过以上配置,您可以获得一个稳定、高可用的 PostgreSQL 集群环境。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
在线格式化和美化您的 SQL 查询(它支持各种 SQL 方言)。 在线工具,SQL 美化和格式化在线工具,online
解析 INSERT 等受限 SQL,导出为 CSV、JSON、XML、YAML、HTML 表格(见页内语法说明)。 在线工具,SQL 转 CSV/JSON/XML在线工具,online
CSV 与 JSON/XML/HTML/TSV/SQL 等互转,单页多 Tab。 在线工具,CSV 工具包在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online