DeepSeek Redis Key数量过多:完整清理指南与最佳实践
Redis Key数量过多:完整清理指南与最佳实践
Redis中Key数量过多会导致内存压力增大、性能下降、备份和恢复变慢等问题。以下是完整的清理方案。
1. 分析和诊断阶段
1.1 查看当前Key数量
```bash
# 查看所有数据库的key数量
redis-cli info keyspace
# 查看当前数据库key总数
redis-cli dbsize
# 查看每个数据库的详细信息
redis-cli --bigkeys
```
1.2 分析Key分布
```bash
# 安装redis-stat工具进行实时监控
git clone https://github.com/junegunn/redis-stat.git
cd redis-stat
./redis-stat --server
# 或者使用redis-rdb-tools分析RDB文件
pip install rdbtools
rdb --command memory dump.rdb --bytes 128 --largest 20 > memory_report.csv
```
2. 渐进式清理策略
2.1 使用SCAN命令(推荐)
```bash
#!/bin/bash
# safe_clean.sh - 安全渐进式清理
REDIS_HOST="127.0.0.1"
REDIS_PORT=6379
REDIS_PASS=""
PATTERN="temp:*" # 要清理的模式
BATCH_SIZE=1000 # 每批处理数量
SLEEP_TIME=0.1 # 批次间隔
MAX_ITERATIONS=1000000 # 最大迭代次数
# 构建redis-cli命令
REDIS_CMD="redis-cli -h $REDIS_HOST -p $REDIS_PORT"
[ -n "$REDIS_PASS" ] && REDIS_CMD="$REDIS_CMD -a $REDIS_PASS"
echo "开始渐进式清理模式: $PATTERN"
cursor="0"
iteration=0
total_deleted=0
while [ "$cursor" != "0" ] || [ $iteration -eq 0 ]; do
if [ $iteration -ge $MAX_ITERATIONS ]; then
echo "达到最大迭代次数"
break
fi
# 获取一批key
result=$($REDIS_CMD SCAN $cursor MATCH "$PATTERN" COUNT $BATCH_SIZE 2>/dev/null)
cursor=$(echo "$result" | head -1)
keys=$(echo "$result" | tail -1)
if [ -n "$keys" ] && [ "$keys" != "nil" ]; then
# 统计key数量
key_count=$(echo $keys | wc -w)
if [ $key_count -gt 0 ]; then
echo "迭代 $iteration: 找到 $key_count 个key,准备删除..."
# 使用UNLINK(非阻塞删除,Redis 4.0+)
$REDIS_CMD UNLINK $keys 2>/dev/null
total_deleted=$((total_deleted + key_count))
echo "已删除 $total_deleted 个key"
fi
fi
iteration=$((iteration + 1))
# 小延迟避免阻塞
sleep $SLEEP_TIME
# 每10000个key输出一次进度
if [ $((iteration % 10)) -eq 0 ]; then
echo "进度: 迭代 $iteration, 已删除 $total_deleted 个key"
fi
done
echo "清理完成,总共删除 $total_deleted 个key"
```
2.2 分批次清理不同模式
```bash
#!/bin/bash
# multi_pattern_clean.sh - 多模式批量清理
PATTERNS=(
"temp:*"
"cache:temp:*"
"session:*"
"test:*"
"debug:*"
"lock:*"
"tmp:*"
)
for pattern in "${PATTERNS[@]}"; do
echo "开始清理模式: $pattern"
# 先查看有多少个key
count=$(redis-cli --scan --pattern "$pattern" | wc -l)
if [ $count -gt 0 ]; then
echo "找到 $count 个key,开始删除..."
# 分批次删除
redis-cli --scan --pattern "$pattern" | \
while read key; do
echo "DEL $key" | redis-cli --pipe
done
echo "模式 $pattern 清理完成"
else
echo "模式 $pattern 没有匹配的key"
fi
echo "等待5秒..."
sleep 5
done
```
3. 按条件清理
3.1 按TTL清理
```bash
#!/bin/bash
# clean_by_ttl.sh - 按过期时间清理
# 清理已过期的key
redis-cli --scan --pattern "*" | while read key; do
ttl=$(redis-cli ttl "$key")
# ttl = -2 表示key不存在
# ttl = -1 表示没有设置过期时间
if [ "$ttl" -eq -2 ]; then
# key已不存在,跳过
continue
elif [ "$ttl" -eq -1 ]; then
# 没有过期时间的key,可以按业务逻辑处理
# 例如:删除7天未访问的key
idle_time=$(redis-cli object idletime "$key" 2>/dev/null || echo 0)
if [ "$idle_time" -gt 604800 ]; then # 7天 = 604800秒
echo "删除闲置key: $key (空闲 $idle_time 秒)"
redis-cli unlink "$key"
fi
fi
done
```
3.2 按内存大小清理
```bash
#!/bin/bash
# clean_large_keys.sh - 清理大Key
THRESHOLD=1048576 # 1MB = 1048576字节
echo "查找大于 ${THRESHOLD} 字节的key..."
redis-cli --scan --pattern "*" | while read key; do
# 获取key的内存使用情况(Redis 4.0+)
memory=$(redis-cli memory usage "$key" 2>/dev/null)
if [ -n "$memory" ] && [ "$memory" -gt "$THRESHOLD" ]; then
key_type=$(redis-cli type "$key")
echo "发现大key: $key (类型: $key_type, 大小: $memory 字节)"
# 根据类型采取不同策略
case $key_type in
"string")
# 对于字符串,可以直接删除或分割
redis-cli unlink "$key"
;;
"hash")
# 对于Hash,可以逐步删除字段
# redis-cli hscan "$key" 0 count 100 | while read field; do
# redis-cli hdel "$key" "$field"
# done
redis-cli unlink "$key"
;;
"list"|"set"|"zset")
redis-cli unlink "$key"
;;
esac
fi
done
```
4. 使用Lua脚本进行原子清理
4.1 Lua批量删除脚本
```lua
-- delete_keys.lua
-- 使用SCAN迭代删除,避免阻塞
local cursor = '0'
local pattern = ARGV[1] or '*'
local batch_size = tonumber(ARGV[2]) or 1000
local limit = tonumber(ARGV[3]) or 10000 -- 最多删除数量
local deleted = 0
repeat
-- 扫描一批key
local result = redis.call('SCAN', cursor, 'MATCH', pattern, 'COUNT', batch_size)
cursor = result[1]
local keys = result[2]
-- 删除这批key
if #keys > 0 then
-- 使用UNLINK避免阻塞
redis.call('UNLINK', unpack(keys))
deleted = deleted + #keys
-- 输出进度
redis.log(redis.LOG_NOTICE, "已删除 " .. deleted .. " 个key")
-- 如果达到限制,停止
if deleted >= limit then
break
end
end
-- 如果游标为0,扫描完成
if cursor == '0' or tonumber(cursor) == 0 then
break
end
-- 小延迟,避免阻塞
redis.call('TIME')
until false
return deleted
```
执行脚本:
```bash
# 加载并执行Lua脚本
redis-cli --eval delete_keys.lua , "temp:*" 1000 10000
```
4.2 复杂条件清理脚本
```lua
-- conditional_clean.lua
-- 按条件清理key
local pattern = ARGV[1] or '*'
local min_idle_seconds = tonumber(ARGV[2]) or 86400 -- 默认24小时
local max_memory = tonumber(ARGV[3]) or 1048576 -- 默认1MB
local action = ARGV[4] or 'delete' -- delete|expire|log
local cursor = '0'
local stats = {
deleted = 0,
expired = 0,
skipped = 0,
large_keys = 0
}
repeat
local result = redis.call('SCAN', cursor, 'MATCH', pattern, 'COUNT', 500)
cursor = result[1]
local keys = result[2]
for i, key in ipairs(keys) do
local should_process = false
-- 检查空闲时间
local idle = redis.call('OBJECT', 'IDLETIME', key)
if idle >= min_idle_seconds then
should_process = true
end
-- 检查内存大小(Redis 4.0+)
local memory = 0
if redis.pcall('MEMORY', 'USAGE', key) then
memory = redis.call('MEMORY', 'USAGE', key)
if memory >= max_memory then
stats.large_keys = stats.large_keys + 1
should_process = true
end
end
if should_process then
if action == 'delete' then
redis.call('UNLINK', key)
stats.deleted = stats.deleted + 1
elseif action == 'expire' then
redis.call('EXPIRE', key, 60) -- 设置为60秒后过期
stats.expired = stats.expired + 1
elseif action == 'log' then
-- 仅记录,不执行操作
redis.log(redis.LOG_WARNING, "候选key: " .. key ..
" (空闲: " .. idle .. "秒, 大小: " .. memory .. "字节)")
end
else
stats.skipped = stats.skipped + 1
end
end
-- 每批处理完稍作休息
if #keys > 0 then
redis.call('TIME')
end
until cursor == '0'
return stats
```
5. 自动化和定时清理
5.1 Crontab定时任务
```bash
# 编辑crontab
crontab -e
# 添加以下内容
# 每天凌晨3点清理临时key
0 3 * * * /usr/local/bin/clean_redis_temp.sh
# 每小时清理过期的session
0 * * * * /usr/local/bin/clean_expired_sessions.sh
# 每周日凌晨2点执行深度清理
0 2 * * 0 /usr/local/bin/deep_clean_redis.sh
# 每月1日凌晨1点清理测试数据
0 1 1 * * /usr/local/bin/clean_test_data.sh
```
5.2 Systemd定时服务
```ini
# /etc/systemd/system/redis-cleanup.service
[Unit]
Description=Redis Key Cleanup Service
After=network.target redis.service
Requires=redis.service
[Service]
Type=oneshot
User=redis
ExecStart=/usr/local/bin/redis-cleanup.sh
StandardOutput=journal
# /etc/systemd/system/redis-cleanup.timer
[Unit]
Description=Run Redis cleanup daily
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
```
```bash
# 启用定时器
sudo systemctl daemon-reload
sudo systemctl enable redis-cleanup.timer
sudo systemctl start redis-cleanup.timer
```
6. 监控和告警
6.1 监控脚本
```bash
#!/bin/bash
# monitor_redis_keys.sh
THRESHOLD_WARNING=1000000 # 警告阈值
THRESHOLD_CRITICAL=2000000 # 严重阈值
ALERT_EMAIL="admin@example.com"
# 获取key数量
KEY_COUNT=$(redis-cli dbsize)
# 获取内存使用
MEMORY_INFO=$(redis-cli info memory)
USED_MEMORY=$(echo "$MEMORY_INFO" | grep "used_memory:" | cut -d: -f2)
echo "Redis Key监控报告"
echo "================="
echo "当前时间: $(date)"
echo "Key总数: $KEY_COUNT"
echo "内存使用: $USED_MEMORY 字节"
# 检查阈值
if [ $KEY_COUNT -ge $THRESHOLD_CRITICAL ]; then
SUBJECT="[CRITICAL] Redis Key数量严重超标"
MESSAGE="Redis Key数量已达到 $KEY_COUNT,超过严重阈值 $THRESHOLD_CRITICAL"
echo "$SUBJECT"
echo "$MESSAGE"
# 发送邮件告警
echo "$MESSAGE" | mail -s "$SUBJECT" $ALERT_EMAIL
# 触发自动清理
/usr/local/bin/emergency_cleanup.sh
elif [ $KEY_COUNT -ge $THRESHOLD_WARNING ]; then
SUBJECT="[WARNING] Redis Key数量警告"
MESSAGE="Redis Key数量已达到 $KEY_COUNT,超过警告阈值 $THRESHOLD_WARNING"
echo "$SUBJECT"
echo "$MESSAGE"
echo "$MESSAGE" | mail -s "$SUBJECT" $ALERT_EMAIL
fi
# 生成详细报告
echo ""
echo "Key模式分析:"
echo "------------"
PATTERNS=(
"user:*"
"session:*"
"cache:*"
"temp:*"
"product:*"
"order:*"
)
for pattern in "${PATTERNS[@]}"; do
count=$(redis-cli --scan --pattern "$pattern" | wc -l)
if [ $count -gt 0 ]; then
percentage=$(echo "scale=2; $count * 100 / $KEY_COUNT" | bc)
echo "$pattern: $count 个 ($percentage%)"
fi
done
# 记录到日志文件
LOG_FILE="/var/log/redis_monitor.log"
echo "[$(date)] Keys: $KEY_COUNT, Memory: $USED_MEMORY" >> $LOG_FILE
```
6.2 使用Redis自带的监控
```bash
# 启用Redis监控
redis-cli config set notify-keyspace-events Ex
# 监控过期key事件
redis-cli --csv psubscribe '__keyevent@0__:expired'
# 查看慢查询
redis-cli slowlog get 10
# 查看客户端信息
redis-cli client list
```
7. 最佳清理策略
7.1 分级清理策略
```bash
#!/bin/bash
# tiered_cleanup.sh - 分级清理策略
echo "=== Redis分级清理策略 ==="
echo ""
# 第一级:清理明显无用的key
echo "1. 清理临时和测试key..."
redis-cli --scan --pattern "temp:*" | xargs -r redis-cli unlink
redis-cli --scan --pattern "test:*" | xargs -r redis-cli unlink
redis-cli --scan --pattern "debug:*" | xargs -r redis-cli unlink
sleep 2
# 第二级:清理过期但未删除的key
echo "2. 清理过期key..."
redis-cli --scan --pattern "*" | while read key; do
ttl=$(redis-cli ttl "$key")
if [ "$ttl" -eq -2 ]; then
# key已过期不存在,Redis会自动清理
continue
fi
done
sleep 2
# 第三级:清理长期未访问的key
echo "3. 清理闲置key(30天未访问)..."
redis-cli --scan --pattern "*" | while read key; do
idle_time=$(redis-cli object idletime "$key" 2>/dev/null || echo 0)
if [ "$idle_time" -gt 2592000 ]; then # 30天
# 检查key类型,避免删除重要数据
key_type=$(redis-cli type "$key")
if [ "$key_type" = "string" ] && [[ "$key" == cache:* ]]; then
echo "删除闲置缓存: $key"
redis-cli unlink "$key"
fi
fi
done
sleep 2
# 第四级:清理大key
echo "4. 清理大key(>1MB)..."
redis-cli --scan --pattern "*" | while read key; do
memory=$(redis-cli memory usage "$key" 2>/dev/null || echo 0)
if [ "$memory" -gt 1048576 ]; then
echo "发现大key: $key ($memory 字节)"
# 这里可以根据业务决定是否删除
fi
done
echo "清理完成"
```
7.2 预防措施
```bash
#!/bin/bash
# redis_prevention.sh - 预防Key过多的措施
# 1. 设置内存淘汰策略
redis-cli config set maxmemory-policy allkeys-lru
# 2. 启用key过期事件通知
redis-cli config set notify-keyspace-events Ex
# 3. 设置合理的过期时间
# 对于不同类型的key设置不同过期时间
# 4. 使用Hash等数据结构合并小key
# 将多个相关key合并为一个Hash
# 5. 定期压缩和优化
redis-cli memory purge # Redis 4.0+
# 6. 监控和告警设置
# 设置key数量阈值告警
echo "预防措施已应用"
```
8. 紧急情况处理
8.1 紧急清理脚本
```bash
#!/bin/bash
# emergency_cleanup.sh - 紧急清理
echo "!!! 紧急清理模式 !!!"
echo "当前Key数量: $(redis-cli dbsize)"
echo "当前内存使用: $(redis-cli info memory | grep used_memory_human)"
# 立即清理所有临时key
echo "清理所有临时key..."
redis-cli --scan --pattern "temp:*" | xargs -r redis-cli unlink
# 清理所有测试key
echo "清理所有测试key..."
redis-cli --scan --pattern "test:*" | xargs -r redis-cli unlink
# 清理所有缓存key(谨慎操作)
echo "清理所有缓存key..."
redis-cli --scan --pattern "cache:*" | while read key; do
# 只清理特定模式的缓存
if [[ "$key" =~ ^cache:(temp|session|token): ]]; then
redis-cli unlink "$key"
fi
done
# 强制内存回收
echo "强制内存回收..."
redis-cli memory purge 2>/dev/null || true
# 重启AOF重写(如果有AOF)
echo "触发AOF重写..."
redis-cli bgrewriteaof 2>/dev/null || true
echo "紧急清理完成"
echo "清理后Key数量: $(redis-cli dbsize)"
```
8.2 使用Redis的FLUSHDB(谨慎!)
```bash
# 仅在所有其他方法都无效时使用
# 警告:这会删除当前数据库的所有数据
# 1. 先备份
redis-cli save
# 2. 切换到非生产数据库进行测试
redis-cli select 9
redis-cli flushdb
# 3. 生产环境谨慎使用
# redis-cli flushdb async # Redis 4.0+ 异步删除
# 4. 或者清空所有数据库
# redis-cli flushall
```
9. 维护计划示例
9.1 每日维护
```bash
#!/bin/bash
# daily_maintenance.sh
# 1. 清理过期session
redis-cli --scan --pattern "session:*" | while read key; do
ttl=$(redis-cli ttl "$key")
if [ "$ttl" -lt 0 ]; then
redis-cli unlink "$key"
fi
done
# 2. 清理24小时前的临时文件
redis-cli --scan --pattern "temp:*" | while read key; do
idle=$(redis-cli object idletime "$key" 2>/dev/null || echo 0)
if [ "$idle" -gt 86400 ]; then
redis-cli unlink "$key"
fi
done
# 3. 记录统计信息
echo "$(date),$(redis-cli dbsize)" >> /var/log/redis_daily_stats.csv
```
9.2 每周维护
```bash
#!/bin/bash
# weekly_maintenance.sh
# 1. 分析key分布
redis-cli --bigkeys
# 2. 深度清理长期未使用的key
for pattern in "cache:*" "log:*" "stat:*"; do
redis-cli --scan --pattern "$pattern" | while read key; do
idle=$(redis-cli object idletime "$key" 2>/dev/null || echo 0)
if [ "$idle" -gt 604800 ]; then # 7天
redis-cli expire "$key" 3600 # 设置为1小时后过期
fi
done
done
# 3. 优化内存
redis-cli memory purge 2>/dev/null || true
```
10. 总结:清理检查清单
1. 分析阶段
· 使用INFO keyspace查看key分布
· 使用--bigkeys找出大key
· 分析业务,识别可清理的key模式
2. 安全准备
· 备份Redis数据(SAVE或BGSAVE)
· 在非高峰时段执行
· 先在小范围测试清理脚本
3. 执行清理
· 使用SCAN而不是KEYS
· 使用UNLINK而不是DEL(Redis 4.0+)
· 分批处理,添加延迟
· 监控Redis性能指标
4. 验证结果
· 检查key数量变化
· 检查内存使用变化
· 监控应用是否正常
5. 建立预防机制
· 设置合理的过期时间
· 实现自动清理脚本
· 建立监控告警
· 定期审计和优化
记住:永远不要在生产环境直接使用KEYS *或FLUSHDB,除非你完全确定后果。始终采用渐进式、可监控的方式清理。
更多推荐



所有评论(0)