08-27-周三_17-09-29

This commit is contained in:
2025-08-27 17:10:05 +08:00
commit 86df397d8f
12735 changed files with 1145479 additions and 0 deletions

View File

@@ -0,0 +1,625 @@
# 第十二章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 工具`