一.基于shell脚本进行删除
针对 Redis 三主三从集群,要实现定时清理过期 Key + 超过 10M 的超大 Key,核心思路是:
- 自动识别集群所有主节点(从节点无需清理,数据同步主节点);
- 基于
SCAN非阻塞遍历主节点 Key,筛选目标后删除; - 通过 Linux
crontab配置定时任务,低峰期自动执行。
以下是基于shell脚本实现
#!/bin/bash # Redis 集群清理脚本:删除过期 Key + 超过10M的Key # 适配三主三从集群,仅清理主节点 ########################## 配置区(根据实际修改) ########################## # Redis 集群任意节点地址(用于获取主节点列表) CLUSTER_ANY_NODE="10.10.19.11:6379" # Redis 密码(无密码则注释此行,后续删除 -a $REDIS_PASSWORD) REDIS_PASSWORD="your_redis_password" # 超大 Key 阈值:10M(字节) MAX_KEY_SIZE=$((10 * 1024 * 1024)) # SCAN 每次遍历 Key 数量(建议 100-500,越小越不阻塞) SCAN_COUNT=500 # 日志文件路径 LOG_FILE="/var/log/redis_cluster_clean.log" ########################################################################### # 日志函数(带时间戳) log() { echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" >> $LOG_FILE } # 检查 redis-cli 是否存在 if ! command -v redis-cli &> /dev/null; then log "错误:未找到 redis-cli,请确保 Redis 客户端已安装" exit 1 fi # 步骤1:获取集群所有主节点(过滤从节点、空行) log "开始获取集群主节点列表..." MASTER_NODES=$(redis-cli -h $(echo $CLUSTER_ANY_NODE | cut -d':' -f1) \ -p $(echo $CLUSTER_ANY_NODE | cut -d':' -f2) \ -a $REDIS_PASSWORD --raw CLUSTER NODES 2>> $LOG_FILE \ | grep -E "master|myself,master" \ | awk '{print $2}' \ | cut -d'@' -f1 \ | sort -u) if [ -z "$MASTER_NODES" ]; then log "错误:未获取到集群主节点,请检查集群连接或密码" exit 1 fi log "获取到主节点列表:$MASTER_NODES" # 步骤2:遍历每个主节点,清理目标 Key TOTAL_DELETED_EXPIRED=0 # 累计删除过期 Key 数 TOTAL_DELETED_LARGE=0 # 累计删除超大 Key 数 for NODE in $MASTER_NODES; do NODE_HOST=$(echo $NODE | cut -d':' -f1) NODE_PORT=$(echo $NODE | cut -d':' -f2) log "开始清理主节点:$NODE_HOST:$NODE_PORT" # 初始化 SCAN 游标 CURSOR=0 # 节点内删除计数 NODE_EXPIRED=0 NODE_LARGE=0 # 循环 SCAN 遍历当前节点所有 Key while :; do # 执行 SCAN 命令(--raw 避免中文/特殊字符乱码) SCAN_RESULT=$(redis-cli -h $NODE_HOST -p $NODE_PORT -a $REDIS_PASSWORD \ --raw SCAN $CURSOR COUNT $SCAN_COUNT 2>> $LOG_FILE) # 解析游标(第一行)和 Key 列表(剩余行) NEW_CURSOR=$(echo "$SCAN_RESULT" | head -n 1) KEYS=$(echo "$SCAN_RESULT" | tail -n +2) # 遍历当前批次 Key for KEY in $KEYS; do # 跳过空 Key [ -z "$KEY" ] && continue # 条件1:删除已过期 Key(TTL ≤ 0,-2=Key不存在,-1=永不过期,0=已过期) TTL=$(redis-cli -h $NODE_HOST -p $NODE_PORT -a $REDIS_PASSWORD --raw TTL "$KEY" 2>> $LOG_FILE) if [ "$TTL" -le 0 ] && [ "$TTL" -ne -1 ]; then # 排除永不过期的 Key redis-cli -h $NODE_HOST -p $NODE_PORT -a $REDIS_PASSWORD DEL "$KEY" >> /dev/null 2>&1 NODE_EXPIRED=$((NODE_EXPIRED + 1)) log "[$NODE] 删除过期 Key: $KEY (TTL: $TTL)" continue fi # 条件2:删除超过10M的 Key KEY_SIZE=$(redis-cli -h $NODE_HOST -p $NODE_PORT -a $REDIS_PASSWORD --raw MEMORY USAGE "$KEY" 2>> $LOG_FILE) # 防止 MEMORY USAGE 返回非数字(如 Key 被并发删除) if [[ "$KEY_SIZE" =~ ^[0-9]+$ ]] && [ "$KEY_SIZE" -gt "$MAX_KEY_SIZE" ]; then redis-cli -h $NODE_HOST -p $NODE_PORT -a $REDIS_PASSWORD DEL "$KEY" >> /dev/null 2>&1 NODE_LARGE=$((NODE_LARGE + 1)) log "[$NODE] 删除超大 Key: $KEY (大小: $KEY_SIZE 字节,阈值: $MAX_KEY_SIZE 字节)" fi done # 游标为 0 表示当前节点遍历完成 if [ "$NEW_CURSOR" = "0" ]; then break fi CURSOR=$NEW_CURSOR done # 累计节点删除数 TOTAL_DELETED_EXPIRED=$((TOTAL_DELETED_EXPIRED + NODE_EXPIRED)) TOTAL_DELETED_LARGE=$((TOTAL_DELETED_LARGE + NODE_LARGE)) log "[$NODE] 清理完成:过期 Key 删 $NODE_EXPIRED 个,超大 Key 删 $NODE_LARGE 个" done # 步骤3:输出总清理结果 log "==================== 清理汇总 ====================" log "集群总清理:过期 Key 共 $TOTAL_DELETED_EXPIRED 个,超大 Key 共 $TOTAL_DELETED_LARGE 个" log "===================================================" log " " # 空行分隔日志 exit 0二.基于lua脚本删除
在 Redis 三主三从集群中使用 Lua 脚本清理过期 Key和超过 10M 的超大 Key,核心优势是:
- Lua 脚本在 Redis 服务端原子执行,减少网络往返(相比 Shell/Python 遍历更高效);
- 结合
SCAN非阻塞遍历,避免阻塞主线程; - 适配集群特性(仅清理主节点,从节点同步删除结果)。
创建redis_clean.lua,实现单节点的 Key 筛选与删除(可直接在 Redis 节点执行)
-- Redis 单节点清理脚本:删除过期 Key + 超过10M的超大 Key -- 参数说明: -- ARGV[1] = 超大 Key 阈值(字节,如 10485760 表示 10M) -- ARGV[2] = SCAN 每次遍历数量(如 200) -- 初始化变量 local max_key_size = tonumber(ARGV[1]) local scan_count = tonumber(ARGV[2]) local cursor = "0" local deleted_expired = 0 -- 过期 Key 删除计数 local deleted_large = 0 -- 超大 Key 删除计数 local batch_del = {} -- 批量删除缓存(避免频繁 DEL) local batch_max = 50 -- 每批次最多删 50 个 Key -- 循环 SCAN 遍历所有 Key repeat -- 执行 SCAN:cursor = 下一个游标, keys = 当前批次 Key 列表 local scan_result = redis.call("SCAN", cursor, "COUNT", scan_count) cursor = scan_result[1] local keys = scan_result[2] -- 遍历当前批次 Key for _, key in ipairs(keys) do -- 跳过空 Key if key == nil or key == "" then goto continue end -- 条件1:删除已过期 Key(ttl ≤ 0 且 不是永不过期) local ttl = redis.call("TTL", key) if ttl <= 0 and ttl ~= -1 then table.insert(batch_del, key) deleted_expired = deleted_expired + 1 goto continue -- 满足过期条件,无需检查大小 end -- 条件2:删除超过阈值的超大 Key local size = redis.call("MEMORY", "USAGE", key) if tonumber(size) > max_key_size then table.insert(batch_del, key) deleted_large = deleted_large + 1 end ::continue:: -- 批量删除:达到批次上限则执行 DEL if #batch_del >= batch_max then redis.call("DEL", unpack(batch_del)) batch_del = {} -- 清空缓存 end end -- 游标为 0 时结束遍历 until cursor == "0" -- 处理剩余未删除的 Key if #batch_del > 0 then redis.call("DEL", unpack(batch_del)) end -- 返回清理结果 return { ["deleted_expired"] = deleted_expired, ["deleted_large"] = deleted_large }编写redis_cluster_lua_clean.sh,自动识别集群主节点,逐个执行 Lua 脚本(适配三主三从集群):
#!/bin/bash # Redis 三主三从集群清理脚本(Lua 版) # 自动识别主节点 → 执行 Lua 脚本 → 输出清理结果 ########################## 配置区(根据实际修改) ########################## # Redis 集群任意节点(用于获取主节点列表) CLUSTER_NODE="192.168.1.10:6379" # Redis 密码(无密码则删除 -a $REDIS_PWD) REDIS_PWD="your_redis_password" # 超大 Key 阈值:10M(字节) MAX_SIZE=$((10 * 1024 * 1024)) # SCAN 每次遍历数量(建议 200-500) SCAN_COUNT=200 # Lua 脚本路径 LUA_SCRIPT="/path/to/redis_clean.lua" # 日志文件 LOG_FILE="/var/log/redis_cluster_lua_clean.log" ########################################################################### # 日志函数(带时间戳) log() { echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" >> $LOG_FILE } # 检查依赖 if ! command -v redis-cli &> /dev/null; then log "错误:未找到 redis-cli,请安装 Redis 客户端" exit 1 fi if [ ! -f "$LUA_SCRIPT" ]; then log "错误:Lua 脚本不存在 → $LUA_SCRIPT" exit 1 fi # 步骤1:获取集群所有主节点 log "开始获取集群主节点列表..." MASTER_NODES=$(redis-cli -h $(echo $CLUSTER_NODE | cut -d':' -f1) \ -p $(echo $CLUSTER_NODE | cut -d':' -f2) \ -a $REDIS_PWD --raw CLUSTER NODES 2>> $LOG_FILE \ | grep -E "master|myself,master" \ | awk '{print $2}' | cut -d'@' -f1 | sort -u) if [ -z "$MASTER_NODES" ]; then log "错误:未获取到主节点,请检查集群连接/密码" exit 1 fi log "获取到主节点:$MASTER_NODES" # 步骤2:遍历主节点执行 Lua 脚本 TOTAL_EXPIRED=0 TOTAL_LARGE=0 for NODE in $MASTER_NODES; do HOST=$(echo $NODE | cut -d':' -f1) PORT=$(echo $NODE | cut -d':' -f2) log "开始清理主节点 → $HOST:$PORT" # 执行 Lua 脚本(--eval 加载脚本,ARGV 传参) RESULT=$(redis-cli -h $HOST -p $PORT -a $REDIS_PWD --raw \ EVAL "$(cat $LUA_SCRIPT)" 0 $MAX_SIZE $SCAN_COUNT 2>> $LOG_FILE) # 解析 Lua 返回结果(示例:{deleted_expired=10,deleted_large=2}) NODE_EXPIRED=$(echo $RESULT | grep -oP 'deleted_expired"\s*:\s*\K\d+') NODE_LARGE=$(echo $RESULT | grep -oP 'deleted_large"\s*:\s*\K\d+') # 兼容空结果 NODE_EXPIRED=${NODE_EXPIRED:-0} NODE_LARGE=${NODE_LARGE:-0} # 累计计数 TOTAL_EXPIRED=$((TOTAL_EXPIRED + NODE_EXPIRED)) TOTAL_LARGE=$((TOTAL_LARGE + NODE_LARGE)) log "[$HOST:$PORT] 清理完成 → 过期 Key:$NODE_EXPIRED 个,超大 Key:$NODE_LARGE 个" done # 步骤3:输出汇总 log "==================== 集群清理汇总 ====================" log "总删除:过期 Key = $TOTAL_EXPIRED 个,超大 Key = $TOTAL_LARGE 个" log "======================================================" log " " # 空行分隔日志 exit 0