本文目录导读:

是的,脚本可以自动清理Redis中的大Key,但需要谨慎操作,因为不当的清理可能导致长时间阻塞、数据丢失或集群不稳定。
以下是几种常用的脚本方案及关键注意事项:
核心思路:分批删除
直接使用 DEL 删除一个大Key(如一个包含百万元素的List/Set/Hash)会阻塞Redis主线程,导致服务中断,脚本的核心是分批、延迟、扫描删除。
常用脚本方案
针对不同类型的大Key,使用对应的游标式命令
#!/bin/bash
# 示例:自动扫描并删除超过 10KB 的 key(针对 Hash/List/Set/ZSet/SortedSet)
REDIS_CLI="redis-cli -h 127.0.0.1 -p 6379 -a yourpassword --no-auth-warning"
THRESHOLD_BYTES=10240 # 10KB
# 使用 SCAN 命令渐进式遍历所有 key
cursor=0
while true; do
result=$(redis-cli --no-auth-warning -a yourpassword SCAN $cursor COUNT 100)
cursor=$(echo "$result" | head -1)
keys=$(echo "$result" | tail -n +2)
for key in $keys; do
# 检查内存大小 (DEBUG OBJECT 会返回 serializedlength)
size=$(redis-cli --no-auth-warning -a yourpassword DEBUG OBJECT "$key" 2>/dev/null | grep -oP 'serializedlength:\K[0-9]+')
if [ "$size" -ge "$THRESHOLD_BYTES" ]; then
type=$(redis-cli --no-auth-warning -a yourpassword TYPE "$key")
echo "Found Large Key: $key (Type: $type, Size: ${size}bytes)"
# 根据不同结构分批删除
case "$type" in
string)
# String 可以直删 (但建议确认业务)
redis-cli --no-auth-warning -a yourpassword DEL "$key"
;;
list)
# 每次 LTRIM 1000 个元素
while true; do
result=$(redis-cli --no-auth-warning -a yourpassword LTRIM "$key" 1000 -1)
# 如果剩余长度 <= 1000,直接 DEL
len=$(redis-cli --no-auth-warning -a yourpassword LLEN "$key")
if [ "$len" -le 1000 ]; then
redis-cli --no-auth-warning -a yourpassword DEL "$key"
break
fi
sleep 0.1 # 延迟,避免阻塞
done
;;
set)
# 每次 SPOP 1000
while true; do
size=$(redis-cli --no-auth-warning -a yourpassword SCARD "$key")
if [ "$size" -eq 0 ]; then break; fi
redis-cli --no-auth-warning -a yourpassword SPOP "$key" 1000 > /dev/null 2>&1
sleep 0.1
done
;;
hash)
# 每次 HDEL 1000 字段
while true; do
fields=$(redis-cli --no-auth-warning -a yourpassword HKEYS "$key" | head -1000)
if [ -z "$fields" ]; then break; fi
redis-cli --no-auth-warning -a yourpassword HDEL "$key" $fields
sleep 0.1
done
;;
zset)
# 每次 ZREMRANGEBYRANK 0 999 (删除前1000个)
while true; do
count=$(redis-cli --no-auth-warning -a yourpassword ZCARD "$key")
if [ "$count" -eq 0 ]; then break; fi
redis-cli --no-auth-warning -a yourpassword ZREMRANGEBYRANK "$key" 0 999
sleep 0.1
done
;;
esac
fi
done
if [ "$cursor" -eq 0 ]; then
break
fi
done
使用 Python 脚本(更可控、更适合复杂逻辑)
import redis
import time
r = redis.Redis(host='127.0.0.1', port=6379, password='yourpassword')
THRESHOLD = 10000 # 元素数超过 1 万视为大Key
def delete_large_key(key):
key_type = r.type(key).decode('utf-8')
print(f"Deleting large key: {key}, type: {key_type}")
if key_type == 'string':
# String 特殊处理:确认是否可删除
r.delete(key)
elif key_type == 'list':
while r.llen(key) > 1000:
# 保留最后 1000 个,前面的全部清除
r.ltrim(key, 0, -1001)
time.sleep(0.1)
r.delete(key)
elif key_type == 'set':
while r.scard(key) > 0:
# 每次 1000 个
members = r.spop(key, 1000)
if not members:
break
time.sleep(0.1)
elif key_type == 'hash':
while True:
fields = list(r.hkeys(key)[:1000])
if not fields:
break
r.hdel(key, *fields)
time.sleep(0.1)
r.delete(key) # 清理空Key
elif key_type == 'zset':
while r.zcard(key) > 0:
# 每次删除排序最小的 1000 个
r.zremrangebyrank(key, 0, 999)
time.sleep(0.1)
# 扫描所有 key
cursor = 0
while True:
cursor, keys = r.scan(cursor=cursor, count=100)
for key in keys:
# 检查元素数量或内存大小
key_type = r.type(key).decode('utf-8')
if key_type in ('list', 'set', 'hash', 'zset'):
size = eval(f'r.{key_type.lower() if key_type != "zset" else "zcard"}(key)')
if size > THRESHOLD:
delete_large_key(key)
elif key_type == 'string':
# 通过 STRLEN 或 DEBUG OBJECT 检查大小
if r.strlen(key) > 10240: # 大于10KB
delete_large_key(key)
if cursor == 0:
break
注意事项(非常重要)
- 绝对避免在高峰期直删大Key:即使使用分批方法,如果主库写入压力大,仍可能造成延迟抖动,建议在低峰期执行。
SCAN命令弱一致性:它不保证完整遍历所有Key,但对于清理大Key,通常可接受。- 对String类型特殊处理:String没有分批删除接口,如果要删一个非常大的String(如几MB),直接
DEL会阻塞,这种情况下,可以考虑:- 使用
UNLINK命令(Redis 4.0+):异步删除,不阻塞,这是最安全的方式。 - 先
GET设空再删(不推荐,只能减少数据拷贝)。
- 使用
- 集群环境:如果使用Redis Cluster,脚本需要处理节点槽位,可以使用
redis-cli --cluster模式或自行遍历每个节点。 - 权限控制:生产环境建议使用只读账号扫描,以避免误删。
- 监控与回滚:执行前记录Key列表,删除后观察主从延迟和内存释放情况。
推荐的专业工具
如果不想自己写脚本,可以使用以下成熟工具:
- Redis Labs 的
redis-rdb-tools:分析RDB文件找到大Key,再针对性删除。 redis-cli --bigkeys:扫描并报告大Key(只读,不删除)。- 阿里巴巴开源的
redis-shake:支持过滤和迁移,可配置删除大Key规则。
脚本可以自动清理,但强烈建议:
- 优先使用
UNLINK异步删除(如果版本 ≥ 4.0)。 - 对于复杂数据类型,使用游标或截断命令分批删除。
- 加上睡眠延迟(0.1~0.5s) 和并发限制,防止阻塞。
- 先在测试环境验证,生产环境慎重执行。