找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1531

积分

0

好友

225

主题
发表于 12 小时前 | 查看: 2| 回复: 0

在单机部署的 PostgreSQL 架构中,一旦遭遇服务器硬件故障、操作系统崩溃或网络中断,服务便会中断,业务连续性无法保障。这种架构缺乏自动故障转移(Failover)的高可用机制,依赖人工干预恢复,操作复杂且恢复时间目标(RTO)较长,难以满足现代业务对高可靠性、可扩展性及容灾能力的要求。

为确保数据库服务的持续可用,构建健壮的 PostgreSQL 高可用(HA)架构至关重要。通过整合 PatroniHAProxyKeepalivedetcd 等组件,可以实现自动故障切换、读写分离与负载均衡,从而达成高可用目标。

组件介绍

Patroni

Patroni 是一个基于 Python 开发的 PostgreSQL 高可用性解决方案。它支持多种分布式配置存储后端,如 ZooKeeper、etcd、Consul 或 Kubernetes,并兼容 PostgreSQL 9.3 及以上版本。Patroni 负责自动化管理 PostgreSQL 实例的启动、停止、主从切换与配置更新。

HAProxy

HAProxy 是一款高性能的开源负载均衡器和反向代理软件,支持 TCP 和 HTTP 应用。在本架构中,HAProxy 负责将客户端的读写请求智能地分发到 PostgreSQL 集群中正确的主库或从库节点,提升应用性能与资源利用率。

Keepalived

Keepalived 基于 VRRP(虚拟路由器冗余协议)协议,用于实现 IP 漂移,为 HAProxy 层提供高可用性。它通过健康检查机制管理负载均衡服务器池,确保虚拟 IP(VIP)始终指向可用的 HAProxy 节点,实现客户端无感知的故障转移。

ETCD

ETCD 是一个强一致性的分布式键值存储系统,常作为 云原生 生态的核心组件。在本架构中,它用于存储 PostgreSQL 高可用集群的配置与状态数据(如 Leader 锁)。Patroni 利用 etcd 来协调集群状态与领导者选举,确保配置信息在集群所有节点间保持一致。

watchdog

watchdog(看门狗)是一个内核模块或硬件,用于监控 PostgreSQL 节点及其相关进程的健康状况。当 Patroni 因进程假死(如脑裂场景)而无法正常运行时,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
  • 8008:用于 Patroni 的 HTTP API。
    sudo firewall-cmd --zone=public --add-port=8008/tcp --permanent
    sudo firewall-cmd --reload
    sudo firewall-cmd --query-port=8008/tcp
HAProxy
  • 5000:用于后端 HTTP 连接。
  • 5001:用于后端 HTTPS 连接。
    sudo firewall-cmd --zone=public --add-port=5000/tcp --permanent
    sudo firewall-cmd --zone=public --add-port=5001/tcp --permanent
    sudo firewall-cmd --reload
etcd
  • 2379:用于 etcd 客户端通信。
  • 2380:用于 etcd 节点间通信。
    sudo firewall-cmd --zone=public --add-port=2379/tcp --permanent
    sudo firewall-cmd --zone=public --add-port=2380/tcp --permanent
    sudo firewall-cmd --reload
Keepalived
  • 112:用于 VRRP 通信。
  • 5405:用于多播流量。
    sudo firewall-cmd --zone=public --add-port=112/tcp --permanent
    sudo firewall-cmd --zone=public --add-port=5405/tcp --permanent
    sudo firewall-cmd --reload
Pgbouncer
  • 6432:用于 Pgbouncer 客户端连接。
    sudo firewall-cmd --zone=public --add-port=6432/tcp --permanent
    sudo firewall-cmd --reload
PostgreSQL
  • 5432:用于 PostgreSQL 客户端连接。
    sudo firewall-cmd --zone=public --add-port=5432/tcp --permanent
    sudo firewall-cmd --reload
Web server
  • 5000, 5001, 7000:用于 Web 服务及反向代理。
    sudo firewall-cmd --zone=public --add-port=5000/tcp --permanent
    sudo firewall-cmd --zone=public --add-port=5001/tcp --permanent
    sudo firewall-cmd --zone=public --add-port=7000/tcp --permanent
    sudo firewall-cmd --reload

安装 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

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 节点

安装 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/

# 验证版本
etcdutl version
etcdctl version
etcd --version
配置 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 节点创建配置文件 /etc/etcd/etcd.conf.yml

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 节点创建配置文件 /etc/etcd/etcd.conf.yml

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,用于创建和管理虚拟 IP(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)

备份原配置后,修改 /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)

备份原配置后,修改 /etc/keepalived/keepalived.conf(两节点配置相同,除了 state 可均为 BACKUP):

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

检查 VIP 是否生效

在各节点上执行 ip addr 命令,检查是否出现 192.168.231.140 的 IP 地址。

配置 HAProxy

安装 HAProxy

在所有节点上安装 HAProxy。

yum install -y haproxy

配置HAProxy

在 mdw 节点上配置 /etc/haproxy/haproxy.cfg,然后分发到其他节点。

# /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 -r /etc/haproxy/haproxy.cfg standby:/etc/haproxy/haproxy.cfg
scp -r /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

启动 HAProxy 服务

sudo systemctl daemon-reload
systemctl start haproxy
systemctl enable haproxy
systemctl status haproxy

启用 watchdog

在所有节点上操作,用于防止 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

# 设置 watchdog 设备权限
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
# 或使用国内镜像:sudo pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple patroni
patroni --version

配置 Patroni

每个节点的配置需调整 namelistenconnect_address 等字段。

mdw 节点配置 /etc/patroni.yml
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/pgsql/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 节点配置

配置文件与 mdw 节点类似,主要修改 namerestapi.listenrestapi.connect_addresspostgresql.listenpostgresql.connect_address10.0.12.4

standby1 节点配置

配置文件与 mdw 节点类似,主要修改 namerestapi.listenrestapi.connect_addresspostgresql.listenpostgresql.connect_address10.0.16.11

配置 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
创建 PostgreSQL 数据目录并赋权
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 后重新启动 Patroni。

查看 patroni 集群状态
patronictl -c /etc/patroni.yml list watchdogmycluster

输出列说明:

  • Member: 节点名称(对应配置中的 name)。
  • Host: PostgreSQL 监听 IP。
  • Role: 角色(Leader 主库,Replica 备库)。
  • State: PostgreSQL 状态(running, streaming 等)。
  • TL: Timeline(时间线编号,主库提升时增加)。
  • Receive LSN: 备库接收到的最新 WAL 位置。
  • Lag: 接收延迟(字节)。
  • Replay LSN: 备库已重放(应用)的 WAL 位置。
  • Lag: 重放延迟(字节),是判断能否安全故障转移的关键。
通过 HAProxy 连接 PostgreSQL

在各节点上检查 VIP 和 HAProxy 端口,并使用 psql 测试连接:

# 检查VIP
ip addr | grep 140
# 检查端口
netstat -nltp | grep 500

# 测试连接(写端口)
psql -h 192.168.231.140 -p 5000 -U postgres
# 测试连接(读端口)
psql -h 192.168.231.140 -p 5001 -U postgres

模拟故障测试

1. 手动切换 Leader 节点

使用 patronictl switchover 命令可以安全地将主库角色从当前节点切换到指定的备库节点,观察整个提升(Promote)过程。

patronictl -c /etc/patroni.yml switchover

根据交互提示选择候选节点和切换时间。切换完成后,再次使用 list 命令查看集群状态,确认时间线(TL)已增加,角色已变更。

2. 模拟主库故障

在主库节点上直接停止 Patroni 服务:

sudo systemctl stop patroni

等待一段时间(超过 loop_wait + ttl),观察集群是否自动将主库角色切换到另一个健康的备库节点。原主库节点在重新启动 Patroni 服务后,会自动以从库(Replica)身份重新加入集群。

3. Patroni 核心控制参数

以下参数在 /etc/patroni.ymldcs 部分配置,控制着故障切换与心跳行为:

参数 默认值 作用 说明
ttl 30 Leader 锁的 TTL(秒) 主节点必须在此时间内更新 etcd 中的 leader key,否则锁过期触发故障转移。
loop_wait 10 Patroni 主循环间隔(秒) 每隔此时间检查集群状态、更新锁、喂狗等。
retry_timeout 10 单次操作超时(秒) 如连接 PostgreSQL、写 WAL、获取锁等操作的超时时间。
maximum_lag_on_failover 1MB 复制延迟限制 故障转移时,备库最大允许的复制延迟(WAL 字节数)。
master_start_timeout 300 主库启动超时(秒) 新主节点启动并接受连接的最大等待时间。

故障时间线示例:

  • T=0s: 主库正常,更新锁(TTL=30s)。
  • T=25s: 主库宕机(无法更新锁)。
  • T=30s: 锁过期(TTL 到期)。
  • T=30~40s: 备库检测到无 Leader,发起故障转移(failover)。
  • T=40s: 新主库完成 promote,更新锁。
  • 总故障恢复时间ttl + loop_wait + promote_time(通常 40~60 秒)。

4. 测试业务持续写入

在主库上创建测试表:

create table t(id int);

编写一个简单的循环写入脚本:

while true; do
  psql -d postgres -Upostgres -h192.168.231.140 -p5000 -c "select inet_server_addr(),now()::timestamp;" -c "insert into t values((random()*10))" -t;
  sleep 1;
done

在脚本运行期间,手动停止当前主库节点的 Patroni 服务。观察脚本输出是否短暂中断后恢复,并且插入操作持续成功,验证 HAProxy 作为 数据库/中间件 层的高可用代理,与 Patroni 配合实现了业务的透明故障切换。




上一篇:Tauri跨平台桌面应用开发:基于Rust与Web技术的轻量、快速与Electron方案对比
下一篇:三星Exynos 2600芯片曝光:全球首发2nm工艺,Galaxy Z Flip 8与S26系列有望搭载
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-24 17:10 , Processed in 0.200583 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表