Appearance
PostgreSQL 文件系统级别备份完整指南
概述
文件系统级别备份是 PostgreSQL 数据库备份的另一种重要策略,通过直接复制数据库文件来实现数据保护。与逻辑备份(如 pg_dump
)不同,文件系统备份直接操作底层存储文件,在特定场景下具有独特的优势。
INFO
文件系统备份适用于需要快速恢复大型数据库、灾难恢复场景,以及需要保持数据库物理结构完整性的情况。
核心概念与工作原理
基本原理
PostgreSQL 将所有数据存储在数据目录(通常为 /usr/local/pgsql/data
或 /var/lib/postgresql/data
)中,包括:
- 数据文件:包含表、索引等数据的物理文件
- WAL 日志:预写式日志,记录所有数据变更
- 配置文件:postgresql.conf、pg_hba.conf 等
- 元数据:系统表、事务状态等
业务场景分析
适用场景:
- 🏢 大型企业数据仓库:TB 级数据库的快速恢复
- 🚀 高可用系统:配合流复制的灾难恢复
- 📊 数据迁移项目:跨服务器的完整数据迁移
- 🔄 开发环境克隆:快速创建生产环境副本
不适用场景:
- 📱 在线服务的增量备份
- 🎯 单表或部分数据的选择性备份
- 🌐 跨版本的数据库迁移
文件系统备份方法详解
方法一:基本文件复制
实现步骤
bash
# 1. 停止 PostgreSQL 服务
sudo systemctl stop postgresql
# 2. 创建备份目录
sudo mkdir -p /backup/postgresql/$(date +%Y%m%d_%H%M%S)
# 3. 执行文件复制备份
sudo tar -czf /backup/postgresql/$(date +%Y%m%d_%H%M%S)/postgres_backup.tar.gz \
-C /var/lib/postgresql/14/main .
# 4. 验证备份文件
ls -lh /backup/postgresql/$(date +%Y%m%d_%H%M%S)/
# 5. 重启服务
sudo systemctl start postgresql
完整示例:生产环境备份脚本
bash
#!/bin/bash
# 文件名:postgres_filesystem_backup.sh
# 配置变量
POSTGRES_VERSION="14"
DATA_DIR="/var/lib/postgresql/${POSTGRES_VERSION}/main"
BACKUP_BASE_DIR="/backup/postgresql"
RETENTION_DAYS=7
# 创建带时间戳的备份目录
BACKUP_DIR="${BACKUP_BASE_DIR}/$(date +%Y%m%d_%H%M%S)"
mkdir -p "$BACKUP_DIR"
# 日志记录函数
log_message() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$BACKUP_DIR/backup.log"
}
# 开始备份流程
log_message "开始 PostgreSQL 文件系统备份"
# 检查磁盘空间(至少需要数据目录 2 倍大小)
REQUIRED_SPACE=$(du -s "$DATA_DIR" | awk '{print $1 * 2}')
AVAILABLE_SPACE=$(df "$BACKUP_BASE_DIR" | tail -1 | awk '{print $4}')
if [ "$AVAILABLE_SPACE" -lt "$REQUIRED_SPACE" ]; then
log_message "错误:磁盘空间不足,需要 ${REQUIRED_SPACE}KB,可用 ${AVAILABLE_SPACE}KB"
exit 1
fi
# 执行 CHECKPOINT 减少恢复时间
log_message "执行 CHECKPOINT"
sudo -u postgres psql -c "CHECKPOINT;" 2>&1 | tee -a "$BACKUP_DIR/backup.log"
# 停止 PostgreSQL 服务
log_message "停止 PostgreSQL 服务"
sudo systemctl stop postgresql
# 检查服务是否完全停止
sleep 5
if pgrep -f postgres > /dev/null; then
log_message "警告:PostgreSQL 进程仍在运行,强制终止"
sudo pkill -9 -f postgres
sleep 3
fi
# 创建数据目录备份
log_message "开始复制数据文件"
tar -czf "$BACKUP_DIR/postgres_data.tar.gz" \
-C "$(dirname "$DATA_DIR")" \
"$(basename "$DATA_DIR")" 2>&1 | tee -a "$BACKUP_DIR/backup.log"
# 记录备份元信息
cat > "$BACKUP_DIR/backup_info.txt" << EOF
备份时间: $(date)
PostgreSQL 版本: $POSTGRES_VERSION
数据目录: $DATA_DIR
备份大小: $(du -h "$BACKUP_DIR/postgres_data.tar.gz" | cut -f1)
服务器: $(hostname)
操作系统: $(uname -a)
EOF
# 重启 PostgreSQL 服务
log_message "重启 PostgreSQL 服务"
sudo systemctl start postgresql
# 验证服务状态
sleep 5
if sudo systemctl is-active --quiet postgresql; then
log_message "PostgreSQL 服务启动成功"
else
log_message "错误:PostgreSQL 服务启动失败"
exit 1
fi
# 清理旧备份
log_message "清理 ${RETENTION_DAYS} 天前的备份"
find "$BACKUP_BASE_DIR" -type d -mtime +$RETENTION_DAYS -exec rm -rf {} \; 2>/dev/null
log_message "备份完成,备份位置:$BACKUP_DIR"
# 发送备份完成通知(可选)
# echo "PostgreSQL 备份完成:$BACKUP_DIR" | mail -s "备份通知" [email protected]
使用示例
bash
# 给脚本添加执行权限
chmod +x postgres_filesystem_backup.sh
# 执行备份
./postgres_filesystem_backup.sh
# 输出示例:
# [2024-06-04 10:30:00] 开始 PostgreSQL 文件系统备份
# [2024-06-04 10:30:01] 执行 CHECKPOINT
# [2024-06-04 10:30:05] 停止 PostgreSQL 服务
# [2024-06-04 10:30:10] 开始复制数据文件
# [2024-06-04 10:32:45] 重启 PostgreSQL 服务
# [2024-06-04 10:32:50] PostgreSQL 服务启动成功
# [2024-06-04 10:32:51] 清理 7 天前的备份
# [2024-06-04 10:32:52] 备份完成,备份位置:/backup/postgresql/20240604_103000
方法二:一致性快照备份
LVM 快照示例
适用于使用 LVM(逻辑卷管理)的系统,可以在不停止数据库的情况下创建一致性快照。
bash
#!/bin/bash
# LVM 快照备份脚本
VG_NAME="vg_data" # 卷组名
LV_NAME="lv_postgres" # 逻辑卷名
SNAPSHOT_NAME="postgres_snap" # 快照名
SNAPSHOT_SIZE="10G" # 快照大小
BACKUP_DIR="/backup/snapshot/$(date +%Y%m%d_%H%M%S)"
# 创建备份目录
mkdir -p "$BACKUP_DIR"
log_message() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}
log_message "开始 LVM 快照备份"
# 执行 CHECKPOINT 确保数据一致性
log_message "执行 CHECKPOINT"
sudo -u postgres psql -c "CHECKPOINT;"
# 创建 LVM 快照
log_message "创建 LVM 快照"
sudo lvcreate -L"$SNAPSHOT_SIZE" -s -n "$SNAPSHOT_NAME" "/dev/$VG_NAME/$LV_NAME"
if [ $? -ne 0 ]; then
log_message "错误:LVM 快照创建失败"
exit 1
fi
# 挂载快照
MOUNT_POINT="/mnt/postgres_snapshot"
sudo mkdir -p "$MOUNT_POINT"
sudo mount "/dev/$VG_NAME/$SNAPSHOT_NAME" "$MOUNT_POINT"
# 从快照复制数据
log_message "从快照复制数据"
sudo tar -czf "$BACKUP_DIR/postgres_snapshot.tar.gz" -C "$MOUNT_POINT" .
# 清理快照
log_message "清理快照资源"
sudo umount "$MOUNT_POINT"
sudo lvremove -f "/dev/$VG_NAME/$SNAPSHOT_NAME"
sudo rmdir "$MOUNT_POINT"
log_message "快照备份完成:$BACKUP_DIR"
ZFS 快照示例
bash
#!/bin/bash
# ZFS 快照备份脚本
DATASET="tank/postgres" # ZFS 数据集
SNAPSHOT_NAME="backup_$(date +%Y%m%d_%H%M%S)"
BACKUP_DIR="/backup/zfs/$(date +%Y%m%d_%H%M%S)"
mkdir -p "$BACKUP_DIR"
log_message() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}
log_message "开始 ZFS 快照备份"
# 执行 CHECKPOINT
sudo -u postgres psql -c "CHECKPOINT;"
# 创建 ZFS 快照
log_message "创建 ZFS 快照"
sudo zfs snapshot "${DATASET}@${SNAPSHOT_NAME}"
# 发送快照到备份位置
log_message "发送快照数据"
sudo zfs send "${DATASET}@${SNAPSHOT_NAME}" | gzip > "$BACKUP_DIR/postgres_zfs.gz"
# 验证快照
sudo zfs list -t snapshot | grep "$SNAPSHOT_NAME"
log_message "ZFS 快照备份完成:$BACKUP_DIR"
# 可选:清理快照(根据保留策略)
# sudo zfs destroy "${DATASET}@${SNAPSHOT_NAME}"
方法三:rsync 增量备份
适用于最小化停机时间的场景,通过两阶段 rsync 实现快速备份。
bash
#!/bin/bash
# rsync 两阶段备份脚本
SOURCE_DIR="/var/lib/postgresql/14/main"
BACKUP_DIR="/backup/rsync/$(date +%Y%m%d_%H%M%S)"
TEMP_BACKUP_DIR="/backup/rsync/temp"
mkdir -p "$BACKUP_DIR"
mkdir -p "$TEMP_BACKUP_DIR"
log_message() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$BACKUP_DIR/rsync_backup.log"
}
log_message "开始 rsync 两阶段备份"
# 第一阶段:在线备份(数据库运行中)
log_message "第一阶段:在线数据同步"
rsync -av --delete "$SOURCE_DIR/" "$TEMP_BACKUP_DIR/" 2>&1 | tee -a "$BACKUP_DIR/rsync_backup.log"
ONLINE_DURATION=$SECONDS
log_message "在线同步完成,耗时:${ONLINE_DURATION}秒"
# 执行 CHECKPOINT 为停机做准备
log_message "执行 CHECKPOINT"
sudo -u postgres psql -c "CHECKPOINT;"
# 停止 PostgreSQL 服务
log_message "停止 PostgreSQL 服务"
STOP_TIME=$(date +%s)
sudo systemctl stop postgresql
# 第二阶段:校验和同步(数据库停止)
log_message "第二阶段:校验和最终同步"
rsync -av --checksum --delete "$SOURCE_DIR/" "$TEMP_BACKUP_DIR/" 2>&1 | tee -a "$BACKUP_DIR/rsync_backup.log"
# 移动到最终备份位置
mv "$TEMP_BACKUP_DIR" "$BACKUP_DIR/data"
# 重启 PostgreSQL 服务
START_TIME=$(date +%s)
sudo systemctl start postgresql
DOWNTIME=$((START_TIME - STOP_TIME))
log_message "PostgreSQL 服务重启完成,停机时间:${DOWNTIME}秒"
# 创建备份信息文件
cat > "$BACKUP_DIR/backup_info.json" << EOF
{
"backup_method": "rsync_two_stage",
"backup_start": "$(date -d @$STOP_TIME)",
"backup_end": "$(date -d @$START_TIME)",
"downtime_seconds": $DOWNTIME,
"online_sync_seconds": $ONLINE_DURATION,
"source_dir": "$SOURCE_DIR",
"backup_size": "$(du -sh "$BACKUP_DIR/data" | cut -f1)",
"server": "$(hostname)"
}
EOF
log_message "rsync 备份完成,停机时间仅:${DOWNTIME}秒"
关键限制与注意事项
限制一:必须停止服务器
在数据库运行时进行文件复制会导致数据不一致,因为:
- 缓冲区数据:内存中的脏页可能未写入磁盘
- 事务状态:正在进行的事务可能处于中间状态
- 文件锁定:某些文件可能被数据库进程锁定
业务影响分析
bash
# 评估停机时间的脚本
#!/bin/bash
DATA_SIZE=$(du -sb /var/lib/postgresql/14/main | cut -f1)
BACKUP_SPEED=100000000 # 100MB/s 估算速度
ESTIMATED_TIME=$((DATA_SIZE / BACKUP_SPEED))
ESTIMATED_MINUTES=$((ESTIMATED_TIME / 60))
echo "数据大小:$(numfmt --to=iec $DATA_SIZE)"
echo "预估备份时间:${ESTIMATED_MINUTES}分钟"
echo "业务影响评估:"
echo "- 在线服务将中断 ${ESTIMATED_MINUTES} 分钟"
echo "- 建议在业务低峰期(如凌晨 2-4 点)执行"
echo "- 考虑使用快照备份减少停机时间"
限制二:不支持部分备份
PostgreSQL 的数据文件之间存在复杂的依赖关系,必须作为整体进行备份。
以下备份方式是**错误的**,会导致数据损坏:
- 仅备份特定数据库目录
- 仅备份表文件而忽略事务日志
- 分别备份数据文件和 WAL 日志
错误示例与后果
bash
# ❌ 错误:仅备份单个数据库
tar -czf wrong_backup.tar.gz /var/lib/postgresql/14/main/base/16384/
# ❌ 错误:仅备份数据文件
tar -czf wrong_backup.tar.gz /var/lib/postgresql/14/main/base/
# ✅ 正确:完整备份
tar -czf correct_backup.tar.gz /var/lib/postgresql/14/main/
错误备份的恢复尝试:
bash
# 尝试恢复错误备份
sudo systemctl stop postgresql
sudo rm -rf /var/lib/postgresql/14/main/*
sudo tar -xzf wrong_backup.tar.gz -C /var/lib/postgresql/14/main/
sudo systemctl start postgresql
# 结果:PostgreSQL 启动失败
# 错误日志:
# FATAL: could not access status of transaction 12345
# DETAIL: Could not read from file "pg_xact/0000" at offset 16384
多文件系统环境的挑战
问题场景
在企业环境中,PostgreSQL 组件经常分布在不同存储设备上:
解决方案:同步快照脚本
bash
#!/bin/bash
# 多文件系统同步快照备份
# 配置存储路径
DATA_PATH="/fast_ssd/postgresql/data"
WAL_PATH="/slow_hdd/postgresql/wal"
TABLESPACE_PATH="/nfs_mount/tablespace"
BACKUP_BASE="/backup/multi_fs/$(date +%Y%m%d_%H%M%S)"
mkdir -p "$BACKUP_BASE"
log_message() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$BACKUP_BASE/sync_backup.log"
}
# 检查所有存储设备的快照能力
check_snapshot_capability() {
log_message "检查存储设备快照能力"
# 检查 LVM 卷
if ! lvdisplay | grep -q "fast_ssd"; then
log_message "警告:SSD 不支持 LVM 快照"
return 1
fi
# 检查文件系统类型
FS_TYPE=$(df -T "$DATA_PATH" | tail -1 | awk '{print $2}')
log_message "数据目录文件系统:$FS_TYPE"
if [[ "$FS_TYPE" != "ext4" && "$FS_TYPE" != "xfs" && "$FS_TYPE" != "zfs" ]]; then
log_message "警告:文件系统 $FS_TYPE 可能不支持一致性快照"
fi
return 0
}
# 执行同步快照备份
perform_sync_snapshot() {
log_message "开始同步快照备份"
# 执行 CHECKPOINT 确保数据一致性
sudo -u postgres psql -c "CHECKPOINT;"
# 创建备份脚本的并行执行
cat > "$BACKUP_BASE/snapshot_commands.sh" << 'EOF'
#!/bin/bash
# 并行执行快照命令
# SSD 快照
lvcreate -L5G -s -n data_snap /dev/vg_fast/lv_data &
PID1=$!
# HDD 快照
lvcreate -L2G -s -n wal_snap /dev/vg_slow/lv_wal &
PID2=$!
# NFS 备份(无法快照,直接复制)
rsync -av /nfs_mount/tablespace/ /backup/tablespace_temp/ &
PID3=$!
# 等待所有操作完成
wait $PID1 $PID2 $PID3
echo "所有快照创建完成:$(date)"
EOF
chmod +x "$BACKUP_BASE/snapshot_commands.sh"
# 停止 PostgreSQL(确保一致性)
log_message "停止 PostgreSQL 服务"
sudo systemctl stop postgresql
# 并行执行快照
log_message "并行创建快照"
sudo "$BACKUP_BASE/snapshot_commands.sh" 2>&1 | tee -a "$BACKUP_BASE/sync_backup.log"
# 重启 PostgreSQL
log_message "重启 PostgreSQL 服务"
sudo systemctl start postgresql
# 从快照复制数据
copy_from_snapshots
}
# 从快照复制数据
copy_from_snapshots() {
log_message "从快照复制数据到备份位置"
# 挂载快照并复制
mkdir -p /mnt/{data_snap,wal_snap}
sudo mount /dev/vg_fast/data_snap /mnt/data_snap
sudo mount /dev/vg_slow/wal_snap /mnt/wal_snap
# 并行复制
tar -czf "$BACKUP_BASE/data.tar.gz" -C /mnt/data_snap . &
tar -czf "$BACKUP_BASE/wal.tar.gz" -C /mnt/wal_snap . &
tar -czf "$BACKUP_BASE/tablespace.tar.gz" -C /backup/tablespace_temp . &
wait # 等待所有复制完成
# 清理快照
sudo umount /mnt/{data_snap,wal_snap}
sudo lvremove -f /dev/vg_fast/data_snap
sudo lvremove -f /dev/vg_slow/wal_snap
rm -rf /backup/tablespace_temp
log_message "快照备份完成"
}
# 主执行流程
if check_snapshot_capability; then
perform_sync_snapshot
else
log_message "回退到传统文件系统备份"
# 执行传统备份逻辑
fi
恢复过程详解
基本恢复流程
完整恢复脚本
bash
#!/bin/bash
# PostgreSQL 文件系统备份恢复脚本
BACKUP_FILE="$1"
POSTGRES_VERSION="14"
DATA_DIR="/var/lib/postgresql/${POSTGRES_VERSION}/main"
POSTGRES_USER="postgres"
if [ -z "$BACKUP_FILE" ]; then
echo "用法: $0 <备份文件路径>"
echo "示例: $0 /backup/postgresql/20240604_103000/postgres_data.tar.gz"
exit 1
fi
log_message() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}
# 验证备份文件
verify_backup() {
log_message "验证备份文件"
if [ ! -f "$BACKUP_FILE" ]; then
log_message "错误:备份文件不存在:$BACKUP_FILE"
exit 1
fi
# 检查备份文件完整性
if ! tar -tzf "$BACKUP_FILE" >/dev/null 2>&1; then
log_message "错误:备份文件损坏或格式错误"
exit 1
fi
# 验证备份内容
log_message "备份文件内容预览:"
tar -tzf "$BACKUP_FILE" | head -10
}
# 准备恢复环境
prepare_recovery() {
log_message "准备恢复环境"
# 停止 PostgreSQL 服务
if systemctl is-active --quiet postgresql; then
log_message "停止 PostgreSQL 服务"
sudo systemctl stop postgresql
# 确保进程完全终止
sleep 5
if pgrep -f postgres; then
log_message "强制终止残留进程"
sudo pkill -9 -f postgres
fi
fi
# 备份当前数据(如果存在)
if [ -d "$DATA_DIR" ] && [ "$(ls -A $DATA_DIR 2>/dev/null)" ]; then
CURRENT_BACKUP="/tmp/postgresql_current_$(date +%Y%m%d_%H%M%S)"
log_message "备份当前数据到:$CURRENT_BACKUP"
sudo mv "$DATA_DIR" "$CURRENT_BACKUP"
fi
# 创建新的数据目录
sudo mkdir -p "$DATA_DIR"
}
# 执行恢复
perform_recovery() {
log_message "开始恢复数据"
# 解压备份到数据目录
log_message "解压备份文件"
sudo tar -xzf "$BACKUP_FILE" -C "$DATA_DIR" --strip-components=1
if [ $? -ne 0 ]; then
log_message "错误:解压备份文件失败"
exit 1
fi
# 设置正确的文件权限
log_message "设置文件权限"
sudo chown -R "$POSTGRES_USER:$POSTGRES_USER" "$DATA_DIR"
sudo chmod 700 "$DATA_DIR"
sudo chmod 600 "$DATA_DIR"/*.conf 2>/dev/null || true
# 验证关键文件存在
verify_critical_files
}
# 验证关键文件
verify_critical_files() {
log_message "验证关键文件"
CRITICAL_FILES=(
"postgresql.conf"
"pg_hba.conf"
"PG_VERSION"
"base"
"global"
"pg_wal"
)
for file in "${CRITICAL_FILES[@]}"; do
if [ ! -e "$DATA_DIR/$file" ]; then
log_message "错误:缺少关键文件:$file"
exit 1
fi
done
log_message "关键文件验证通过"
}
# 启动并验证恢复
start_and_verify() {
log_message "启动 PostgreSQL 服务"
# 启动服务
sudo systemctl start postgresql
# 等待服务启动
sleep 10
# 检查服务状态
if ! systemctl is-active --quiet postgresql; then
log_message "错误:PostgreSQL 服务启动失败"
log_message "查看错误日志:"
sudo journalctl -u postgresql --no-pager -n 20
exit 1
fi
# 测试数据库连接
log_message "测试数据库连接"
if sudo -u postgres psql -c "SELECT version();" >/dev/null 2>&1; then
log_message "数据库连接成功"
else
log_message "错误:无法连接到数据库"
exit 1
fi
# 检查数据库状态
check_database_status
}
# 检查数据库状态
check_database_status() {
log_message "检查数据库状态"
# 检查恢复过程
RECOVERY_INFO=$(sudo -u postgres psql -t -c "
SELECT
CASE
WHEN pg_is_in_recovery() THEN 'In Recovery'
ELSE 'Normal Operation'
END as status,
pg_size_pretty(pg_database_size(current_database())) as db_size;
" 2>/dev/null)
if [ $? -eq 0 ]; then
log_message "数据库状态:$RECOVERY_INFO"
fi
# 列出数据库
log_message "可用数据库:"
sudo -u postgres psql -l 2>/dev/null | grep -E "^\s*\w+\s*\|"
# 检查表空间
log_message "表空间信息:"
sudo -u postgres psql -c "SELECT spcname, pg_size_pretty(pg_tablespace_size(spcname)) FROM pg_tablespace;" 2>/dev/null
}
# 主执行流程
main() {
log_message "开始 PostgreSQL 文件系统备份恢复"
log_message "备份文件:$BACKUP_FILE"
verify_backup
prepare_recovery
perform_recovery
start_and_verify
log_message "恢复完成!数据库已准备就绪"
log_message "建议执行以下后续操作:"
echo " 1. 验证数据完整性"
echo " 2. 更新统计信息:ANALYZE;"
echo " 3. 重建索引(如需要):REINDEX;"
echo " 4. 检查应用程序连接"
}
# 执行恢复
main
恢复验证检查清单
bash
#!/bin/bash
# 恢复后验证脚本
log_message() {
echo "[验证] $1"
}
# 1. 服务状态检查
log_message "检查 PostgreSQL 服务状态"
systemctl status postgresql --no-pager
# 2. 数据库连接测试
log_message "测试数据库连接"
sudo -u postgres psql -c "SELECT current_timestamp, version();"
# 3. 数据完整性检查
log_message "检查数据完整性"
sudo -u postgres psql -c "
SELECT
schemaname,
tablename,
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) as size
FROM pg_tables
WHERE schemaname NOT IN ('information_schema', 'pg_catalog')
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC
LIMIT 10;
"
# 4. 事务日志检查
log_message "检查 WAL 恢复状态"
sudo -u postgres psql -c "
SELECT
pg_is_in_recovery() as in_recovery,
pg_last_wal_receive_lsn() as last_wal_received,
pg_last_wal_replay_lsn() as last_wal_replayed;
"
# 5. 性能基准测试
log_message "执行简单性能测试"
sudo -u postgres psql -c "
SELECT
count(*) as connection_test
FROM pg_stat_activity;
"
log_message "验证完成"
性能优化与最佳实践
备份性能优化
1. 并行备份策略
bash
#!/bin/bash
# 并行备份脚本 - 适用于多核心服务器
CORES=$(nproc)
DATA_DIR="/var/lib/postgresql/14/main"
BACKUP_DIR="/backup/parallel/$(date +%Y%m%d_%H%M%S)"
mkdir -p "$BACKUP_DIR"
log_message() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}
# 分析数据目录结构
analyze_data_structure() {
log_message "分析数据目录结构"
# 找出最大的数据库目录
find "$DATA_DIR/base" -maxdepth 1 -type d -exec du -s {} \; | \
sort -rn | head -5 > "$BACKUP_DIR/large_databases.txt"
log_message "最大的数据库目录:"
cat "$BACKUP_DIR/large_databases.txt"
}
# 并行备份执行
parallel_backup() {
log_message "开始并行备份(使用 $CORES 个核心)"
# 停止数据库
sudo systemctl stop postgresql
# 并行压缩不同组件
(
log_message "备份数据文件"
tar -czf "$BACKUP_DIR/base.tar.gz" -C "$DATA_DIR" base/
) &
(
log_message "备份 WAL 文件"
tar -czf "$BACKUP_DIR/pg_wal.tar.gz" -C "$DATA_DIR" pg_wal/
) &
(
log_message "备份全局数据"
tar -czf "$BACKUP_DIR/global.tar.gz" -C "$DATA_DIR" global/
) &
(
log_message "备份配置文件"
tar -czf "$BACKUP_DIR/config.tar.gz" -C "$DATA_DIR" *.conf
) &
# 等待所有备份完成
wait
# 重启数据库
sudo systemctl start postgresql
log_message "并行备份完成"
}
analyze_data_structure
parallel_backup
2. 压缩优化策略
bash
#!/bin/bash
# 不同压缩算法的性能对比
DATA_DIR="/var/lib/postgresql/14/main"
TEST_FILE="$DATA_DIR/base"
BACKUP_BASE="/tmp/compression_test"
mkdir -p "$BACKUP_BASE"
# 测试不同压缩方法
test_compression() {
local method="$1"
local extension="$2"
local command="$3"
echo "测试 $method 压缩..."
start_time=$(date +%s)
eval "$command '$BACKUP_BASE/test.$extension' '$TEST_FILE/'"
end_time=$(date +%s)
duration=$((end_time - start_time))
size=$(du -h "$BACKUP_BASE/test.$extension" | cut -f1)
echo "$method: 时间=${duration}s, 大小=$size"
}
# 压缩算法对比
echo "压缩性能测试:"
test_compression "gzip" "tar.gz" "tar -czf"
test_compression "bzip2" "tar.bz2" "tar -cjf"
test_compression "xz" "tar.xz" "tar -cJf"
test_compression "lz4" "tar.lz4" "tar -c | lz4 >"
test_compression "zstd" "tar.zst" "tar -c | zstd >"
# 清理测试文件
rm -f "$BACKUP_BASE"/test.*
输出示例:
压缩性能测试:
gzip: 时间=45s, 大小=2.1G
bzip2: 时间=120s, 大小=1.8G
xz: 时间=180s, 大小=1.6G
lz4: 时间=15s, 大小=2.8G
zstd: 时间=25s, 大小=2.0G
推荐:
- 快速备份:使用 lz4(速度优先)
- 存储优化:使用 xz(压缩率优先)
- 平衡选择:使用 zstd(速度与压缩率平衡)
监控与报警
备份监控脚本
bash
#!/bin/bash
# 备份监控和报警脚本
BACKUP_DIR="/backup/postgresql"
ALERT_EMAIL="[email protected]"
MAX_BACKUP_AGE_HOURS=25 # 超过25小时认为备份失败
# 检查最新备份
check_latest_backup() {
local latest_backup=$(find "$BACKUP_DIR" -name "*.tar.gz" -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -d' ' -f2-)
if [ -z "$latest_backup" ]; then
send_alert "错误:未找到任何备份文件"
return 1
fi
local backup_time=$(stat -c %Y "$latest_backup")
local current_time=$(date +%s)
local age_hours=$(( (current_time - backup_time) / 3600 ))
echo "最新备份:$latest_backup"
echo "备份时间:$(date -d @$backup_time)"
echo "已过时间:${age_hours}小时"
if [ $age_hours -gt $MAX_BACKUP_AGE_HOURS ]; then
send_alert "警告:备份已过期${age_hours}小时,超过阈值${MAX_BACKUP_AGE_HOURS}小时"
return 1
fi
return 0
}
# 检查备份完整性
check_backup_integrity() {
local latest_backup=$(find "$BACKUP_DIR" -name "*.tar.gz" -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -d' ' -f2-)
echo "验证备份完整性:$latest_backup"
if tar -tzf "$latest_backup" >/dev/null 2>&1; then
echo "✓ 备份文件完整"
return 0
else
send_alert "错误:备份文件损坏 - $latest_backup"
return 1
fi
}
# 检查磁盘空间
check_disk_space() {
local usage=$(df "$BACKUP_DIR" | tail -1 | awk '{print $5}' | sed 's/%//')
echo "备份磁盘使用率:${usage}%"
if [ $usage -gt 90 ]; then
send_alert "警告:备份磁盘空间不足,使用率${usage}%"
return 1
elif [ $usage -gt 80 ]; then
echo "注意:磁盘使用率较高(${usage}%),建议清理旧备份"
fi
return 0
}
# 发送报警
send_alert() {
local message="$1"
local hostname=$(hostname)
local timestamp=$(date)
echo "🚨 报警:$message"
# 发送邮件报警
cat << EOF | mail -s "PostgreSQL备份报警 - $hostname" "$ALERT_EMAIL"
PostgreSQL 备份系统报警
服务器: $hostname
时间: $timestamp
报警信息: $message
请及时检查备份系统状态。
详细信息:
$(df -h "$BACKUP_DIR")
$(ls -la "$BACKUP_DIR" | tail -5)
EOF
# 可选:发送到监控系统
# curl -X POST "http://monitoring.company.com/alert" \
# -d "source=postgresql_backup&message=$message&host=$hostname"
}
# 生成备份报告
generate_backup_report() {
local report_file="$BACKUP_DIR/backup_report_$(date +%Y%m%d).txt"
cat > "$report_file" << EOF
PostgreSQL 备份状态报告
生成时间:$(date)
服务器:$(hostname)
备份目录统计:
$(du -sh "$BACKUP_DIR"/* 2>/dev/null | sort -h)
最近5次备份:
$(ls -lt "$BACKUP_DIR"/*.tar.gz 2>/dev/null | head -5)
磁盘使用情况:
$(df -h "$BACKUP_DIR")
备份配置:
- 备份目录:$BACKUP_DIR
- 最大备份age:${MAX_BACKUP_AGE_HOURS}小时
- 报警邮箱:$ALERT_EMAIL
EOF
echo "备份报告已生成:$report_file"
}
# 主监控流程
main() {
echo "=== PostgreSQL 备份监控检查 ==="
echo "开始时间:$(date)"
local exit_code=0
check_latest_backup || exit_code=1
check_backup_integrity || exit_code=1
check_disk_space || exit_code=1
generate_backup_report
if [ $exit_code -eq 0 ]; then
echo "✓ 所有检查通过"
else
echo "❌ 发现问题,请查看上述报警信息"
fi
echo "结束时间:$(date)"
return $exit_code
}
# 执行监控
main
与 SQL 转储的对比分析
特性对比表
特性 | 文件系统备份 | SQL 转储 (pg_dump) |
---|---|---|
备份速度 | ⚡ 快速(直接文件复制) | 🐌 较慢(需要查询和格式化) |
恢复速度 | ⚡ 快速(文件解压) | 🐌 较慢(需要重新执行 SQL) |
备份大小 | 📦 较大(包含索引) | 💾 较小(仅数据和结构) |
停机时间 | ⏸️ 需要停机 | ✅ 在线备份 |
部分备份 | ❌ 不支持 | ✅ 支持单表/单库 |
跨版本兼容 | ❌ 版本敏感 | ✅ 版本兼容性好 |
一致性保证 | ⚠️ 需要特殊处理 | ✅ 事务一致性 |
使用场景建议
混合备份策略
bash
#!/bin/bash
# 混合备份策略脚本
DB_SIZE_THRESHOLD=50 # GB
BACKUP_DIR="/backup/mixed"
# 获取数据库大小
get_database_size() {
local size_bytes=$(sudo -u postgres psql -t -c "
SELECT pg_database_size(current_database());
" | tr -d ' ')
local size_gb=$((size_bytes / 1024 / 1024 / 1024))
echo $size_gb
}
# 选择备份策略
choose_backup_strategy() {
local db_size=$(get_database_size)
local current_hour=$(date +%H)
echo "当前数据库大小:${db_size}GB"
echo "当前时间:$(date)"
if [ $db_size -lt $DB_SIZE_THRESHOLD ]; then
echo "数据库较小,使用 SQL 转储备份"
perform_sql_dump
elif [ $current_hour -ge 2 ] && [ $current_hour -le 4 ]; then
echo "在维护时间窗口,使用文件系统备份"
perform_filesystem_backup
else
echo "数据库较大且不在维护窗口,使用在线 SQL 转储"
perform_sql_dump
fi
}
# SQL 转储备份
perform_sql_dump() {
local backup_file="$BACKUP_DIR/sql_dump_$(date +%Y%m%d_%H%M%S).sql.gz"
echo "开始 SQL 转储备份"
sudo -u postgres pg_dumpall | gzip > "$backup_file"
echo "SQL 转储完成:$backup_file"
}
# 文件系统备份
perform_filesystem_backup() {
local backup_file="$BACKUP_DIR/filesystem_$(date +%Y%m%d_%H%M%S).tar.gz"
echo "开始文件系统备份"
sudo systemctl stop postgresql
tar -czf "$backup_file" -C /var/lib/postgresql/14 main/
sudo systemctl start postgresql
echo "文件系统备份完成:$backup_file"
}
# 执行混合策略
mkdir -p "$BACKUP_DIR"
choose_backup_strategy
自动化与调度
Cron 任务配置
bash
# 编辑 crontab
sudo crontab -e
# 添加以下任务:
# 每日凌晨 2 点执行文件系统备份
0 2 * * * /opt/scripts/postgres_filesystem_backup.sh >> /var/log/postgres_backup.log 2>&1
# 每小时执行备份监控检查
0 * * * * /opt/scripts/backup_monitor.sh >> /var/log/backup_monitor.log 2>&1
# 每周日清理超过 30 天的备份
0 3 * * 0 find /backup/postgresql -type f -mtime +30 -delete
# 每月生成备份统计报告
0 1 1 * * /opt/scripts/backup_report.sh | mail -s "月度备份报告" [email protected]
Systemd 服务配置
创建系统服务以更好地管理备份任务:
bash
# 创建服务文件
sudo tee /etc/systemd/system/postgres-backup.service << 'EOF'
[Unit]
Description=PostgreSQL Filesystem Backup
After=postgresql.service
Requires=postgresql.service
[Service]
Type=oneshot
User=root
ExecStart=/opt/scripts/postgres_filesystem_backup.sh
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
# 创建定时器文件
sudo tee /etc/systemd/system/postgres-backup.timer << 'EOF'
[Unit]
Description=PostgreSQL Backup Timer
Requires=postgres-backup.service
[Timer]
OnCalendar=daily
Persistent=true
RandomizedDelaySec=300
[Install]
WantedBy=timers.target
EOF
# 启用并启动定时器
sudo systemctl daemon-reload
sudo systemctl enable postgres-backup.timer
sudo systemctl start postgres-backup.timer
# 查看定时器状态
sudo systemctl status postgres-backup.timer
sudo systemctl list-timers postgres-backup.timer
故障排除指南
常见问题与解决方案
1. 备份过程中服务无法停止
bash
# 问题诊断脚本
#!/bin/bash
check_postgres_processes() {
echo "检查 PostgreSQL 相关进程:"
ps aux | grep postgres | grep -v grep
echo -e "\n检查数据库连接:"
sudo -u postgres psql -c "
SELECT pid, usename, application_name, client_addr, state
FROM pg_stat_activity
WHERE state = 'active';
" 2>/dev/null || echo "无法连接到数据库"
}
force_stop_postgres() {
echo "强制停止 PostgreSQL:"
# 1. 尝试优雅停止
sudo systemctl stop postgresql
sleep 5
# 2. 检查是否还有进程
if pgrep -f postgres; then
echo "发现残留进程,发送 SIGTERM"
sudo pkill -TERM -f postgres
sleep 10
fi
# 3. 强制终止
if pgrep -f postgres; then
echo "强制终止所有 PostgreSQL 进程"
sudo pkill -9 -f postgres
sleep 3
fi
# 4. 清理共享内存
echo "清理共享内存段"
sudo ipcs -m | grep postgres | awk '{print $2}' | xargs -r sudo ipcrm -m
echo "PostgreSQL 停止完成"
}
check_postgres_processes
force_stop_postgres
2. 恢复后数据库无法启动
bash
# 恢复故障诊断脚本
#!/bin/bash
diagnose_recovery_failure() {
local data_dir="/var/lib/postgresql/14/main"
echo "=== PostgreSQL 恢复故障诊断 ==="
# 1. 检查文件权限
echo "检查文件权限:"
ls -la "$data_dir" | head -10
# 2. 检查关键文件
echo -e "\n检查关键文件:"
for file in postgresql.conf pg_hba.conf PG_VERSION; do
if [ -f "$data_dir/$file" ]; then
echo "✓ $file 存在"
else
echo "❌ $file 缺失"
fi
done
# 3. 检查数据目录结构
echo -e "\n检查目录结构:"
find "$data_dir" -maxdepth 2 -type d | sort
# 4. 检查日志文件
echo -e "\n最近的错误日志:"
journalctl -u postgresql --no-pager -n 20
# 5. 检查 PostgreSQL 版本兼容性
if [ -f "$data_dir/PG_VERSION" ]; then
local data_version=$(cat "$data_dir/PG_VERSION")
local server_version=$(postgres --version | awk '{print $3}' | cut -d. -f1)
echo -e "\n版本兼容性:"
echo "数据版本:$data_version"
echo "服务器版本:$server_version"
if [ "$data_version" != "$server_version" ]; then
echo "❌ 版本不匹配!需要升级或降级"
else
echo "✓ 版本匹配"
fi
fi
}
fix_common_issues() {
local data_dir="/var/lib/postgresql/14/main"
echo -e "\n=== 尝试修复常见问题 ==="
# 1. 修复文件权限
echo "修复文件权限..."
sudo chown -R postgres:postgres "$data_dir"
sudo chmod 700 "$data_dir"
# 2. 检查并修复配置文件
if [ -f "$data_dir/postgresql.conf.backup" ]; then
echo "发现配置备份,恢复配置文件..."
sudo cp "$data_dir/postgresql.conf.backup" "$data_dir/postgresql.conf"
fi
# 3. 清理锁文件
if [ -f "$data_dir/postmaster.pid" ]; then
echo "清理锁文件..."
sudo rm -f "$data_dir/postmaster.pid"
fi
# 4. 尝试单用户模式启动
echo "尝试单用户模式启动..."
sudo -u postgres postgres --single -D "$data_dir" template1 << 'EOF'
SELECT version();
\q
EOF
if [ $? -eq 0 ]; then
echo "✓ 单用户模式正常"
else
echo "❌ 单用户模式失败"
fi
}
diagnose_recovery_failure
fix_common_issues
3. WAL 日志恢复问题
bash
# WAL 恢复诊断脚本
#!/bin/bash
check_wal_recovery() {
local data_dir="/var/lib/postgresql/14/main"
echo "=== WAL 恢复状态检查 ==="
# 1. 检查 WAL 目录
echo "WAL 目录内容:"
ls -la "$data_dir/pg_wal/" | head -10
# 2. 检查控制文件
if command -v pg_controldata >/dev/null; then
echo -e "\n控制文件信息:"
sudo -u postgres pg_controldata "$data_dir" | grep -E "(state|Latest checkpoint|REDO)"
fi
# 3. 检查恢复配置
if [ -f "$data_dir/recovery.conf" ]; then
echo -e "\n恢复配置文件存在:"
cat "$data_dir/recovery.conf"
elif [ -f "$data_dir/postgresql.auto.conf" ]; then
echo -e "\n自动配置文件:"
grep -i recovery "$data_dir/postgresql.auto.conf"
fi
# 4. 尝试WAL重放
echo -e "\n启动 PostgreSQL 并监控恢复过程..."
sudo systemctl start postgresql &
# 监控日志中的恢复信息
timeout 30 sudo journalctl -u postgresql -f | while read line; do
echo "$line" | grep -E "(recovery|redo|checkpoint|consistent)"
done
}
check_wal_recovery
总结
文件系统级别备份是 PostgreSQL 备份策略中的重要组成部分,特别适用于大型数据库的快速备份与恢复场景。关键要点包括:
核心优势
- 高性能:直接文件操作,备份和恢复速度快
- 完整性:保留所有数据库对象和物理结构
- 适用性:适合大型数据库和灾难恢复场景
关键限制
- 停机要求:通常需要停止数据库服务
- 整体性:无法进行选择性备份
- 版本敏感:对 PostgreSQL 版本要求严格
最佳实践
- 合理选择:根据数据库大小和业务要求选择备份方式
- 监控自动化:建立完善的监控和报警机制
- 定期验证:定期测试备份文件的完整性和可恢复性
- 混合策略:结合 SQL 转储和文件系统备份的优势
通过合理规划和实施文件系统级别备份,可以为 PostgreSQL 数据库提供可靠的数据保护,确保业务连续性和数据安全。