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

626 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 第十二章Redis 运维管理
## 概述
Redis运维管理是确保Redis服务稳定运行的关键环节。本章将深入介绍Redis的日常运维管理包括监控告警、备份恢复、故障处理、容量规划等核心内容帮助运维人员建立完善的Redis运维体系。
### 学习目标
- 掌握Redis监控指标和告警策略
- 学会Redis备份恢复的最佳实践
- 了解Redis故障诊断和处理方法
- 掌握Redis容量规划和扩容策略
- 学会Redis运维自动化工具的使用
## 监控告警
### 核心监控指标
**性能指标**
```shell
# 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
```
**系统指标**
```shell
# 系统资源监控
# 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 工具`
## 备份恢复
### 备份策略
**自动备份脚本**
```shell
#!/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 $@
```
### 恢复策略
**数据恢复脚本**
```shell
#!/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 $@
```
## 故障处理
### 常见故障诊断
**故障诊断脚本** `待补充`
### 故障处理手册
**常见故障及解决方案**
```shell
# 网络问题:
- 客户端无法连接Redis
- 连接超时
- 连接被拒绝
# 内存问题:
- 内存使用率过高
- OOM错误
- 性能下降
# 性能问题
- 响应时间慢
- QPS下降
- 慢查询增多
# 持久化问题
- RDB保存失败
- AOF文件损坏
- 数据丢失
# 主从复制问题
- 主从同步失败
- 复制延迟过大
- 从节点数据不一致
```
## 容量规划
### 容量评估脚本
**Redis容量分析工具**`参考 redis_capacity_planner.py 工具`