1. 先说结论:这套架构到底解决什么问题

看到这个标题,很多刚接触PostgreSQL的同学第一反应是:数据库不是装好就能用吗?为什么要搞主从复制,还要再套一个keepalived?我2018年第一次在生产环境做PG高可用时也是这个想法,直到某天凌晨两点主库磁盘满掉、业务直接停摆,才真正理解“高可用”这三个字的分量。

PostgreSQL本身是一个单机数据库,它没有内置的自动故障转移能力。你可以用自带的流复制搭出一主一备,但备库不会主动变成主库,应用也不能自动把连接切过去。这套方案要解决的,就是两个核心问题:

  • 数据冗余 :主库的数据实时同步到备库,主库出故障时数据不丢或尽量少丢。
  • 自动切换 :主库故障后,备库自动提升为新主库,业务通过VIP(虚拟IP)无感知地切换到新主库上继续跑。

这套方案的主角是三个:PostgreSQL负责数据存储和流复制,repmgr负责主备关系管理、监控和故障时的角色提升,keepalived负责VIP漂移。repmgr管“谁能当主”,keepalived管“流量怎么过去”,两者各管一层,配合起来就是一个完整的数据库高可用闭环。

我写的这篇东西,适合三类人:一是刚接手PG数据库运维、想搭一套靠谱高可用架构的DBA;二是公司没有专职DBA、需要自己动手的运维或后端开发;三是为了应付技术面试、想把这个经典组合讲明白的求职者。我会按从零开始的顺序,把安装、配置、故障演练、踩坑全部走一遍,所有命令都是我在CentOS 7.9和Rocky Linux 8上实测过可用的。

提示:本文所有命令和配置均在RHEL系发行版上验证,Debian/Ubuntu系的路径和包管理器略有差异,但思路完全一致,照着调整即可。

2. 方案选型:为什么是repmgr + keepalived,而不是Patroni或Pacemaker

2.1 三个方案的横向对比

在动手安装之前,先花点时间聊方案选型。PostgreSQL的高可用方案现在主流有三类: repmgr + keepalived 、 Patroni + etcd/consul 、 Pacemaker + corosync (通常配DRBD或共享存储)。我见过太多人上来就装,装到中途发现架构不匹配,推倒重来,所以在第一步就把优劣说清楚。

特性 repmgr + keepalived Patroni + etcd/consul Pacemaker + corosync
架构复杂度 低,两个组件 较高,需额外维护一致性组件 高,集群资源管理复杂
对脑裂的处理 依赖repmgr判断+脚本兜底 通过etcd/consul分布式锁 有Fencing机制,更严格
学习成本 中等 较高 高
适用规模 中小型业务,2~3节点 中大型业务,多节点动态选主 传统企业,与存储强绑定
自动故障转移 支持,repmgrd守护进程 支持,Raft共识 支持,资源约束

2.2 我这套方案的优势和边界

repmgr + keepalived最核心的优势是 轻 。它不需要额外引入一套分布式协调组件,也没有那么多资源约束概念,一台主库、一台备库、一个VIP,三个组件就能转起来。对于用户量几万、QPS几千的业务,这个架构完全够用,而且排障链路短:数据库层的问题看repmgr日志,网络层的问题看keepalived日志,心智负担很小。

那它的边界在哪里?最大的风险是 脑裂 ,也就是主备都认为自己是主、都尝试对外提供服务。keepalived的VRRP协议只能在网络层面保证同一时刻只有一个节点持有VIP,但如果网络出现瞬断,两个节点可能同时进入MASTER状态。repmgr这边,repmgrd在检测到主库失联后会尝试将备库提升为primary,但网络抖动也可能触发误切换。

应对这个风险,业界通用的做法是 加仲裁节点 。最少用三台机器,仲裁节点不跑数据库,只参与keepalived的投票和repmgr的见证。条件不允许的话,至少要在repmgr配置里关闭自动切换、改为半自动(failover=manual),或者把repmgrd的检测时间和重试次数调大,用时间维度过滤瞬断。

2.3 什么时候不要选这套方案

我必须说清楚,如果你的业务对数据一致性要求极其苛刻——比如金融交易、订单流水,RPO接近零,那这套架构不达标。repmgr的默认流复制是异步的,主库崩溃瞬间未同步的事务可能丢失。这时候你该考虑 同步复制 (synchronous_commit = on + synchronous_standby_names),或者直接上Patroni配同步节点、甚至上共享存储+Pacemaker。另外如果节点超过三个、需要动态扩缩容,Patroni的Raft选主机制明显更顺手。

我实际的经验是: 3节点以下、没有专职DBA、想快速交付一套能用的高可用环境,repmgr + keepalived是最优解;节点多、要求强一致、有专门基础架构团队,直接Patroni别犹豫。 剩下的内容都围绕前者展开。

3. 环境准备:三件套缺一不可

3.1 主机规划与版本确认

我先给出一套我在生产环境验证过的标准规划。两台数据库节点加一台可选的监控(见证)节点,CPU内存按业务量来,至少2C4G起步。操作系统用的是Rocky Linux 8.8(CentOS 7.9同样适用,只是PG编译依赖会老一点)。

主机名 IP 角色 说明
pg-primary 192.168.10.11 主库节点 初始主库,跑PG + repmgr + keepalived
pg-standby 192.168.10.12 备库节点 初始备库,跑PG + repmgr + keepalived
pg-witness 192.168.10.13 见证节点 可选,跑repmgr witness + keepalived仲裁
VIP 192.168.10.100 虚拟IP 应用连接入口,正常时绑定在主库节点

这里有个很多人踩过的坑: VIP网段必须和业务网卡在同一个二层网络 ,不然VRRP广播报文传不过去,keepalived漂移根本不会发生。如果你跨机房做多活,需要单独规划三层路由,那是另一套方案了,本文不展开。

PostgreSQL版本方面,我做这套环境时用的16.3。选16的原因有三个:逻辑复制在16上重大改进、流复制协议稳定、repmgr官方对16的支持已经非常成熟。如果你有历史包袱在跑11或12,repmgr 5.x也有对应支持,别盲目升大版本,PG的大版本升级不是小事。

3.2 系统基础配置:10分钟搞定

主机名、hosts解析、免密SSH、时间同步、防火墙,这五项是集群的“地基”,任何一个出问题都会让你在后面排查到怀疑人生。

第一,设置主机名并写入hosts。keepalived和repmgr都是靠主机名通信的,主机名不一致直接报错。

hostnamectl set-hostname pg-primary
cat >> /etc/hosts <<EOF
192.168.10.11 pg-primary
192.168.10.12 pg-standby
192.168.10.13 pg-witness
EOF

第二,配置双节点免密SSH登录。repmgr的standby clone操作要通过SSH在备库上执行,密码交互会导致脚本卡死。

ssh-keygen -t rsa -b 4096 -N "" -f ~/.ssh/id_rsa
ssh-copy-id pg-primary
ssh-copy-id pg-standby

第三,时间同步。流复制对时间偏差没那么敏感,但事务时间戳和日志排查需要一致的时间线,用chrony同步一下更稳妥。

dnf install -y chrony
systemctl enable --now chronyd
chronyc sources -v

第四,关闭SELinux,放行(或关闭)防火墙。生产环境安全要求高的话,只放行必要的端口:PostgreSQL的5432,repmgr的SSH通信端口,keepalived的VRRP协议(通常是IP协议112号以及Windows下组播地址224.0.0.18)。我为了演示方便直接关掉了防火墙,但你要知道哪些端口是必需的。

setenforce 0
sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config
systemctl stop firewalld
systemctl disable firewalld

第五,确认两台节点的locale和时区一致,避免复制时文本编码问题。

timedatectl set-timezone Asia/Shanghai
localectl set-locale LANG=en_US.UTF-8

3.3 PostgreSQL安装:编译还是二进制包

这一步我纠结过很久。二进制包安装快、省心,但自定义编译参数受限;源码编译灵活、可控,但费时且依赖多。如果你用的是CentOS 7.9,PGDG官方源里就有编译好的RPM包,直接装;如果你用的是Rocky 8/9或者想用PG 16的最新小版本,RPM包也覆盖到了。我的建议是: 能用RPM包就别编译 ,生产环境稳定优先,别在安装上折腾。

dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x86_64/pgdg-redhat-repo-latest.noarch.rpm
dnf -y install postgresql16-server postgresql16

但要注意,PGDG源在安装后不会自动初始化数据目录,需要手动执行:

/usr/pgsql-16/bin/postgresql-16-setup initdb
systemctl enable --now postgresql-16

初始化完成的第一步就是修改postgres用户密码和设置默认连接:

su - postgres
psql -c "ALTER USER postgres WITH PASSWORD 'YourStrongPass';"
exit

到这里,单机PG已经跑起来了。但注意,这只是起点。接下来要做的所有配置,都是为了让它具备“被管理”的条件。

4. PostgreSQL基础配置:高可用的地基

4.1 必须改的postgresql.conf参数

repmgr的主从复制基于PostgreSQL的WAL日志流复制。要让复制正常工作,主库必须开启wal_level、配置足够的max_wal_senders和wal_keep_size,备库要配置hot_standby。这些参数在PG 16里默认值比老版本友好,但高可用场景下仍要显式确认。

su - postgres
vi /var/lib/pgsql/16/data/postgresql.conf

我贴上生产环境经过验证的核心配置:

listen_addresses = '*'
port = 5432
max_connections = 300
wal_level = replica
max_wal_senders = 10
max_replication_slots = 10
wal_keep_size = 1GB
hot_standby = on
synchronous_commit = local

这里重点解释两个容易被忽视的参数:

  • max_wal_senders :备库连接主库拉取WAL时,每个备库至少占用一个WAL sender进程。如果只搭一主一备,设10个已经富余,但如果你后续加节点或者做逻辑复制,这个值就决定上限。
  • wal_keep_size :主库保留多少WAL日志给落后的备库追上。如果备库断开太久,超过这个值还没追上的话,备库只能重新全量clone,这在生产中属于比较严重的故障。条件允许还可以额外配置 archive_mode = on 和 archive_command ,把WAL归档到独立存储,进一步增强容错。

4.2 pg_hba.conf:复制用户的授权

流复制需要一个专门的复制账号,一般叫 repl 。PostgreSQL文档推荐单独建一个最小权限用户,只用 REPLICATION 权限,不要用超级用户。

CREATE ROLE repl WITH REPLICATION LOGIN PASSWORD 'ReplPass123';

然后编辑pg_hba.conf,加入下面两行。注意顺序,pg_hba.conf是按顺序匹配的,复制账号的规则要放在数据库普通用户规则之前。

host    replication     repl            192.168.10.11/32        md5
host    replication     repl            192.168.10.12/32        md5
host    all             all             192.168.10.0/24         md5

配置完成后重启PG使参数生效:

systemctl restart postgresql-16

验证复制连接是否正常,可以在主库本地执行:

/usr/pgsql-16/bin/pg_isready -h 192.168.10.11 -p 5432
psql -h 192.168.10.11 -U repl -d postgres -c "SELECT 1;"

能查到结果说明复制账号和监听都没问题。这个动作做完,PostgreSQL层面就具备了被repmgr接管的基础条件。

注意:PostgreSQL的 listen_addresses 如果没设置成 * 或明确网段,备库通过TCP拉取WAL会一直卡在“连接超时”。很多新手搭不起来主从,第一嫌疑就是这个参数。

5. repmgr主从复制搭建全流程

5.1 repmgr的安装与版本匹配

repmgr的版本必须和PostgreSQL大版本匹配。PG 16对应repmgr 5.4+,PG 15用repmgr 5.3+。如果你装了PG 16却用repmgr 5.2的老包,启动的时候会直接报 repmgr: fatal: unsupported PostgreSQL major version ,这个错太典型了,我踩过一次。

PGDG源里直接有repmgr的RPM包,安装即可:

dnf install -y repmgr16

验证版本:

repmgr --version
# repmgr version 5.4.1

5.2 repmgr.conf:一张一张参数说清楚

repmgr的配置文件路径我放在 /etc/repmgr/16/repmgr.conf 。两个数据库节点的配置文件几乎一样,唯一不同的是 node_id 和 node_name 。我贴出主库的完整配置:

node_id=1
node_name='pg-primary'
conninfo='host=pg-primary port=5432 user=repl dbname=postgres connect_timeout=5'
data_directory='/var/lib/pgsql/16/data'
failover=automatic
promote_command='/usr/pgsql-16/bin/repmgr standby promote -f /etc/repmgr/16/repmgr.conf --log-level DEBUG'
follow_command='/usr/pgsql-16/bin/repmgr standby follow -f /etc/repmgr/16/repmgr.conf --log-level DEBUG'
monitoring_history=yes
log_level=DEBUG
log_file='/var/log/repmgr/repmgr.log'
ssh_options='-o ConnectTimeout=5 -o StrictHostKeyChecking=no'

逐个说下重点参数:

  • conninfo :这里的主机名必须是两台机器都能解析的名字。我直接用了hosts里的 pg-primary ,别用IP,因为repmgr的节点身份是靠node_name和conninfo里的host对应的,IP切换时容易出幻觉问题。
  • failover=automatic :开启自动切换。如果不放心,可以先设成 manual ,观察repmgr的判定逻辑后再打开。我第一次搭生产环境就吃了自动切换的亏,网络抖动导致误切换,从那以后我强烈建议先手动演练几轮再开automatic。
  • promote_command 和 follow_command :这是repmgr在切换时执行的系统命令,负责把备库提升为新主库、或者把原主库重新作为备库跟随新主库。命令里 -f 指定配置文件,别省。
  • log_level=DEBUG :调试阶段建议开DEBUG,以后稳定了可以改成INFO,否则日志刷得太快容易掩盖关键信息。

备库的配置文件只改两处: node_id=2 、 node_name='pg-standby' ,其余完全一致。两个节点都执行:

mkdir -p /var/log/repmgr
chown -R postgres:postgres /var/log/repmgr

5.3 主库注册:repmgr primary register

在 主库 上,以postgres用户执行:

su - postgres
/usr/pgsql-16/bin/repmgr -f /etc/repmgr/16/repmgr.conf primary register

这一步做了什么?它在repmgr的元数据表里登记主库信息,创建repmgr相关的schema和表。执行成功后查询确认:

/usr/pgsql-16/bin/repmgr -f /etc/repmgr/16/repmgr.conf cluster show

输出里应该有:

 ID | Name       | Role    | Status    | Upstream | Location | Priority | Timeline | Connection string
----+------------+---------+-----------+----------+----------+----------+----------+-----------------
 1  | pg-primary | primary | * running | -        | default  | 100      | 1        | host=pg-primary ...

注意Status那一列,必须是 * running ,星号表示这个节点是当前的主节点。如果显示 ? unreachable ,大概率是conninfo或者SSH问题,先回头检查hosts解析和免密。

5.4 备库clone:从头搭建流复制

备库上PostgreSQL已经装好,但数据目录是空的(或者还是initdb初始化的原始状态)。repmgr提供了一个命令,直接通过SSH从主库拉取全量数据并自动搭建流复制:

su - postgres
/usr/pgsql-16/bin/repmgr -h pg-primary -U repl -d postgres -f /etc/repmgr/16/repmgr.conf standby clone

这个命令本质做了四件事:

  1. 在主库上执行 pg_basebackup ,把主库数据文件完整拷贝到备库。
  2. 在备库生成 standby.signal 文件,告诉PG启动时进入备库模式。
  3. 自动写入主库的连接信息和replication slot。
  4. 更新repmgr元数据,把备库登记进集群。

clone完成后,启动备库的PG服务:

systemctl start postgresql-16

然后注册备库:

/usr/pgsql-16/bin/repmgr -f /etc/repmgr/16/repmgr.conf standby register

到这里,一个基于repmgr管理的主从复制就建好了。验证一下:

/usr/pgsql-16/bin/repmgr -f /etc/repmgr/16/repmgr.conf cluster show

应该看到两个节点,主库 * running ,备库 running as standby 。

再深入验证复制是否在实时同步。在主库建一张测试表并插入数据:

psql -c "CREATE TABLE ha_test (id serial primary key, ts timestamptz default now());"
psql -c "INSERT INTO ha_test (ts) VALUES (now());"

在备库查询:

psql -c "SELECT * FROM ha_test;"

能查到刚插入的记录,说明流复制链路完全打通。如果查不到,别急着往下走,看主库的 pg_stat_replication 视图:

SELECT client_addr, state, sync_state, replay_lag FROM pg_stat_replication;

正常情况下 state 是 streaming , replay_lag 接近0或为空。如果是 catchup 状态,说明备库还在追WAL,等一会儿再查。

5.5 开启repmgrd守护进程

repmgr装好只是“管理工具”,真正实现自动切换得靠后台守护进程repmgrd。它在两个节点上都要启动,一个负责监控主库心跳,一个负责监控备库复制状态:

systemctl enable --now repmgrd-16
systemctl status repmgrd-16

repmgrd启动后,每间隔 reconnect_attempts 和 reconnect_interval (默认在配置文件里,可以自行调整)都会检查上游节点状态。我用过一个比较稳妥的参数组合:

reconnect_attempts=6
reconnect_interval=10

意思是在60秒内连续6次连接失败,才判定主库失联。这样普通的网络抖动不会触发切换,而真正的宕机又能在一分钟内被识别。

提示:repmgrd的日志和repmgr的命令日志是两个概念。前者在 /var/log/repmgr/repmgrd.log ,后者在 /var/log/repmgr/repmgr.log 。排障时看错文件会浪费大量时间,我经历过一次。

6. keepalived故障转移配置

6.1 keepalived是干什么的

PostgreSQL层的主备切换做完了,但如果应用的数据库连接地址还是指向原来的物理IP,一切等于白搭。keepalived解决的就是这个问题:它对外提供一个虚拟IP(VIP),VIP平时绑定在主库节点,当主库故障、备库提升后,VIP自动漂移到备库节点,应用连接的IP不用变,就能无缝切换到新主库上。

keepalived的核心是VRRP协议。简单理解,组内的节点周期性互相发心跳报文,谁的优先级高谁就是MASTER、持有VIP;MASTER挂了,BACKUP节点中优先级最高的顶上。这里的关键点来了: keepalived只知道节点死没死,不知道是数据库死还是机器死。 所以必须写脚本去探测数据库和repmgr的状态,脚本探测异常时主动降低本节点优先级,把VIP让给健康节点。

6.2 安装keepalived

dnf install -y keepalived
systemctl enable keepalived

6.3 主库的keepalived配置

配置文件在 /etc/keepalived/keepalived.conf 。我直接贴出经过生产验证的完整版:

global_defs {
    router_id PG_HA_01
    enable_script_security
}

vrrp_script check_pg_alive {
    script "/etc/keepalived/check_pg_alive.sh"
    interval 3
    timeout 3
    fall 2
    rise 2
}

vrrp_instance VI_DB {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 150
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 8aK9#zFq
    }

    virtual_ipaddress {
        192.168.10.100/24 dev eth0 label eth0:1
    }

    track_script {
        check_pg_alive
    }
}

参数逐个说明:

  • state MASTER :主库配置成MASTER。注意它只在启动时决定初始状态,后续靠优先级动态调整。
  • priority 150 :主库的优先级。备库我会配100。优先级高的在VRRP竞选里胜出。
  • virtual_router_id 51 :同一个集群内的keepalived节点这个值必须一致,否则分不到一个组里。
  • auth_pass :VRRP认证密码,同一组必须一致,长度不超过8位。
  • advert_int 1 :VRRP广播间隔,单位秒。间隔越小切换越快,但过于频繁会增大网络负担,1秒是通用值。
  • fall 2 和 rise 2 :脚本连续失败2次才认为故障,连续成功2次才恢复。这个参数专门防抖动。

备库的配置只有两处不同: state BACKUP 、 priority 100 ,其他一模一样。

6.4 VRRP脚本怎么写

这是整个方案里最需要花心思的地方。脚本的目标是: 数据库活着、repmgr确认本节点是主,本节点才有资格持有VIP。 我写了一个保守但可靠的版本:

#!/bin/bash
# /etc/keepalived/check_pg_alive.sh

PG_USER="postgres"

# 1. 检查postgres进程是否存活
pgrep -f "postgres.*-D /var/lib/pgsql/16/data" > /dev/null 2>&1
if [ $? -ne 0 ]; then
    exit 1
fi

# 2. 用psql探测数据库是否真正可连接
psql -U "$PG_USER" -h 127.0.0.1 -p 5432 -c "SELECT 1;" > /dev/null 2>&1
if [ $? -ne 0 ]; then
    exit 1
fi

# 3. 如果repmgr可用,确认本节点是否为主节点
if [ -x /usr/pgsql-16/bin/repmgr ]; then
    NODE_TYPE=$(/usr/pgsql-16/bin/repmgr -f /etc/repmgr/16/repmgr.conf node check 2>/dev/null)
    echo "$NODE_TYPE" | grep -q "primary"
    if [ $? -ne 0 ]; then
        exit 1
    fi
fi

# 4. 检查主从握手是否正常(可选,防止主库客户端连不上备库)
psql -U "$PG_USER" -h 127.0.0.1 -p 5432 -c "SELECT COUNT(*) FROM pg_stat_replication;" > /dev/null 2>&1

exit 0

脚本的第三个判断是这个方案的精髓。当repmgr完成角色切换后,原主库的repmgr会把它标记为 standby ,脚本就会让本节点的优先级自动失效,VIP立刻从残废的原主库漂移到健康的新主库上。这样就把“数据库层切换”和“网络层切换”绑在了一起。

脚本给执行权限,并保证postgres用户能通过localhost免密连接(在pg_hba.conf中配置 host all postgres 127.0.0.1/32 trust ,或者用 .pgpass 文件):

chmod +x /etc/keepalived/check_pg_alive.sh
chown root:root /etc/keepalived/check_pg_alive.sh

6.5 验证VIP绑定

启动keepalived后,在主库上检查:

systemctl restart keepalived
ip addr show eth0 | grep 192.168.10.100

看到VIP绑定在主库的eth0上: inet 192.168.10.100/24 scope global eth0:1 。在备库上,同样的命令应该没有任何输出。至此,整套架构已经搭建完毕:流复制在跑,repmgr在监控,keepalived在守门。接下来就是最激动人心也最吓人的环节——故障演练。

7. 故障演练:主库挂了到底会发生什么

7.1 模拟主库数据库进程崩溃

先演练最常见的情况:主库的postgres进程突然崩溃(相当于进程被OOM Killer干掉),机器本身还活着。

在主库执行:

pkill -9 postgres

等30秒左右,在备库和witness节点观察变化。预期发生的事件链是这样的:

  1. repmgrd探测到主库连接失败 。连接重试次数达到阈值后,它认为主库失联。
  2. repmgrd提升备库 。它执行 standby promote ,备库从只读模式切换为读写模式。
  3. keepalived脚本探测失败 。主库的postgres进程已经没了, check_pg_alive.sh 返回非零,主库keepalived的优先级被强制降低。
  4. VRRP切换 。备库的keepalived发现主库节点不再广播或优先级低于自己,抢到VIP。

验证是否切换成功:

# 在备库检查角色
/usr/pgsql-16/bin/repmgr -f /etc/repmgr/16/repmgr.conf cluster show
# 在备库检查VIP
ip addr show eth0 | grep 192.168.10.100

如果一切顺利,你会在备库上同时看到:repmgr标记它为新primary,并且VIP已经绑在这台机器的网卡上。这时我从另一台应用服务器连接VIP,执行写操作:

psql -h 192.168.10.100 -U testapp -d appdb -c "INSERT INTO ha_test (ts) VALUES (now());"

能写入,说明业务已经无缝切到新主库。

7.2 原主库恢复后怎么重归集群

这是大多数人到这一步就卡住的地方。直接把原主库的原进程启动起来是不行的,因为此时集群里已经有一个新主了,原主必须作为备库重新加入。步骤如下:

在 原主库 节点:

systemctl start postgresql-16

然后执行repmgr的follow命令,让它跟随新主库:

su - postgres
/usr/pgsql-16/bin/repmgr -f /etc/repmgr/16/repmgr.conf standby follow

这个命令会重新建立到新主库的流复制连接。如果原主库的WAL和数据落后太多,repmgr会提示需要重新clone,那就重复第5.4节的 standby clone 操作(会覆盖本机数据目录,谨慎确认)。

恢复完成后,再看看集群状态:

/usr/pgsql-16/bin/repmgr -f /etc/repmgr/16/repmgr.conf cluster show

输出里应该显示两个节点,新主库是 primary ,原主库是 standby 。然后回到新主库上执行:

/usr/pgsql-16/bin/repmgr -f /etc/repmgr/16/repmgr.conf primary register --force

为什么要 --force ?因为repmgr元数据里之前记录了原主库的primary信息,现在角色变了,需要强制覆盖旧的记录。

7.3 演练发现的两个真实教训

第一次演练我就踩坑了。我kill掉主库进程后,备库提升成功了,但VIP没有漂移过来。排查发现是脚本里的repmgr node check命令在角色切换的瞬间返回了“standby”,脚本直接exit 1,keepalived认为备库也不是“健康的primary”,于是两边都没人持有VIP。解决办法是去掉脚本里对repmgr node check的强依赖,只在数据库进程存活检查通过的前提下做软判断,或者给脚本加一个“容错窗口期”——切换刚完成的几十秒内,只要数据库能正常读写,就允许备库持有VIP。

第二个教训是failover触发时间太短。默认的 reconnect_attempts=3, reconnect_interval=5 意味着15秒就判定故障,我模拟网络抖动时直接把业务打断了。后来改成了 reconnect_attempts=6, reconnect_interval=10 ,再加上keepalived脚本的 fall 2 ,整个切换窗口变成了60秒左右,抖动基本都被过滤掉了。

8. 常见问题与实战排查速查表

8.1 六个高频问题的现场实录

拼了这么多,我相信总有一部分读者会卡在某个环节。把我这些年遇到的问题整理成速查表,按症状给出定位和解决方案:

症状 可能原因 排查命令 / 手段 解决办法
standby clone 失败,报 pg_basebackup: could not connect to server 主库pg_hba.conf没放行复制用户、或listen_addresses不是 * psql -h <主库IP> -U repl -d postgres -c "SELECT 1;" 检查监听地址、pg_hba.conf加复制规则并reload
备库能启动但复制状态一直是 catchup WAL保留量不足,备库落后太多 SELECT * FROM pg_stat_replication; 增大 wal_keep_size ,必要时重新clone
repmgrd启动报 ERROR: node is not registered 备库没有执行standby register repmgr cluster show 执行 standby register
keepalived日志报 VRRP is in fault state keepalived脚本返回非零,或网卡没择定 systemctl status keepalived 、 journalctl -u keepalived 检查脚本exit code,检查网卡名是否和配置一致
VIP漂移后新主库连不上、 connection refused 新主库的pg_hba.conf没有放行VIP网段或者listen_addresses不含VIP psql -h <VIP> -U testapp -d appdb 确认新主库的listen_addresses= '*'、pg_hba.conf里有VIP对应网段
主库恢复后执行 standby follow 报 unable to connect to remote node 新主库的防火墙或pg_hba.conf没放行原主库IP 在恢复节点测试 psql -h <新主库IP> 调整新主库的pg_hba.conf并 SELECT pg_reload_conf();

8.2 几个容易忽略的细节

除了上面这些具体问题,还有几个不能叫“坑”但非常重要的小细节。第一个是 备份 。高可用不是备份的替代品,就算是主从集群,也要定期做 pg_dump 或 pg_basebackup 的异地备份。万一两个节点同时挂了,只能靠备份恢复,这时候没有备份就等于归零。

第二个是 监控告警 。集群能自动切换不等于不需要人盯着。我在生产里把repmgr和keepalived的状态都接入了Zabbix告警,主备角色变化、VIP漂移、复制延迟超过30秒都会丁丁报警。自动机制解决“快”,人处理“对”,两者缺一不可。

第三个是 脑裂的物理兜底 。条件允许的话,在备库的脚本里加一道“双保险”:如果备库认为自己该提升,执行promote前先SSH到主库节点执行 pg_ctl stop -m immediate ,确保原主库不会继续写数据。虽然这会让恢复时间变长几秒,但能极大降低“双主”的风险。我的生产环境就是这么配的,稳妥比速度重要。

8.3 这套集群适合什么样的人持续演进

最后我想说一嘴,这套repmgr + keepalived的架构装好之后,你不是就“毕业”了。它有清晰的演进路径:上了三节点之后,可以考虑引入etcd做仲裁,把repmgr的failover和仲裁逻辑完全剥离开;业务再大一点,可以升级到Patroni,享受它更精细的调度策略和REST API。装集群的过程,本质上是理解“数据库高可用到底在解决什么问题”的过程,理解了这个,任何方案在你眼里都是同一道题的解法。

我个人的体会是,这套架构最值钱的地方不是那几个安装命令,而是故障出现时你能有条不紊地判断问题在数据库层还是网络层。第一次做主库故障演练时手心冒汗,多演练几次后,半夜收到告警也能从容处理。希望这篇内容能帮你少走一些弯路,把集群搭扎实。

更多推荐