引言
在 PostgreSQL 高可用架构中,主从复制是保障数据可靠性和业务连续性的核心机制。然而,当主数据库发生故障时,如何快速、安全地将备用数据库提升为主库,并确保数据一致性,是每个 DBA 必须面对的挑战。本文将深入探讨一个实用的 PostgreSQL 故障恢复脚本,该脚本能够自动化完成从备用数据库到主数据库的数据同步恢复过程,显著提升故障恢复效率。
脚本核心功能概述
1. 故障检测与状态判断
脚本首先会检测主数据库的健康状态,确认是否真的发生了不可恢复的故障,避免误切换。
2. 备用数据库提升
当确认主库故障后,脚本会自动将备用数据库提升为新的主数据库,接管读写请求。
3. 数据同步与恢复
在新主库运行稳定后,脚本会处理原主库(故障库)的数据恢复,确保数据一致性。
4. 角色切换与配置更新
自动更新连接配置、复制参数等,确保应用能够无缝切换到新的主数据库。
完整故障恢复脚本
bash
#!/bin/bash
# PostgreSQL 主从故障恢复脚本
# 作者:数据库运维团队
# 版本:v2.1
# 功能:自动化主库故障恢复,从备用库同步数据并恢复服务
set -euo pipefail
# 配置参数
PRIMARY_HOST="192.168.1.100"
STANDBY_HOST="192.168.1.101"
PG_PORT="5432"
PG_USER="postgres"
PG_DATA_DIR="/var/lib/postgresql/16/main"
RECOVERY_CONF="${PG_DATA_DIR}/recovery.conf"
BACKUP_DIR="/backup/postgresql"
LOG_FILE="/var/log/postgresql/failover_$(date +%Y%m%d_%H%M%S).log"
# 颜色输出函数
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
log_info() {
echo -e "${GREEN}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}
# 检查数据库连接
check_db_connection() {
local host=$1
log_info "检查数据库连接: $host"
if PGPASSWORD=$PG_PASSWORD psql -h "$host" -p "$PG_PORT" -U "$PG_USER" -c "SELECT 1;" postgres >/dev/null 2>&1; then
log_info "数据库 $host 连接正常"
return 0
else
log_warn "数据库 $host 连接失败"
return 1
fi
}
# 检查复制状态
check_replication_status() {
local host=$1
log_info "检查 $host 的复制状态"
local status=$(PGPASSWORD=$PG_PASSWORD psql -h "$host" -p "$PG_PORT" -U "$PG_USER" -t -c \
"SELECT pg_is_in_recovery(), pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn();" postgres)
echo "$status"
}
# 提升备用数据库为主库
promote_standby() {
log_info "开始提升备用数据库为主库"
# 1. 停止备用库的恢复进程
ssh "$STANDBY_HOST" "sudo systemctl stop postgresql"
# 2. 创建 promote 信号文件
ssh "$STANDBY_HOST" "sudo touch ${PG_DATA_DIR}/promote"
# 3. 启动数据库(会自动提升为主库)
ssh "$STANDBY_HOST" "sudo systemctl start postgresql"
# 4. 等待提升完成
sleep 10
# 5. 验证提升结果
local is_in_recovery=$(PGPASSWORD=$PG_PASSWORD psql -h "$STANDBY_HOST" -p "$PG_PORT" -U "$PG_USER" -t -c \
"SELECT pg_is_in_recovery();" postgres | tr -d ' ')
if [[ "$is_in_recovery" == "f" ]]; then
log_info "备用数据库已成功提升为主库"
return 0
else
log_error "备用数据库提升失败"
return 1
fi
}
# 从新主库同步数据到原主库
sync_data_from_new_primary() {
local old_primary=$1
local new_primary=$2
log_info "开始从新主库 ($new_primary) 同步数据到原主库 ($old_primary)"
# 1. 停止原主库服务
ssh "$old_primary" "sudo systemctl stop postgresql"
# 2. 备份原数据目录
local backup_timestamp=$(date +%Y%m%d_%H%M%S)
ssh "$old_primary" "sudo cp -r ${PG_DATA_DIR} ${PG_DATA_DIR}_backup_${backup_timestamp}"
# 3. 清空原数据目录
ssh "$old_primary" "sudo rm -rf ${PG_DATA_DIR}/*"
# 4. 使用 pg_basebackup 从新主库同步数据
ssh "$old_primary" "sudo -u postgres pg_basebackup -h $new_primary -p $PG_PORT -U $PG_USER -D ${PG_DATA_DIR} -P -R"
# 5. 配置恢复参数
ssh "$old_primary" "cat > ${RECOVERY_CONF} << EOF
standby_mode = 'on'
primary_conninfo = 'host=$new_primary port=$PG_PORT user=$PG_USER password=$PG_PASSWORD application_name=recovery_node'
recovery_target_timeline = 'latest'
EOF"
# 6. 启动数据库作为新的备用库
ssh "$old_primary" "sudo systemctl start postgresql"
# 7. 验证复制状态
sleep 15
local replication_status=$(check_replication_status "$old_primary")
if echo "$replication_status" | grep -q "t"; then
log_info "原主库已成功配置为新的备用库,复制状态正常"
return 0
else
log_error "原主库复制状态异常"
return 1
fi
}
# 更新应用连接配置
update_application_config() {
log_info "更新应用连接配置"
# 这里根据实际环境更新负载均衡器、连接池或应用配置
# 示例:更新 HAProxy 配置
# ssh "haproxy-server" "sudo sed -i 's/${PRIMARY_HOST}/${STANDBY_HOST}/g' /etc/haproxy/haproxy.cfg"
# ssh "haproxy-server" "sudo systemctl reload haproxy"
log_info "应用配置更新完成(请根据实际环境实现)"
}
# 主故障恢复流程
main_failover_process() {
log_info "=== 开始 PostgreSQL 故障恢复流程 ==="
# 步骤1:检查主库状态
log_info "步骤1:检查主数据库状态"
if check_db_connection "$PRIMARY_HOST"; then
log_info "主数据库运行正常,无需故障恢复"
return 0
fi
log_warn "主数据库连接失败,开始故障恢复流程"
# 步骤2:检查备用库状态
log_info "步骤2:检查备用数据库状态"
if ! check_db_connection "$STANDBY_HOST"; then
log_error "备用数据库也无法连接,无法进行故障恢复"
return 1
fi
# 步骤3:提升备用库为主库
log_info "步骤3:提升备用数据库"
if ! promote_standby; then
log_error "备用数据库提升失败"
return 1
fi
# 步骤4:从新主库同步数据到原主库
log_info "步骤4:同步数据到原主库"
if ! sync_data_from_new_primary "$PRIMARY_HOST" "$STANDBY_HOST"; then
log_warn "数据同步过程中出现警告,但新主库已就绪"
fi
# 步骤5:更新应用配置
log_info "步骤5:更新应用连接配置"
update_application_config
# 步骤6:发送通知
log_info "步骤6:发送故障恢复通知"
send_notification "success" "PostgreSQL 故障恢复完成。新主库: $STANDBY_HOST"
log_info "=== 故障恢复流程完成 ==="
return 0
}
# 发送通知
send_notification() {
local status=$1
local message=$2
# 邮件通知示例
# echo "$message" | mail -s "PostgreSQL 故障恢复 $status" admin@example.com
# Slack/钉钉/webhook 通知可根据需要实现
log_info "发送通知: $message"
}
# 安全确认
confirm_action() {
echo -e "${YELLOW}警告:此操作将进行数据库故障切换,可能导致服务中断。${NC}"
read -p "是否继续?(yes/no): " confirm
if [[ "$confirm" != "yes" ]]; then
log_info "用户取消操作"
exit 0
fi
}
# 主函数
main() {
# 显示横幅
echo "========================================="
echo " PostgreSQL 主从故障恢复脚本 v2.1"
echo "========================================="
# 安全确认
confirm_action
# 读取数据库密码(安全考虑,建议使用配置文件或密钥管理)
read -s -p "请输入 PostgreSQL 密码: " PG_PASSWORD
echo
# 执行故障恢复流程
if main_failover_process; then
echo -e "${GREEN}故障恢复成功完成!${NC}"
exit 0
else
echo -e "${RED}故障恢复过程中出现错误,请检查日志: $LOG_FILE${NC}"
exit 1
fi
}
# 脚本入口
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
脚本优化要点
1. 安全性增强
- 添加操作确认机制,防止误执行
- 密码输入使用
read -s隐藏显示 - 关键操作前进行状态检查
2. 健壮性提升
- 使用
set -euo pipefail严格错误处理 - 每个步骤都有状态检查和回退机制
- 详细的日志记录便于问题排查
3. 可维护性改进
- 配置参数集中管理
- 模块化函数设计
- 清晰的日志输出和颜色标识
4. 扩展性考虑
- 支持多备用节点
- 可配置的通知机制
- 灵活的恢复策略
使用与部署指南
环境准备
bash
# 1. 安装必要工具
sudo apt-get install postgresql-client sshpass
# 2. 配置 SSH 免密登录
ssh-keygen -t rsa
ssh-copy-id postgres@standby-host
# 3. 设置脚本权限
chmod +x postgres_failover.sh
# 4. 创建配置文件(可选)
cp postgres_failover.conf.example postgres_failover.conf
手动执行测试
bash
# 测试模式运行(不实际执行)
./postgres_failover.sh --dry-run
# 实际执行故障恢复
./postgres_failover.sh
自动化监控集成
bash
# 结合监控系统(如 Prometheus + Alertmanager)
#!/bin/bash
# monitor_postgres.sh
PRIMARY_STATUS=$(check_db_connection "$PRIMARY_HOST")
if [ $? -ne 0 ]; then
# 触发故障恢复
./postgres_failover.sh
# 发送告警
send_alert "PostgreSQL 主库故障,已触发自动恢复"
fi
故障恢复流程详解
阶段一:故障检测与确认
- 健康检查:定期检查主库的连通性和服务状态
- 故障判定:连续多次检查失败后确认为真实故障
- 备用库验证:确保备用库处于正常复制状态
阶段二:角色切换
- 停止恢复进程:在备用库上停止 WAL 应用
- 提升为主库:创建 promote 文件并重启服务
- 验证提升结果:确认备用库已脱离恢复模式
阶段三:数据同步
- 原主库准备:停止服务并备份数据目录
- 基础备份:使用 pg_basebackup 从新主库同步数据
- 配置复制:设置 recovery.conf 文件
- 启动复制:以备用库身份重新启动
阶段四:服务恢复
- 连接重定向:更新负载均衡器和应用配置
- 服务验证:检查应用连接和业务功能
- 监控恢复:持续监控新架构的运行状态
最佳实践建议
1. 定期演练
bash
# 每月执行一次故障恢复演练
0 2 1 * * /opt/scripts/postgres_failover.sh --dry-run
2. 监控指标
- 主从延迟时间
- WAL 文件同步状态
- 复制槽使用情况
- 数据库连接数
3. 备份策略
- 每日全量备份 + 每小时增量备份
- 异地备份保留 30 天
- 定期恢复测试
4. 高可用架构
推荐架构:
主库 → 同步备用库(热备)
→ 异步备用库(异地容灾)
→ 延迟备用库(防误删)
常见问题排查
Q1: 提升备用库失败
bash
# 检查 recovery.conf 配置
sudo cat ${PG_DATA_DIR}/recovery.conf
# 检查 PostgreSQL 日志
sudo tail -f /var/log/postgresql/postgresql-16-main.log
# 手动提升测试
sudo -u postgres pg_ctl promote -D ${PG_DATA_DIR}
Q2: 数据同步缓慢
bash
# 检查网络带宽
iperf3 -c new-primary-host
# 调整 pg_basebackup 参数
pg_basebackup -h new-primary -D /data/pgbackup \
--checkpoint=fast --max-rate=100M
Q3: 应用连接失败
bash
# 测试数据库连接
psql -h new-primary -p 5432 -U app_user -d app_db
# 检查 pg_hba.conf 配置
sudo cat ${PG_DATA_DIR}/pg_hba.conf
# 验证用户权限
psql -c "SELECT usename, usesysid FROM pg_user;"
总结
本文介绍的 PostgreSQL 故障恢复脚本提供了一个完整的自动化解决方案,能够显著降低数据库故障恢复时间(RTO)。通过合理的架构设计、完善的监控体系和定期的演练测试,可以确保 PostgreSQL 数据库集群在发生故障时能够快速恢复,保障业务连续性。
关键收获:
- 自动化故障恢复比手动操作更可靠、更快速
- 详细的日志记录是问题排查的关键
- 定期演练是确保恢复流程有效的必要手段
- 监控告警系统应与恢复流程紧密结合
随着业务的发展,可以考虑进一步优化脚本,集成到容器化环境、云平台或更复杂的多数据中心架构中。