Redis Sentinel 生产环境备份与恢复 SOP 操作流程

文档版本:V4.2
适用环境:Redis Sentinel 哨兵模式生产环境
最后更新:2026-02-06

1. 目的

本标准操作流程(SOP)规定了 Redis Sentinel 哨兵模式生产环境下的数据备份、配置备份、恢复及故障处理的标准操作方法,确保 Redis 集群数据的安全性完整性可恢复性,并规范告警通知流程。

2. 核心设计理念

本 SOP 基于以下核心原则设计:

  • 数据备份:采用 RDB(从节点) + AOF(主节点) 混合备份策略,兼顾备份效率与数据完整性。
  • 配置备份:同时备份 Redis 配置文件和 Sentinel 配置文件(含逻辑状态快照)。
  • 自动化告警:模块化支持邮件、飞书、钉钉、企微等多种告警通道,确保运维人员及时感知备份状态。
  • 脚本化操作:提供统一的 Bash 脚本 redis_sentinel_sop.sh,封装所有操作,降低人为失误。

3. 角色与职责

角色 职责
运维工程师 执行备份与恢复操作,配置告警,维护脚本,监控备份日志,处理故障。
DBA 审核备份策略,优化备份性能,制定数据保留策略,处理复杂恢复场景。

4. 前置条件与依赖

4.1 环境要求

  • 操作系统:CentOS 7.x / Ubuntu 20.04+
  • Redis 版本:>= 6.0
  • 哨兵模式:已配置并运行正常
  • 磁盘空间:备份目录预留足够空间(建议至少为当前数据集的 3 倍)

4.2 软件依赖

  • redis-cli, redis-server, redis-check-rdb, redis-check-aof (Redis 工具集)
  • mailx (邮件告警,可选)
  • curl (Webhook 告警)
  • gzip, gunzip (压缩工具)
  • scp, ssh (远程拷贝,需配置 SSH 免密登录)

4.3 权限要求

  • 执行脚本的用户需对 Redis 数据目录、备份目录有读写权限。
  • 执行恢复操作的用户需有 systemctl 管理 Redis 服务的权限。
  • SSH 用户需有远程目标主机的免密登录权限。

4.4 配置文件路径

  • Redis 配置文件:/datas/redis/conf/redis.conf
  • Sentinel 配置文件:/datas/redis/conf/sentinel.conf
  • Redis 数据目录:/datas/redis/data
  • 备份根目录:/datas/backup/redis

5. 核心概念说明

5.1 备份策略详解

备份类型 备份对象 备份源 备份方式 优势 局限性
RDB 数据集快照 最优从节点 通过 redis-cli --rdb 从远程拉取 文件小,恢复快,对主库性能影响小 可能丢失最后一次快照后的数据
AOF 写命令日志 主节点 通过 BGREWRITEAOF 触发后,通过 SSH/本地拷贝 数据完整性高,通常只丢失1秒数据 文件大,恢复慢,重写时消耗资源
Redis配置 redis.conf 本机 + 远程主节点 直接文件拷贝 (cp/scp) 保存运行参数、持久化策略、安全设置
Sentinel配置 sentinel.conf + 逻辑状态 本机 物理文件拷贝 + 逻辑状态导出 保存哨兵拓扑、监控主库信息、投票规则 逻辑状态快照仅用于审计排障

5.2 告警通道说明

  • 邮件 (mail):正式通知,适合记录存档。
  • 飞书/钉钉/企微 (webhook):即时告警,适合快速响应。需在脚本中预先配置正确的 Webhook 地址和关键词。

6. 操作流程

6.1 备份操作

6.1.1 定期全量备份(推荐)

目的:执行完整的数据和配置备份。
频率:每日一次(建议在业务低峰期,如凌晨 2:00)
操作命令

/root/redis_sentinel_sop.sh backup all

预期结果

  • 脚本将依次执行 RDB、AOF、Redis 配置、Sentinel 配置的备份。
  • 所有备份文件会被压缩 (.gz) 并存放在相应的子目录下。
  • 旧备份文件会根据保留策略自动清理(RDB/AOF: 7天, Redis 配置: 30天, Sentinel 配置: 30天)。
  • 成功或失败会触发告警通知。
    备份文件命名规范
# RDB (备份最优从节点)
redis_rdb_{slave_ip}_{timestamp}.rdb.gz
# AOF (备份主节点)
appendonly_{master_ip}_{timestamp}.aof.gz
# Redis 配置 (本地)
redis_local_{timestamp}.conf.gz
# Redis 配置 (远程主节点)
redis_master_{master_ip}_{timestamp}.conf.gz
# Sentinel 配置 (物理文件)
sentinel_conf_{timestamp}.conf.gz
# Sentinel 状态 (逻辑快照)
sentinel_state_{timestamp}.conf.gz
6.1.2 单独备份

目的:针对特定类型进行备份。
场景:需要快速备份某个特定组件,或作为排查问题的补充手段。
操作命令

# 仅备份 RDB
/root/redis_sentinel_sop.sh backup rdb
# 仅备份 AOF
/root/redis_sentinel_sop.sh backup aof
# 仅备份 Redis 配置
/root/redis_sentinel_sop.sh backup redis_config
# 仅备份 Sentinel 配置 (V4.2 新增,包含逻辑状态)
/root/redis_sentinel_sop.sh backup sentinel_config
# 备份所有配置 (Redis + Sentinel)
/root/redis_sentinel_sop.sh backup config

6.2 恢复操作

⚠️ 警告:恢复操作会覆盖现有数据,是一个高风险操作。执行前必须:

  1. 确认恢复目标环境正确(切勿在生产环境操作)。
  2. 确认要恢复的备份文件无误。
  3. 通知所有相关人员,获得明确批准。
  4. 在执行恢复操作前,务必对当前数据进行再次备份。
  5. 建议在测试环境验证恢复流程后,再在生产环境执行。
6.2.1 RDB 恢复

目的:从 RDB 快照恢复数据。
场景:数据误删除、需要将数据回滚到某个时间点、搭建从库。
操作步骤

  1. 停止服务
    systemctl stop redis
    
  2. 备份当前文件(关键安全步骤):
    mv /datas/redis/data/dump.rdb /datas/redis/data/dump.rdb.bak_$(date +%s)
    
  3. 执行恢复脚本
    /root/redis_sentinel_sop.sh restore rdb /path/to/your/backup/redis_rdb_xxx.rdb.gz
    
    脚本会自动解压、校验、覆盖文件。
  4. 启动服务并验证
    systemctl start redis
    # 等待几秒后检查
    redis-cli INFO server
    redis-cli DBSIZE
    
6.2.2 AOF 恢复

目的:从 AOF 日志恢复数据。
场景:RDB 文件损坏、需要尽可能少地丢失数据。
操作步骤

  1. 停止服务
    systemctl stop redis
    
  2. 备份当前文件(关键安全步骤):
    mv /datas/redis/data/appendonly.aof /datas/redis/data/appendonly.aof.bak_$(date +%s)
    
  3. 执行恢复脚本
    /root/redis_sentinel_sop.sh restore aof /path/to/your/backup/appendonly_xxx.aof.gz
    
    脚本会自动解压、校验、覆盖文件。
  4. 启动服务并验证
    systemctl start redis
    # 等待 Redis 加载 AOF 并可能进行 AOF 重写
    redis-cli INFO server
    redis-cli DBSIZE
    

6.3 故障处理流程

6.3.1 备份失败告警

现象:收到“Redis Backup Failed”或“Checksum Failed”告警。
排查步骤

  1. 查看日志
    tail -n 50 /var/log/redis_sentinel_sop.log
    
    查找具体错误信息(如 SSH 连接失败、Redis 命令执行失败、磁盘空间不足等)。
  2. 手动验证
    • 尝试手动执行失败的备份命令(如手动 redis-cli --rdb)。
    • 检查 Redis 哨兵状态是否正常 (redis-cli -p 26379 SENTINEL MASTERS)。
    • 检查网络连通性和 SSH 免密登录。
  3. 恢复服务
    • 修复问题后,立即执行一次手动全量备份
    • 如果是存储空间问题,及时清理旧备份或扩容。
6.3.2 恢复失败

现象:执行恢复操作后,Redis 服务无法启动或数据异常。
应急回滚步骤

  1. 立即停止服务
    systemctl stop redis
    
  2. 回滚文件:使用步骤 2 中备份的 .bak_ 文件恢复。
    cp /datas/redis/data/dump.rdb.bak_<timestamp> /datas/redis/data/dump.rdb
    # 或
    cp /datas/redis/data/appendonly.aof.bak_<timestamp> /datas/redis/data/appendonly.aof
    
  3. 启动服务
    systemctl start redis
    
  4. 深入排查:在测试环境分析备份文件为何无法恢复,必要时联系 Redis 技术支持。

7. 定期维护任务

  • 每周:检查备份日志,确认备份任务是否成功执行。检查备份目录磁盘使用率。
  • 每月:从备份文件中抽取一份,在测试环境进行恢复演练,验证备份数据的可用性。
  • 每季度:审查并优化备份保留策略。审核告警联系人列表是否有效。

8. 应急预案

8.1 主节点数据丢失且 RDB/AOF 均不可用

场景:极端灾难场景,主节点磁盘物理损坏,且无可用备份。
应对

  1. 尝试从从节点抢救:选择一个数据最全的从节点,将其提升为新的主节点(可通过 Sentinel 自动故障转移或手动 SLAVEOF NO ONE)。
  2. 数据损失评估:评估从节点与原主节点的数据差异,告知业务方可能的数据丢失范围。
  3. 重建集群:在新的主节点上重新配置其他从节点。

8.2 备份存储故障

场景:备份目录所在的磁盘故障,所有备份文件丢失。
应对

  1. 立即隔离故障磁盘,防止数据进一步损坏。
  2. 尝试数据恢复:使用专业数据恢复服务尝试从故障磁盘恢复数据。
  3. 重新部署备份:在新的磁盘上重新部署备份策略,并立即执行全量备份
  4. 加强监控:增加对备份目录磁盘健康度(如 SMART 信息)的监控。

9. 附录

9.1 脚本快速参考

# 备份操作
/root/redis_sentinel_sop.sh backup all           # 全量备份
/root/redis_sentinel_sop.sh backup rdb            # RDB 备份
/root/redis_sentinel_sop.sh backup aof            # AOF 备份
/root/redis_sentinel_sop.sh backup config         # 配置备份
# 恢复操作 (⚠️ 高风险)
/root/redis_sentinel_sop.sh restore rdb <file>    # RDB 恢复
/root/redis_sentinel_sop.sh restore aof <file>    # AOF 恢复

9.2 关键配置参数说明

参数 说明 建议值
BACKUP_ROOT_DIR 备份根目录 /datas/backup/redis
LOG_FILE 脚本日志路径 /var/log/redis_sentinel_sop.log
REDIS_DATA_DIR Redis 数据目录 /datas/redis/data
SENTINEL_HOST/PORT 哨兵连接信息 127.0.0.1 / 26379
MASTER_NAME 哨兵监控的主名称 mymaster
REDIS_PASS Redis 密码 (设置强密码)
ALERT_XXX_ENABLED 告警开关 true/false

9.3 常见问题 (FAQ)

Q1: 备份脚本执行很慢,如何优化?
A: 检查以下几点:

  • 网络带宽:主从节点间、本地与备份存储间的带宽是否充足。
  • 磁盘 I/O:Redis 数据目录和备份目录的磁盘 IOPS 是否饱和。
  • 从节点负载:备份 RDB 的从节点是否本身负载过高。
  • 优化建议:将备份目录放在独立的物理磁盘上;考虑使用 redis-cli --rdb--replica 选项(从从节点备份时效果更好)。
    Q2: 收到 “Checksum Failed” 告警怎么办?
    A: 这表示备份文件校验失败,文件可能损坏。立即执行
  1. 检查存储介质是否健康。
  2. 重新执行备份操作。
  3. 如果连续失败,可能需要排查网络传输问题或源 Redis 实例本身的数据问题。
    Q3: 如何修改备份保留时间?
    A: 编辑脚本 redis_sentinel_sop.sh,找到 find ... -mtime +N ... 命令,修改 +N 的值(N 为天数)。修改后重启定时任务(如果使用 cron)或下次执行时生效。
    Q4: 恢复后,数据量和备份前不一致?
    A: 这通常是正常的:
  • RDB:只包含备份时刻的数据快照,之后的写操作会丢失。
  • AOF:通常只丢失1秒的数据。如果差异巨大,请检查备份文件是否正确,或恢复过程中是否有错误。
    Q5: 可以同时开启 RDB 和 AOF 持久化吗?
    A: 可以,这也是生产环境的常见配置。Redis 启动时会优先使用 AOF 文件来恢复数据,因为它通常更完整。本 SOP 的备份策略也反映了这一点,同时备份两者以提供最大灵活性。

文档维护:本 SOP 由运维团队维护。任何环境变更、经验教训或流程改进都应及时更新到本文档中。

10. Redis Sentinel 生产环境备份与恢复 SOP 脚本

#!/bin/bash
######################################################################
# Redis Sentinel 生产环境备份与恢复 SOP 脚本 (V4.2 - Sentinel 备份增强)
#
# 核心设计理念:
# 1. 数据备份: RDB (从节点) + AOF (主节点/SSH)。
# 2. 配置备份: Redis 配置文件。
# 3. Sentinel 备份 (V4.2 新增):
#    - 物理文件: 备份 sentinel.conf (用于重建服务)。
#    - 逻辑状态: 备份 Master/Slave/Sentinel 拓扑及 Quorum (用于状态分析)。
# 4. 告警: 模块化支持邮件、飞书、钉钉、企微。
#
# 日期: 2024-06-18
######################################################################

# ==================== 1. 基础配置区域 ====================
# 哨兵连接配置
SENTINEL_HOST="127.0.0.1"
SENTINEL_PORT="26379"
SENTINEL_PASS=""
MASTER_NAME="mymaster"

# Redis 认证信息
REDIS_PASS="Redis@%202601"

# 本地备份服务器配置
BACKUP_ROOT_DIR="/datas/backup/redis"
LOG_FILE="/var/log/redis_sentinel_sop.log"

# Redis 数据节点配置
REDIS_DATA_DIR="/datas/redis/data"
REDIS_SYSTEMD_SERVICE="redis"
REDIS_USER="redis"

# SSH 远程配置
SSH_USER="root"
SSH_KEY_PATH="/root/.ssh/id_rsa"

# 配置文件路径
REDIS_CONFIG_FILE="/datas/redis/conf/redis.conf"
SENTINEL_CONFIG_FILE="/datas/redis/conf/sentinel.conf"

# ==================== 2. 告警模块配置 ====================
ALERT_MAIL_ENABLED=true
ALERT_FEISHU_ENABLED=true
ALERT_DINGTALK_ENABLED=false
ALERT_WECOM_ENABLED=false

MAIL_TO="ops@example.com"
FEISHU_WEBHOOK="https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxxx"
FEISHU_KEYWORD="[Redis告警]"
DINGTALK_WEBHOOK="https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxxx"
DINGTALK_KEYWORD="[Redis告警]"
WECOM_WEBHOOK="https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxxxxxxx"

# ==================== 参数解析 ====================
ACTION="$1"
BACKUP_TYPE="$2"
RESTORE_FILE="$3"

# ==================== 初始化 ====================
mkdir -p "$BACKUP_ROOT_DIR"/{rdb,aof,config,sentinel_files,sentinel_state}
mkdir -p "$(dirname "$LOG_FILE")"
touch "$LOG_FILE"

# 日志函数
log() {
    if [ "$1" == "-q" ]; then
        shift
        echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
    else
        echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
    fi
}

# ==================== 3. 告警模块 ====================

alert_send_mail() {
    local subject="$1"
    local body="$2"
    if [ "$ALERT_MAIL_ENABLED" != true ]; then return 0; fi
    if command -v mail &> /dev/null; then
        echo "$body" | mail -s "$subject" "$MAIL_TO"
        log "[MAIL] 邮件已发送"
    fi
}

alert_send_feishu() {
    local subject="$1"
    local body="$2"
    if [ "$ALERT_FEISHU_ENABLED" != true ] || [ -z "$FEISHU_WEBHOOK" ]; then return 0; fi
    local msg_content="${FEISHU_KEYWORD}\n\n**${subject}**\n\n${body}"
    curl -s -X POST "$FEISHU_WEBHOOK" -H 'Content-Type: application/json' -d "{\"msg_type\":\"text\",\"content\":{\"text\":\"${msg_content}\"}}" > /dev/null
    log "[FEISHU] 飞书已发送"
}

alert_send_dingtalk() {
    local subject="$1"
    local body="$2"
    if [ "$ALERT_DINGTALK_ENABLED" != true ] || [ -z "$DINGTALK_WEBHOOK" ]; then return 0; fi
    local msg_content="${DINGTALK_KEYWORD}\n\n${subject}\n\n${body}"
    curl -s -X POST "$DINGTALK_WEBHOOK" -H 'Content-Type: application/json' -d "{\"msgtype\":\"text\",\"text\":{\"content\":\"${msg_content}\"}}" > /dev/null
    log "[DINGTALK] 钉钉已发送"
}

alert_send_wecom() {
    local subject="$1"
    local body="$2"
    if [ "$ALERT_WECOM_ENABLED" != true ] || [ -z "$WECOM_WEBHOOK" ]; then return 0; fi
    local msg_content="${subject}\n\n${body}"
    curl -s -X POST "$WECOM_WEBHOOK" -H 'Content-Type: application/json' -d "{\"msgtype\":\"text\",\"text\":{\"content\":\"${msg_content}\"}}" > /dev/null
    log "[WECOM] 企业微信已发送"
}

send_alert() {
    local subject="$1"
    local body="$2"
    log "[ALERT] $subject"
    alert_send_mail "$subject" "$body" &
    alert_send_feishu "$subject" "$body" &
    alert_send_dingtalk "$subject" "$body" &
    alert_send_wecom "$subject" "$body" &
    wait
}

# 安全的 Redis CLI 执行
redis_cli_safe() {
    local host=$1 port=$2 pass=$3; shift 3
    local cmd="redis-cli -h $host -p $port"
    [ -n "$pass" ] && cmd="$cmd -a $pass --no-auth-warning"
    $cmd "$@"
}

# ==================== 4. 核心业务逻辑 ====================

get_current_node_ip() {
    ip addr show | grep -E 'inet\s' | grep -v '127.0.0.1' | awk '{print $2}' | cut -d/ -f1 | head -n 1
}

get_master_node() {
    local cmd="redis-cli -h $SENTINEL_HOST -p $SENTINEL_PORT"
    [ -n "$SENTINEL_PASS" ] && cmd="$cmd -a $SENTINEL_PASS --no-auth-warning"
    $cmd SENTINEL GET-MASTER-ADDR-BY-NAME $MASTER_NAME | tr '\n' ' ' | sed 's/ $//'
}

is_master_node() {
    local current_ip=$(get_current_node_ip)
    local master_info=$(get_master_node)
    local master_ip=$(echo "$master_info" | cut -d' ' -f1)
    [ "$current_ip" == "$master_ip" ]
}

get_slaves() {
    redis_cli_safe "$SENTINEL_HOST" "$SENTINEL_PORT" "$SENTINEL_PASS" SENTINEL SLAVES $MASTER_NAME 2>/dev/null | \
    grep -A 3 "ip" | grep -Ev "ip|port" |grep -v "^--$" | paste - -|awk '{print $1":"$2}'
}

select_best_slave() {
    local slaves=$(get_slaves)
    local best_slave=""
    local max_offset=-1
    if [ -z "$slaves" ]; then
        log "错误: 未发现任何 Slave 节点。"
        send_alert "Redis Backup Failed" "No available slaves."
        exit 1
    fi
    log -q "发现从节点列表: $slaves"
    for slave in $slaves; do
        local ip=$(echo $slave | cut -d: -f1)
        local port=$(echo $slave | cut -d: -f2)
        if redis-cli -h $ip -p $port -a "$REDIS_PASS" --no-auth-warning PING &> /dev/null; then
            local offset=$(redis-cli -h $ip -p $port -a "$REDIS_PASS" --no-auth-warning INFO replication | grep master_repl_offset | cut -d: -f2 | tr -d '\r')
            log -q "检查 Slave [$ip:$port] - Offset: $offset"
            if [ -n "$offset" ] && [ $offset -gt $max_offset ]; then
                max_offset=$offset
                best_slave="$ip:$port"
            fi
        fi
    done
    echo "$best_slave"
}

# ==================== 5. 备份逻辑 (核心重构) ====================

backup_rdb() {
    log "========== 开始 RDB 备份 =========="
    local best_slave=$(select_best_slave)
    local slave_ip=$(echo "$best_slave" | cut -d: -f1)
    local slave_port=$(echo "$best_slave" | cut -d: -f2)
    local timestamp=$(date +%Y%m%d_%H%M%S)
    local backup_file="${BACKUP_ROOT_DIR}/rdb/redis_rdb_${slave_ip}_${timestamp}.rdb"
    
    log "正在从 $slave_ip:$slave_port 拉取数据到 $backup_file ..."
    if redis-cli -h "$slave_ip" -p "$slave_port" -a "$REDIS_PASS" --rdb "$backup_file" --no-auth-warning; then
        log "RDB 拉取完成。校验中..."
        if command -v redis-check-rdb &> /dev/null; then
            if redis-check-rdb "$backup_file" 2>/dev/null | grep -q "Checksum OK"; then
                log "[SUCCESS] RDB 校验通过。"
            else
                log "[ERROR] RDB 校验失败。"
                send_alert "Redis Checksum Failed" "RDB file corrupted."
                exit 1
            fi
        fi
        gzip -f "$backup_file"
        log "RDB 备份完成: ${backup_file}.gz"
        find "$BACKUP_ROOT_DIR/rdb" -name "redis_rdb_*.rdb.gz" -mtime +7 -exec rm -f {} \;
    else
        log "[ERROR] RDB 拉取失败。"
        send_alert "Redis Backup Failed" "Failed to pull RDB."
        exit 1
    fi
}

backup_aof() {
    log "========== 开始 AOF 备份 =========="
    
    local master_info=$(get_master_node)
    local master_ip=$(echo "$master_info" | cut -d' ' -f1)
    local master_port=$(echo "$master_info" | cut -d' ' -f2)
    
    if [ -z "$master_ip" ]; then
        log "错误: 无法从哨兵获取主节点 IP。"
        send_alert "Redis AOF Backup Failed" "Cannot get master node."
        exit 1
    fi
    
    log "目标主节点: $master_ip:$master_port"
    
    log "正在触发主节点 AOF 重写..."
    redis-cli -h "$master_ip" -p "$master_port" -a "$REDIS_PASS" --no-auth-warning BGREWRITEAOF &> /dev/null
    
    log "等待 AOF 重写完成..."
    local wait_count=0
    while [ $wait_count -lt 20 ]; do
        local bg_rewrite_status=$(redis-cli -h "$master_ip" -p "$master_port" -a "$REDIS_PASS" --no-auth-warning INFO persistence | grep aof_rewrite_in_progress | cut -d: -f2 | tr -d '\r')
        if [ "$bg_rewrite_status" == "0" ]; then break; fi
        sleep 1
        wait_count=$((wait_count+1))
    done
    
    local timestamp=$(date +%Y%m%d_%H%M%S)
    local local_backup_file="${BACKUP_ROOT_DIR}/aof/appendonly_${master_ip}_${timestamp}.aof"
    local remote_aof_path="${REDIS_DATA_DIR}/appendonly.aof"
    
    if is_master_node; then
        log "检测到当前即为主节点,执行本地拷贝..."
        cp "$remote_aof_path" "$local_backup_file"
    else
        log "检测到当前非主节点,尝试通过 SSH 远程拷贝..."
        if [ ! -f "$SSH_KEY_PATH" ]; then
            log "错误: 未找到 SSH 私钥。"
            send_alert "SSH Connection Failed" "SSH Key not found."
            exit 1
        fi
        log "正在从 $master_ip 拉取 AOF 文件..."
        scp -i "$SSH_KEY_PATH" -o StrictHostKeyChecking=no ${SSH_USER}@${master_ip}:${remote_aof_path} "$local_backup_file"
        
        if [ $? -ne 0 ]; then
            log "错误: SCP 拉取 AOF 文件失败。"
            send_alert "SCP Transfer Failed" "Failed to pull AOF from $master_ip"
            exit 1
        fi
    fi
    
    if [ -f "$local_backup_file" ]; then
        log "备份文件大小: $(du -h "$local_backup_file" | cut -f1)"
        gzip -f "$local_backup_file"
        log "AOF 备份完成: ${local_backup_file}.gz"
        find "$BACKUP_ROOT_DIR/aof" -name "appendonly_*.aof.gz" -mtime +7 -exec rm -f {} \;
    fi
}


backup_redis_config() {
    log "========== 开始 Redis 配置备份 =========="
    local timestamp=$(date +%Y%m%d_%H%M%S)
    local local_backup_file="${BACKUP_ROOT_DIR}/config/redis_local_${timestamp}.conf"
    # 1. 本地备份
    if [ -f "$REDIS_CONFIG_FILE" ]; then
        log "[物理] 备份配置文件: $REDIS_CONFIG_FILE"
        cp "$REDIS_CONFIG_FILE" "${BACKUP_ROOT_DIR}/config/redis_local_${timestamp}.conf"
        gzip -f "$local_backup_file"
        log "Redis 配置备份完成: ${local_backup_file}.gz"
    fi

    # 2. 远程主节点备份
    local master_info=$(get_master_node)
    local master_ip=$(echo "$master_info" | cut -d' ' -f1)
    local remote_backup_file="${BACKUP_ROOT_DIR}/config/redis_master_${master_ip}_${timestamp}.conf"
    if [ -n "$master_ip" ] && [ ! "$master_ip" == "$(get_current_node_ip)" ]; then
        scp -i "$SSH_KEY_PATH" -o StrictHostKeyChecking=no ${SSH_USER}@${master_ip}:${REDIS_CONFIG_FILE} "$remote_backup_file" 2>/dev/null
        if [ $? -eq 0 ]; then
             gzip -f "$remote_backup_file"
             log "Redis 配置备份完成: ${remote_backup_file}.gz"
        fi
    fi
    
    find "$BACKUP_ROOT_DIR/config" -name "redis_*.conf.gz" -mtime +30 -exec rm -f {} \;
}

# ==================== 6. Sentinel 备份 (V4.2 合并版) ====================

backup_sentinel_config() {
    log "========== 开始 Sentinel 双重备份 =========="
    local timestamp=$(date +%Y%m%d_%H%M%S)

    # --- 1. 备份物理配置文件 (用于重建) ---
    if [ -f "$SENTINEL_CONFIG_FILE" ]; then
        local file_backup="${BACKUP_ROOT_DIR}/sentinel/sentinel_conf_${timestamp}.conf"
        log "[物理] 备份配置文件: $SENTINEL_CONFIG_FILE"
        cp "$SENTINEL_CONFIG_FILE" "$file_backup"
        gzip -f "$file_backup"
        log "Sentinel 配置备份完成: $file_backup"
    else
        log "[物理] 警告: 本地未找到 Sentinel 配置文件 $SENTINEL_CONFIG_FILE"
    fi

    # --- 2. 备份逻辑状态快照 (用于排障/审计) ---
    local state_backup="${BACKUP_ROOT_DIR}/sentinel/sentinel_state_${timestamp}.conf"
    log "[逻辑] 生成状态快照..."
    
    {
        echo "# Sentinel Logic State Snapshot - $(date)"
        echo "# Master Name: $MASTER_NAME"
        echo ""
        
        echo "## [MASTER]"
        redis_cli_safe "$SENTINEL_HOST" "$SENTINEL_PORT" "$SENTINEL_PASS" SENTINEL MASTER "$MASTER_NAME"
        echo ""
        
        echo "## [SLAVES]"
        redis_cli_safe "$SENTINEL_HOST" "$SENTINEL_PORT" "$SENTINEL_PASS" SENTINEL SLAVES "$MASTER_NAME"
        echo ""
        
        echo "## [SENTINELS]"
        redis_cli_safe "$SENTINEL_HOST" "$SENTINEL_PORT" "$SENTINEL_PASS" SENTINEL SENTINELS "$MASTER_NAME"
        echo ""
        
        echo "## [CKQUORUM]"
        redis_cli_safe "$SENTINEL_HOST" "$SENTINEL_PORT" "$SENTINEL_PASS" SENTINEL CKQUORUM "$MASTER_NAME"
    } > "$state_backup"
    
    gzip -f "$state_backup"
    log "Sentinel 配置备份状态完成: ${state_backup}.gz"
    
    # 清理旧备份 (配置文件保留30天,状态快照保留7天)
    find "$BACKUP_ROOT_DIR/sentinel_files" -name "sentinel_conf_*.conf.gz" -mtime +30 -exec rm -f {} \;
    find "$BACKUP_ROOT_DIR/sentinel_state" -name "sentinel_state_*.conf.gz" -mtime +7 -exec rm -f {} \;
    
    log "Sentinel 双重备份完成。"
}

# ==================== 7. 恢复逻辑 ====================

restore_rdb() {
    log "========== 开始 RDB 恢复 =========="
    [ -z "$RESTORE_FILE" ] && log "错误: 缺少文件路径。" && exit 1
    [ ! -f "$RESTORE_FILE" ] && log "错误: 文件不存在。" && exit 1
    
    local temp_file="/tmp/redis_restore_temp.rdb"
    if [[ "$RESTORE_FILE" == *.gz ]]; then gunzip -c "$RESTORE_FILE" > "$temp_file" && RESTORE_FILE="$temp_file"; fi
    
    if ! redis-check-rdb "$RESTORE_FILE" 2>/dev/null | grep -q "Checksum OK"; then
        log "[ERROR] RDB 校验失败。"; send_alert "Restore Failed" "RDB Checksum error."; exit 1
    fi

    echo "警告: 将覆盖现有数据,输入 yes 确认:"; read confirm
    [ "$confirm" != "yes" ] && exit 0

    systemctl stop $REDIS_SYSTEMD_SERVICE
    mv "${REDIS_DATA_DIR}/dump.rdb" "${REDIS_DATA_DIR}/dump.rdb.bak_$(date +%s)" 2>/dev/null
    cp "$RESTORE_FILE" "${REDIS_DATA_DIR}/dump.rdb"
    chown $REDIS_USER:$REDIS_USER "${REDIS_DATA_DIR}/dump.rdb"
    systemctl start $REDIS_SYSTEMD_SERVICE
    log "RDB 恢复完成。"
}

restore_aof() {
    log "========== 开始 AOF 恢复 =========="
    [ -z "$RESTORE_FILE" ] && log "错误: 缺少文件路径。" && exit 1
    [ ! -f "$RESTORE_FILE" ] && log "错误: 文件不存在。" && exit 1
    
    local temp_file="/tmp/redis_restore_temp.aof"
    if [[ "$RESTORE_FILE" == *.gz ]]; then gunzip -c "$RESTORE_FILE" > "$temp_file" && RESTORE_FILE="$temp_file"; fi
    
    if ! redis-check-aof "$RESTORE_FILE" 2>/dev/null | grep -q "OK"; then
        log "[ERROR] AOF 校验失败。"; send_alert "Restore Failed" "AOF Checksum error."; exit 1
    fi

    echo "警告: 将覆盖现有 AOF,输入 yes 确认:"; read confirm
    [ "$confirm" != "yes" ] && exit 0

    systemctl stop $REDIS_SYSTEMD_SERVICE
    mv "${REDIS_DATA_DIR}/appendonly.aof" "${REDIS_DATA_DIR}/appendonly.aof.bak_$(date +%s)" 2>/dev/null
    cp "$RESTORE_FILE" "${REDIS_DATA_DIR}/appendonly.aof"
    chown $REDIS_USER:$REDIS_USER "${REDIS_DATA_DIR}/appendonly.aof"
    systemctl start $REDIS_SYSTEMD_SERVICE
    log "AOF 恢复完成。"
}

# ==================== 8. 主流程 ====================

case "$ACTION" in
    backup)
        case "$BACKUP_TYPE" in
            rdb) backup_rdb ;;
            aof) backup_aof ;;
            redis_config) backup_redis_config ;;
            sentinel_config) backup_sentinel_config ;;
            config)
                backup_redis_config
                backup_sentinel_config
                ;;
            all)
                backup_rdb
                backup_aof
                backup_redis_config
                backup_sentinel_config
                ;;
            *)
                echo "用法: $0 backup [rdb|aof|redis_config|sentinel_config|config|all]"
                exit 1
                ;;
        esac
        ;;
    restore)
        case "$BACKUP_TYPE" in
            rdb) restore_rdb ;;
            aof) restore_aof ;;
            *) echo "用法: $0 restore [rdb|aof] <file>" ;;
        esac
        ;;
    *)
        echo "Redis Sentinel SOP Script V4.2 (Enhanced Sentinel Backup)"
        echo "================================================================"
        echo "备份命令:"
        echo "  $0 backup rdb             # 数据: RDB"
        echo "  $0 backup aof             # 数据: AOF"
        echo "  $0 backup redis_config    # 配置: Redis.conf"
        echo "  $0 backup sentinel_config # 配置: Sentinel.conf + 状态快照"
        echo "  $0 backup config          # 配置: 所有配置"
        echo "  $0 backup all             # 全量: 数据 + 配置"
        echo "恢复命令:"
        echo "  $0 restore rdb            # 数据: RDB"
        echo "  $0 restore aof            # 数据: AOF"
        exit 1
        ;;
esac


Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐