Files
Cloud-book/数据库/Redis_2025/12_Redis运维管理.md
2025-08-27 17:10:05 +08:00

16 KiB
Raw Blame History

第十二章Redis 运维管理

概述

Redis运维管理是确保Redis服务稳定运行的关键环节。本章将深入介绍Redis的日常运维管理包括监控告警、备份恢复、故障处理、容量规划等核心内容帮助运维人员建立完善的Redis运维体系。

学习目标

  • 掌握Redis监控指标和告警策略
  • 学会Redis备份恢复的最佳实践
  • 了解Redis故障诊断和处理方法
  • 掌握Redis容量规划和扩容策略
  • 学会Redis运维自动化工具的使用

监控告警

核心监控指标

性能指标

# Redis性能监控指标
# 1. 连接数
redis-cli info clients | grep connected_clients

# 2. 内存使用
redis-cli info memory | grep used_memory_human

# 3. 命令执行统计
redis-cli info commandstats

# 4. 键空间统计
redis-cli info keyspace

# 5. 复制延迟
redis-cli info replication | grep master_repl_offset

# 6. 慢查询
redis-cli slowlog get 10

系统指标

# 系统资源监控
# CPU使用率
top -p $(pgrep redis-server)

# 内存使用
ps aux | grep redis-server

# 网络连接
netstat -an | grep :6379

# 磁盘I/O
iostat -x 1

# 文件描述符
lsof -p $(pgrep redis-server) | wc -l

监控脚本实现

Redis监控脚本参考 redis_monitor.py 工具

告警配置

告警规则配置 参考 redis_alerts.yml 工具

告警处理脚本 参考 redis_alert_handler.py 工具

备份恢复

备份策略

自动备份脚本

#!/bin/bash
# redis_backup.sh - Redis自动备份脚本

# 配置参数
REDIS_HOST="localhost"
REDIS_PORT="6379"
REDIS_PASSWORD=""
BACKUP_DIR="/data/redis_backup"
RETENTION_DAYS=7
LOG_FILE="/var/log/redis_backup.log"

# 日志函数
log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a $LOG_FILE
}

# 创建备份目录
mkdir -p $BACKUP_DIR

# 备份函数
backup_redis() {
    local backup_type=$1
    local timestamp=$(date '+%Y%m%d_%H%M%S')
    local backup_name="redis_${backup_type}_${timestamp}"
    local backup_path="$BACKUP_DIR/$backup_name"
    
    log "开始 $backup_type 备份: $backup_name"
    
    case $backup_type in
        "rdb")
            # RDB备份
            if [ -n "$REDIS_PASSWORD" ]; then
                redis-cli -h $REDIS_HOST -p $REDIS_PORT -a $REDIS_PASSWORD BGSAVE
            else
                redis-cli -h $REDIS_HOST -p $REDIS_PORT BGSAVE
            fi
            
            # 等待备份完成
            while true; do
                if [ -n "$REDIS_PASSWORD" ]; then
                    result=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT -a $REDIS_PASSWORD LASTSAVE)
                else
                    result=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT LASTSAVE)
                fi
                
                if [ "$result" != "$last_save" ]; then
                    break
                fi
                sleep 1
            done
            
            # 复制RDB文件
            redis_data_dir=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT CONFIG GET dir | tail -1)
            rdb_filename=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT CONFIG GET dbfilename | tail -1)
            
            if [ -f "$redis_data_dir/$rdb_filename" ]; then
                cp "$redis_data_dir/$rdb_filename" "$backup_path.rdb"
                log "RDB备份完成: $backup_path.rdb"
            else
                log "错误: RDB文件不存在"
                return 1
            fi
            ;;
        
        "aof")
            # AOF备份
            redis_data_dir=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT CONFIG GET dir | tail -1)
            aof_filename=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT CONFIG GET appendfilename | tail -1)
            
            if [ -f "$redis_data_dir/$aof_filename" ]; then
                cp "$redis_data_dir/$aof_filename" "$backup_path.aof"
                log "AOF备份完成: $backup_path.aof"
            else
                log "警告: AOF文件不存在"
            fi
            ;;
        
        "full")
            # 全量备份(包含配置文件)
            mkdir -p "$backup_path"
            
            # 备份RDB
            backup_redis "rdb"
            if [ $? -eq 0 ]; then
                mv "$BACKUP_DIR/redis_rdb_"*.rdb "$backup_path/"
            fi
            
            # 备份AOF
            backup_redis "aof"
            if [ $? -eq 0 ]; then
                mv "$BACKUP_DIR/redis_aof_"*.aof "$backup_path/"
            fi
            
            # 备份配置文件
            redis_config=$(ps aux | grep redis-server | grep -v grep | awk '{for(i=1;i<=NF;i++) if($i ~ /\.conf$/) print $i}')
            if [ -n "$redis_config" ] && [ -f "$redis_config" ]; then
                cp "$redis_config" "$backup_path/redis.conf"
                log "配置文件备份完成: $backup_path/redis.conf"
            fi
            
            # 创建备份信息文件
            cat > "$backup_path/backup_info.txt" << EOF
备份时间: $(date)
备份类型: 全量备份
Redis版本: $(redis-cli -h $REDIS_HOST -p $REDIS_PORT INFO server | grep redis_version | cut -d: -f2 | tr -d '\r')
数据库大小: $(redis-cli -h $REDIS_HOST -p $REDIS_PORT DBSIZE)
内存使用: $(redis-cli -h $REDIS_HOST -p $REDIS_PORT INFO memory | grep used_memory_human | cut -d: -f2 | tr -d '\r')
EOF
            
            # 压缩备份
            cd $BACKUP_DIR
            tar -czf "${backup_name}.tar.gz" "$backup_name"
            rm -rf "$backup_name"
            
            log "全量备份完成: ${backup_path}.tar.gz"
            ;;
    esac
}

# 清理过期备份
cleanup_old_backups() {
    log "清理 $RETENTION_DAYS 天前的备份文件"
    find $BACKUP_DIR -name "redis_*" -type f -mtime +$RETENTION_DAYS -delete
    log "清理完成"
}

# 验证备份
verify_backup() {
    local backup_file=$1
    
    if [ ! -f "$backup_file" ]; then
        log "错误: 备份文件不存在: $backup_file"
        return 1
    fi
    
    local file_size=$(stat -c%s "$backup_file")
    if [ $file_size -eq 0 ]; then
        log "错误: 备份文件为空: $backup_file"
        return 1
    fi
    
    log "备份验证通过: $backup_file (大小: $file_size 字节)"
    return 0
}

# 主函数
main() {
    local backup_type=${1:-"full"}
    
    log "=== Redis备份开始 ==="
    log "备份类型: $backup_type"
    
    # 检查Redis连接
    if ! redis-cli -h $REDIS_HOST -p $REDIS_PORT ping > /dev/null 2>&1; then
        log "错误: 无法连接到Redis服务器"
        exit 1
    fi
    
    # 执行备份
    backup_redis $backup_type
    
    # 验证备份
    latest_backup=$(ls -t $BACKUP_DIR/redis_${backup_type}_* 2>/dev/null | head -1)
    if [ -n "$latest_backup" ]; then
        verify_backup "$latest_backup"
    fi
    
    # 清理过期备份
    cleanup_old_backups
    
    log "=== Redis备份完成 ==="
}

# 执行主函数
main $@

恢复策略

数据恢复脚本

#!/bin/bash
# redis_restore.sh - Redis数据恢复脚本

# 配置参数
REDIS_HOST="localhost"
REDIS_PORT="6379"
REDIS_PASSWORD=""
BACKUP_DIR="/data/redis_backup"
LOG_FILE="/var/log/redis_restore.log"

# 日志函数
log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a $LOG_FILE
}

# 停止Redis服务
stop_redis() {
    log "停止Redis服务"
    
    # 尝试优雅关闭
    if [ -n "$REDIS_PASSWORD" ]; then
        redis-cli -h $REDIS_HOST -p $REDIS_PORT -a $REDIS_PASSWORD SHUTDOWN SAVE
    else
        redis-cli -h $REDIS_HOST -p $REDIS_PORT SHUTDOWN SAVE
    fi
    
    # 等待进程结束
    sleep 5
    
    # 强制杀死进程(如果仍在运行)
    pkill -f redis-server
    
    log "Redis服务已停止"
}

# 启动Redis服务
start_redis() {
    log "启动Redis服务"
    
    # 查找Redis配置文件
    local config_file="/etc/redis/redis.conf"
    if [ ! -f "$config_file" ]; then
        config_file="/usr/local/etc/redis.conf"
    fi
    
    if [ -f "$config_file" ]; then
        redis-server "$config_file" &
    else
        redis-server &
    fi
    
    # 等待服务启动
    local retry_count=0
    while [ $retry_count -lt 30 ]; do
        if redis-cli -h $REDIS_HOST -p $REDIS_PORT ping > /dev/null 2>&1; then
            log "Redis服务启动成功"
            return 0
        fi
        sleep 1
        retry_count=$((retry_count + 1))
    done
    
    log "错误: Redis服务启动失败"
    return 1
}

# 恢复RDB文件
restore_rdb() {
    local rdb_file=$1
    
    if [ ! -f "$rdb_file" ]; then
        log "错误: RDB文件不存在: $rdb_file"
        return 1
    fi
    
    log "恢复RDB文件: $rdb_file"
    
    # 获取Redis数据目录
    local redis_data_dir=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT CONFIG GET dir 2>/dev/null | tail -1)
    if [ -z "$redis_data_dir" ]; then
        redis_data_dir="/var/lib/redis"
    fi
    
    local rdb_filename=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT CONFIG GET dbfilename 2>/dev/null | tail -1)
    if [ -z "$rdb_filename" ]; then
        rdb_filename="dump.rdb"
    fi
    
    # 停止Redis
    stop_redis
    
    # 备份现有RDB文件
    if [ -f "$redis_data_dir/$rdb_filename" ]; then
        mv "$redis_data_dir/$rdb_filename" "$redis_data_dir/${rdb_filename}.backup.$(date +%s)"
        log "现有RDB文件已备份"
    fi
    
    # 复制新的RDB文件
    cp "$rdb_file" "$redis_data_dir/$rdb_filename"
    chown redis:redis "$redis_data_dir/$rdb_filename" 2>/dev/null
    
    # 启动Redis
    start_redis
    
    if [ $? -eq 0 ]; then
        log "RDB恢复完成"
        return 0
    else
        log "错误: RDB恢复失败"
        return 1
    fi
}

# 恢复AOF文件
restore_aof() {
    local aof_file=$1
    
    if [ ! -f "$aof_file" ]; then
        log "错误: AOF文件不存在: $aof_file"
        return 1
    fi
    
    log "恢复AOF文件: $aof_file"
    
    # 验证AOF文件
    redis-check-aof --fix "$aof_file"
    if [ $? -ne 0 ]; then
        log "警告: AOF文件可能有问题已尝试修复"
    fi
    
    # 获取Redis数据目录
    local redis_data_dir=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT CONFIG GET dir 2>/dev/null | tail -1)
    if [ -z "$redis_data_dir" ]; then
        redis_data_dir="/var/lib/redis"
    fi
    
    local aof_filename=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT CONFIG GET appendfilename 2>/dev/null | tail -1)
    if [ -z "$aof_filename" ]; then
        aof_filename="appendonly.aof"
    fi
    
    # 停止Redis
    stop_redis
    
    # 备份现有AOF文件
    if [ -f "$redis_data_dir/$aof_filename" ]; then
        mv "$redis_data_dir/$aof_filename" "$redis_data_dir/${aof_filename}.backup.$(date +%s)"
        log "现有AOF文件已备份"
    fi
    
    # 复制新的AOF文件
    cp "$aof_file" "$redis_data_dir/$aof_filename"
    chown redis:redis "$redis_data_dir/$aof_filename" 2>/dev/null
    
    # 启动Redis
    start_redis
    
    if [ $? -eq 0 ]; then
        log "AOF恢复完成"
        return 0
    else
        log "错误: AOF恢复失败"
        return 1
    fi
}

# 恢复全量备份
restore_full() {
    local backup_file=$1
    
    if [ ! -f "$backup_file" ]; then
        log "错误: 备份文件不存在: $backup_file"
        return 1
    fi
    
    log "恢复全量备份: $backup_file"
    
    # 创建临时目录
    local temp_dir="/tmp/redis_restore_$(date +%s)"
    mkdir -p "$temp_dir"
    
    # 解压备份文件
    if [[ "$backup_file" == *.tar.gz ]]; then
        tar -xzf "$backup_file" -C "$temp_dir"
    elif [[ "$backup_file" == *.zip ]]; then
        unzip "$backup_file" -d "$temp_dir"
    else
        log "错误: 不支持的备份文件格式"
        return 1
    fi
    
    # 查找解压后的目录
    local extract_dir=$(find "$temp_dir" -maxdepth 1 -type d | grep -v "^$temp_dir$" | head -1)
    if [ -z "$extract_dir" ]; then
        extract_dir="$temp_dir"
    fi
    
    # 恢复配置文件
    if [ -f "$extract_dir/redis.conf" ]; then
        log "发现配置文件,请手动检查是否需要恢复"
        log "配置文件位置: $extract_dir/redis.conf"
    fi
    
    # 恢复数据文件
    local rdb_file=$(find "$extract_dir" -name "*.rdb" | head -1)
    local aof_file=$(find "$extract_dir" -name "*.aof" | head -1)
    
    if [ -n "$rdb_file" ]; then
        restore_rdb "$rdb_file"
    elif [ -n "$aof_file" ]; then
        restore_aof "$aof_file"
    else
        log "错误: 未找到数据文件"
        rm -rf "$temp_dir"
        return 1
    fi
    
    # 清理临时文件
    rm -rf "$temp_dir"
    
    log "全量恢复完成"
}

# 列出可用备份
list_backups() {
    log "可用的备份文件:"
    
    if [ ! -d "$BACKUP_DIR" ]; then
        log "备份目录不存在: $BACKUP_DIR"
        return 1
    fi
    
    local backup_files=$(find "$BACKUP_DIR" -name "redis_*" -type f | sort -r)
    
    if [ -z "$backup_files" ]; then
        log "未找到备份文件"
        return 1
    fi
    
    local index=1
    echo "$backup_files" | while read file; do
        local size=$(stat -c%s "$file" 2>/dev/null || echo "unknown")
        local date=$(stat -c%y "$file" 2>/dev/null | cut -d' ' -f1,2 | cut -d'.' -f1)
        printf "%2d. %s (大小: %s, 日期: %s)\n" $index "$(basename "$file")" "$size" "$date"
        index=$((index + 1))
    done
}

# 验证恢复结果
verify_restore() {
    log "验证恢复结果"
    
    # 检查Redis连接
    if ! redis-cli -h $REDIS_HOST -p $REDIS_PORT ping > /dev/null 2>&1; then
        log "错误: Redis服务未正常运行"
        return 1
    fi
    
    # 获取基本信息
    local db_size=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT DBSIZE)
    local memory_usage=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT INFO memory | grep used_memory_human | cut -d: -f2 | tr -d '\r')
    
    log "恢复验证结果:"
    log "- 数据库大小: $db_size 个键"
    log "- 内存使用: $memory_usage"
    log "- Redis状态: 正常"
    
    return 0
}

# 主函数
main() {
    local action=$1
    local backup_file=$2
    
    log "=== Redis恢复开始 ==="
    
    case $action in
        "list")
            list_backups
            ;;
        "rdb")
            if [ -z "$backup_file" ]; then
                log "错误: 请指定RDB备份文件"
                exit 1
            fi
            restore_rdb "$backup_file"
            verify_restore
            ;;
        "aof")
            if [ -z "$backup_file" ]; then
                log "错误: 请指定AOF备份文件"
                exit 1
            fi
            restore_aof "$backup_file"
            verify_restore
            ;;
        "full")
            if [ -z "$backup_file" ]; then
                log "错误: 请指定全量备份文件"
                exit 1
            fi
            restore_full "$backup_file"
            verify_restore
            ;;
        *)
            echo "用法: $0 {list|rdb|aof|full} [backup_file]"
            echo "  list - 列出可用备份"
            echo "  rdb  - 恢复RDB备份"
            echo "  aof  - 恢复AOF备份"
            echo "  full - 恢复全量备份"
            exit 1
            ;;
    esac
    
    log "=== Redis恢复完成 ==="
}

# 执行主函数
main $@

故障处理

常见故障诊断

故障诊断脚本 待补充

故障处理手册

常见故障及解决方案

# 网络问题:
- 客户端无法连接Redis
- 连接超时
- 连接被拒绝

# 内存问题:
- 内存使用率过高
- OOM错误
- 性能下降

# 性能问题
- 响应时间慢
- QPS下降
- 慢查询增多

# 持久化问题
- RDB保存失败
- AOF文件损坏
- 数据丢失

# 主从复制问题
- 主从同步失败
- 复制延迟过大
- 从节点数据不一致

容量规划

容量评估脚本

Redis容量分析工具参考 redis_capacity_planner.py 工具