青蛙小白
博客 / 2023/11

PostgreSQL 16.x 整实例恢复与 repmgr 主从重建

2023/11/14 · — 字 · 阅读约 — 分钟 ·
目录

最近帮一个朋友处理了一次生产环境的 PostgreSQL 恢复,要把数据库状态回退到某天凌晨的备份点。环境是两节点 PostgreSQL 集群,主从由 repmgr 管理。

主库恢复不算难,麻烦的是后半段:恢复后怎么把从库重新拉回正确状态。按处理顺序记一下过程和踩到的坑。


确认备份方式

动手之前先看备份是怎么做的。机器上的定时任务:

0 0 * * * /path/to/scripts/pg_dump.sh >> /path/to/scripts/pg_dump.log 2>&1

脚本核心逻辑:

backup_dir='/path/to/backups'

/usr/local/pgsql/bin/pg_dumpall -U ${pgsql_user} -h ${pgsql_host} -p ${pgsql_port} -f ${backup_dir}/${curr_sql}

cd ${backup_dir}
tar -zcvf ${curr_file} ${curr_sql}
rm -f ${curr_sql}

用的是 pg_dumpall,备份整个实例,输出是 SQL 文本,最后打包成 .tar.gz

这意味着只能做逻辑恢复,恢复到的"时间点"就是备份导出时的状态,不是 PITR 那种精确到秒的恢复。


不能直接往现有实例里灌

pg_dumpall 的输出包含数据库、角色、权限、schema、表、数据、索引等整实例对象。如果直接往还在运行的实例上执行,会大量报 already exists 错误,最后变成半旧半新的混合状态。

所以恢复思路是:停库 – 备份旧数据目录 – 初始化空实例 – 再导入


主库恢复

停库,备份旧数据目录

systemctl stop postgresql
mv /path/to/data /path/to/data.bak_YYYYMMDD

旧数据目录不要直接删,改名留着。后面万一需要回看配置或旧数据,有这个目录在手里不慌。

初始化空实例

mkdir -p /path/to/data
chown -R postgres:postgres /path/to/data
chmod 700 /path/to/data

su - postgres -c "/usr/local/pgsql/bin/initdb -D /path/to/data"

拷回配置文件

把旧目录的配置拷回新目录,保持端口、认证方式、监听地址等和原实例一致:

cp /path/to/data.bak/postgresql.conf /path/to/data/
cp /path/to/data.bak/pg_hba.conf /path/to/data/
chown postgres:postgres /path/to/data/postgresql.conf /path/to/data/pg_hba.conf

只拷配置,不拷旧数据文件。

启动并恢复

systemctl start postgresql
/usr/local/pgsql/bin/psql -U postgres -c "select version();"

确认空实例正常后,执行恢复:

/usr/local/pgsql/bin/psql -U postgres -f /path/to/backup.sql 2>&1 | tee /path/to/restore.log

过程中会刷 CREATE ROLECREATE DATABASECOPYALTER TABLECREATE INDEX 等输出,都是正常的。跑完后检查日志:

grep -n '^ERROR:' /path/to/restore.log

这次没有 ERROR:,算顺利。


验证主库

恢复结束后检查三项:

数据库列表:

select datname from pg_database order by 1;

角色列表:

psql -U postgres -c "\du"

业务库连接:

psql -U postgres -d <业务库> -c "select current_database();"

确认业务库、管理库、各类账号都在,主库恢复完成。


处理从库 – 主从关系断了

主库恢复完看 repmgr 集群状态,发现从库(节点 B)虽然在 repmgr 里注册的是 standby,但实际已经作为 primary 在跑了。

这就是脑裂了。主库是逻辑备份重建的全新实例,从库还保持着自己原来的运行状态,复制关系已经不存在了。

先停从库把风险控住:

systemctl stop postgresql

然后走标准的从库重建流程:

# 备份旧目录
mv /path/to/data /path/to/data.bak_YYYYMMDD

# 创建新目录
mkdir -p /path/to/data
chown -R postgres:postgres /path/to/data
chmod 700 /path/to/data

# 从主库 clone
su - postgres -c "repmgr -h <primary_host> -U <repmgr_user> -d <repmgr_db> -f /path/to/repmgr.conf standby clone --force"

底层就是 pg_basebackup 从主库拉一份基线。


踩坑:从库监听地址配错了

clone 完启动后,执行 repmgr standby register --force 失败。

排查发现不是复制链路问题,是 postgresql.conf 里的 listen_addresses 不对。之前拷配置的时候把主库的 listen_addresses 一起抄过来了,里面绑的 IP 是主库节点的地址:

listen_addresses = '127.0.0.1,<node_ip>'

<node_ip> 不是从库自己的地址,导致 TCP 连接不通。本地 Unix Socket 能用所以 psql 直连没问题,但 repmgrconninfo 连 TCP 端口就报错了。

改成从库自己的地址,重启:

systemctl restart postgresql

类似 listen_addressesconninfoprimary_conninfo 这些带节点属性的配置,两台机器之间不能无脑照抄。


重新注册 standby

监听地址修正后,注册 standby:

su - postgres -c "repmgr -f /path/to/repmgr.conf standby register --force"

回主库检查集群状态:

su - postgres -c "repmgr -f /path/to/repmgr.conf cluster show"

主节点 primary | running,从节点 standby | running

再看主库复制状态:

select application_name, client_addr, state, sync_state
from pg_stat_replication;

从库以 streaming 状态挂上来,复制链路恢复。


确认业务连接

数据库和复制恢复不等于业务好了,还要看应用有没有连回来:

select usename, datname, client_addr, application_name, state
from pg_stat_activity
where pid <> pg_backend_pid()
order by datname, usename;

看到业务账号从应用侧地址连进来(哪怕状态是 idle),说明网络通、鉴权通、目标库正确,应用已经开始恢复连接。再配合业务侧做关键功能验证就行。


小结

回头看这次恢复,主库那边没什么意外,pg_dumpall 逻辑恢复的套路比较固定,记得对空实例做就行。真正花时间的是从库——脑裂、监听地址抄错、repmgr 重新注册,这几个问题串起来折腾了不少时间。

另外 listen_addressesconninfoprimary_conninfo 这类带节点 IP 的配置,以后在主从之间拷配置的时候要逐项过一遍,不能偷懒。

评论