一.基于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
三.redis集群的日常巡检脚本
针对 Redis 三主三从集群 定制的日常监控巡检脚本,覆盖集群健康、主从复制、性能、资源、大 Key / 过期 Key 等核心维度,输出结构化日志 + 异常告警(支持钉钉 / 邮件扩展),可直接落地生产环境
#!/bin/bash
# Redis 三主三从集群日常巡检脚本
# 覆盖:集群状态、槽位、主从复制、内存、CPU、连接数、大Key、过期Key
# 适用场景:生产环境Redis Cluster(3主3从)
# 日志路径:/var/log/redis_cluster_monitor.log
# 告警扩展:支持钉钉/邮件(需自行配置告警接口)
########################## 配置区(根据实际修改) ##########################
# Redis集群任意节点(用于获取主节点列表)
CLUSTER_ANY_NODE="192.168.1.10:6379"
# Redis密码(无密码则注释此行,删除后续 -a $REDIS_PWD)
REDIS_PWD="your_redis_password"
# 内存使用率阈值(%)
MEM_THRESHOLD=80
# CPU使用率阈值(%)
CPU_THRESHOLD=70
# 最大连接数阈值(%,基于maxclients)
CONN_THRESHOLD=80
# 大Key阈值(字节,10M)
BIG_KEY_SIZE=$((10 * 1024 * 1024))
# 巡检日志路径
LOG_FILE="/var/log/redis_cluster_monitor.log"
# 钉钉告警机器人WebHook(可选,需开启则填写)
DINGTALK_WEBHOOK="https://oapi.dingtalk.com/robot/send?access_token=your_token"
###########################################################################
# 全局变量
ALERT_FLAG=0 # 告警标记:0=无异常,1=有异常
TOTAL_MASTER=3 # 预期主节点数
TOTAL_SLAVE=3 # 预期从节点数
TOTAL_SLOTS=16384 # Redis总槽位数
# ===================== 工具函数 =====================
# 日志函数(带时间戳)
log() {
local LEVEL=$1
local MSG=$2
echo "[$(date +'%Y-%m-%d %H:%M:%S')] [$LEVEL] $MSG" >> $LOG_FILE
}
# 告警函数(钉钉推送+日志标记)
alert() {
local MSG=$1
log "ALERT" "$MSG"
ALERT_FLAG=1
# 钉钉告警(可选,注释则关闭)
if [ -n "$DINGTALK_WEBHOOK" ]; then
curl -s -X POST $DINGTALK_WEBHOOK \
-H "Content-Type: application/json" \
-d "{
\"msgtype\": \"text\",
\"text\": {\"content\": \"【Redis集群告警】$MSG\"},
\"at\": {\"isAtAll\": true}
}" > /dev/null 2>&1
fi
}
# 检查命令是否存在
check_cmd() {
if ! command -v $1 &> /dev/null; then
log "ERROR" "未找到命令:$1,请安装后重试"
exit 1
fi
}
# 获取Redis进程PID
get_redis_pid() {
local HOST=$1
local PORT=$2
redis-cli -h $HOST -p $PORT -a $REDIS_PWD --raw CONFIG GET pid 2>/dev/null | grep -E "^[0-9]+$"
}
# ===================== 核心巡检函数 =====================
# 1. 检查集群基础状态(节点数、槽位)
check_cluster_basic() {
log "INFO" "===== 开始检查集群基础状态 ====="
# 获取集群节点列表
local NODE_LIST=$(redis-cli -h $(echo $CLUSTER_ANY_NODE | cut -d':' -f1) \
-p $(echo $CLUSTER_ANY_NODE | cut -d':' -f2) \
-a $REDIS_PWD --raw CLUSTER NODES 2>> $LOG_FILE)
if [ -z "$NODE_LIST" ]; then
alert "集群连接失败!请检查节点地址/密码是否正确"
return
fi
# 统计主/从节点数
local MASTER_COUNT=$(echo "$NODE_LIST" | grep -c -E "master|myself,master")
local SLAVE_COUNT=$(echo "$NODE_LIST" | grep -c "slave")
log "INFO" "集群节点统计:预期主节点=$TOTAL_MASTER,实际=$MASTER_COUNT;预期从节点=$TOTAL_SLAVE,实际=$SLAVE_COUNT"
# 节点数异常告警
if [ $MASTER_COUNT -ne $TOTAL_MASTER ] || [ $SLAVE_COUNT -ne $TOTAL_SLAVE ]; then
alert "节点数异常!主节点=$MASTER_COUNT(预期$TOTAL_MASTER),从节点=$SLAVE_COUNT(预期$TOTAL_SLAVE)"
fi
# 检查槽位分配
local SLOTS_ASSIGNED=$(redis-cli -h $(echo $CLUSTER_ANY_NODE | cut -d':' -f1) \
-p $(echo $CLUSTER_ANY_NODE | cut -d':' -f2) \
-a $REDIS_PWD --raw CLUSTER INFO 2>> $LOG_FILE | grep "cluster_slots_assigned" | awk -F':' '{print $2}')
log "INFO" "集群槽位分配:实际=$SLOTS_ASSIGNED,预期=$TOTAL_SLOTS"
if [ $SLOTS_ASSIGNED -ne $TOTAL_SLOTS ]; then
alert "槽位分配异常!已分配=$SLOTS_ASSIGNED(预期$TOTAL_SLOTS),集群可能不可用"
fi
# 检查槽位迁移/未覆盖
local SLOTS_MIGRATING=$(echo "$(redis-cli -h $(echo $CLUSTER_ANY_NODE | cut -d':' -f1) -p $(echo $CLUSTER_ANY_NODE | cut -d':' -f2) -a $REDIS_PWD --raw CLUSTER INFO 2>> $LOG_FILE)" | grep "cluster_slots_migrating" | awk -F':' '{print $2}')
local SLOTS_UNCOVERED=$(echo "$(redis-cli -h $(echo $CLUSTER_ANY_NODE | cut -d':' -f1) -p $(echo $CLUSTER_ANY_NODE | cut -d':' -f2) -a $REDIS_PWD --raw CLUSTER INFO 2>> $LOG_FILE)" | grep "cluster_slots_uncovered" | awk -F':' '{print $2}')
log "INFO" "槽位迁移数=$SLOTS_MIGRATING,未覆盖数=$SLOTS_UNCOVERED"
if [ $SLOTS_MIGRATING -ne 0 ] || [ $SLOTS_UNCOVERED -ne 0 ]; then
alert "槽位状态异常!迁移数=$SLOTS_MIGRATING,未覆盖数=$SLOTS_UNCOVERED"
fi
}
# 2. 检查主从复制状态
check_replication() {
log "INFO" "===== 开始检查主从复制状态 ====="
# 获取所有主节点
local MASTER_NODES=$(redis-cli -h $(echo $CLUSTER_ANY_NODE | cut -d':' -f1) \
-p $(echo $CLUSTER_ANY_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)
for NODE in $MASTER_NODES; do
local HOST=$(echo $NODE | cut -d':' -f1)
local PORT=$(echo $NODE | cut -d':' -f2)
log "INFO" "检查主节点复制状态:$HOST:$PORT"
# 主节点从节点连接数
local SLAVE_CONN=$(redis-cli -h $HOST -p $PORT -a $REDIS_PWD --raw INFO replication 2>> $LOG_FILE | grep "connected_slaves" | awk -F':' '{print $2}')
log "INFO" "主节点$HOST:$PORT 已连接从节点数=$SLAVE_CONN"
if [ $SLAVE_CONN -ne 1 ]; then # 三主三从,每个主节点对应1个从节点
alert "主节点$HOST:$PORT 从节点连接数异常!已连接=$SLAVE_CONN(预期1)"
fi
# 从节点复制状态
local SLAVE_STATUS=$(redis-cli -h $HOST -p $PORT -a $REDIS_PWD --raw INFO replication 2>> $LOG_FILE | grep "slave0:.*status=" | awk -F',' '{for(i=1;i<=NF;i++){if($i ~ /status=/){print substr($i,8)}}}')
if [ "$SLAVE_STATUS" != "online" ]; then
alert "主节点$HOST:$PORT 从节点复制状态异常!状态=$SLAVE_STATUS(预期online)"
fi
# 复制延迟(单位:字节)
local REPL_OFFSET=$(redis-cli -h $HOST -p $PORT -a $REDIS_PWD --raw INFO replication 2>> $LOG_FILE | awk -F':' '/master_repl_offset/ {master=$2} /slave_repl_offset/ {slave=$2} END {print master-slave}')
log "INFO" "主节点$HOST:$PORT 复制延迟=$REPL_OFFSET 字节"
if [ $REPL_OFFSET -gt 102400 ]; then # 延迟超过100KB告警
alert "主节点$HOST:$PORT 复制延迟过高!延迟=$REPL_OFFSET 字节"
fi
done
}
# 3. 检查资源使用率(内存、CPU、连接数)
check_resource() {
log "INFO" "===== 开始检查资源使用率 ====="
local MASTER_NODES=$(redis-cli -h $(echo $CLUSTER_ANY_NODE | cut -d':' -f1) \
-p $(echo $CLUSTER_ANY_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)
for NODE in $MASTER_NODES; do
local HOST=$(echo $NODE | cut -d':' -f1)
local PORT=$(echo $NODE | cut -d':' -f2)
log "INFO" "检查节点资源:$HOST:$PORT"
# 内存使用率
local USED_MEM=$(redis-cli -h $HOST -p $PORT -a $REDIS_PWD --raw INFO memory 2>> $LOG_FILE | grep "used_memory" | awk -F':' '{print $2}')
local MAX_MEM=$(redis-cli -h $HOST -p $PORT -a $REDIS_PWD --raw INFO memory 2>> $LOG_FILE | grep "maxmemory" | awk -F':' '{print $2}')
local MEM_USAGE=0
if [ $MAX_MEM -ne 0 ]; then
MEM_USAGE=$(echo "scale=2; ($USED_MEM / $MAX_MEM) * 100" | bc -l)
fi
local USED_MEM_HUMAN=$(redis-cli -h $HOST -p $PORT -a $REDIS_PWD --raw INFO memory 2>> $LOG_FILE | grep "used_memory_human" | awk -F':' '{print $2}')
local MAX_MEM_HUMAN=$(redis-cli -h $HOST -p $PORT -a $REDIS_PWD --raw INFO memory 2>> $LOG_FILE | grep "maxmemory_human" | awk -F':' '{print $2}')
log "INFO" "节点$HOST:$PORT 内存使用:$USED_MEM_HUMAN / $MAX_MEM_HUMAN ($MEM_USAGE%)"
if [ $(echo "$MEM_USAGE > $MEM_THRESHOLD" | bc -l) -eq 1 ]; then
alert "节点$HOST:$PORT 内存使用率超标!当前=$MEM_USAGE%(阈值=$MEM_THRESHOLD%)"
fi
# CPU使用率
local PID=$(get_redis_pid $HOST $PORT)
if [ -n "$PID" ]; then
local CPU_USAGE=$(top -b -n 1 -p $PID | grep -E "^$PID" | awk '{print $9}' | cut -d'.' -f1)
log "INFO" "节点$HOST:$PORT CPU使用率:$CPU_USAGE%(PID=$PID)"
if [ $CPU_USAGE -gt $CPU_THRESHOLD ]; then
alert "节点$HOST:$PORT CPU使用率超标!当前=$CPU_USAGE%(阈值=$CPU_THRESHOLD%)"
fi
else
alert "节点$HOST:$PORT 未获取到Redis PID,无法检查CPU使用率"
fi
# 连接数
local CONN_CURRENT=$(redis-cli -h $HOST -p $PORT -a $REDIS_PWD --raw INFO clients 2>> $LOG_FILE | grep "connected_clients" | awk -F':' '{print $2}')
local CONN_MAX=$(redis-cli -h $HOST -p $PORT -a $REDIS_PWD --raw CONFIG GET maxclients 2>> $LOG_FILE | grep -E "^[0-9]+$" | tail -n1)
local CONN_USAGE=$(echo "scale=2; ($CONN_CURRENT / $CONN_MAX) * 100" | bc -l)
log "INFO" "节点$HOST:$PORT 连接数:$CONN_CURRENT / $CONN_MAX ($CONN_USAGE%)"
if [ $(echo "$CONN_USAGE > $CONN_THRESHOLD" | bc -l) -eq 1 ]; then
alert "节点$HOST:$PORT 连接数超标!当前=$CONN_CURRENT(阈值=$CONN_THRESHOLD%)"
fi
done
}
# 4. 检查大Key和过期Key(抽样,避免阻塞)
check_keys() {
log "INFO" "===== 开始检查大Key和过期Key(抽样) ====="
local MASTER_NODES=$(redis-cli -h $(echo $CLUSTER_ANY_NODE | cut -d':' -f1) \
-p $(echo $CLUSTER_ANY_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)
for NODE in $MASTER_NODES; do
local HOST=$(echo $NODE | cut -d':' -f1)
local PORT=$(echo $NODE | cut -d':' -f2)
log "INFO" "检查节点Key:$HOST:$PORT"
# 抽样遍历Key(SCAN COUNT=100,避免阻塞)
local CURSOR=0
local BIG_KEY_COUNT=0
local EXPIRED_KEY_COUNT=0
while :; do
local SCAN_RESULT=$(redis-cli -h $HOST -p $PORT -a $REDIS_PWD --raw SCAN $CURSOR COUNT 100 2>> $LOG_FILE)
local NEW_CURSOR=$(echo "$SCAN_RESULT" | head -n1)
local KEYS=$(echo "$SCAN_RESULT" | tail -n +2)
for KEY in $KEYS; do
[ -z "$KEY" ] && continue
# 检查过期Key
local TTL=$(redis-cli -h $HOST -p $PORT -a $REDIS_PWD --raw TTL "$KEY" 2>> $LOG_FILE)
if [ $TTL -le 0 ] && [ $TTL -ne -1 ]; then
EXPIRED_KEY_COUNT=$((EXPIRED_KEY_COUNT + 1))
fi
# 检查大Key
local KEY_SIZE=$(redis-cli -h $HOST -p $PORT -a $REDIS_PWD --raw MEMORY USAGE "$KEY" 2>> $LOG_FILE)
if [[ "$KEY_SIZE" =~ ^[0-9]+$ ]] && [ $KEY_SIZE -gt $BIG_KEY_SIZE ]; then
BIG_KEY_COUNT=$((BIG_KEY_COUNT + 1))
log "WARN" "节点$HOST:$PORT 发现大Key:$KEY(大小=$KEY_SIZE 字节)"
fi
done
[ "$NEW_CURSOR" = "0" ] && break
CURSOR=$NEW_CURSOR
done
log "INFO" "节点$HOST:$PORT 抽样结果:过期Key数=$EXPIRED_KEY_COUNT,大Key数=$BIG_KEY_COUNT"
if [ $BIG_KEY_COUNT -gt 0 ]; then
alert "节点$HOST:$PORT 存在$BIG_KEY_COUNT个超大Key(阈值=10M),请及时清理"
fi
done
}
# ===================== 主流程 =====================
# 前置检查
check_cmd "redis-cli"
check_cmd "bc"
check_cmd "curl"
# 初始化日志
log "INFO" "===== Redis集群巡检开始 ====="
# 执行巡检
check_cluster_basic
check_replication
check_resource
check_keys
# 巡检总结
if [ $ALERT_FLAG -eq 0 ]; then
log "INFO" "===== Redis集群巡检完成:无异常 ====="
else
log "INFO" "===== Redis集群巡检完成:存在异常(已告警) ====="
fi
echo -e "\n" >> $LOG_FILE # 日志分隔符
exit 0