清理三主三从redis集群的过期key和键值超过10M的key

一.基于shell脚本进行删除

针对 Redis 三主三从集群,要实现定时清理过期 Key + 超过 10M 的超大 Key,核心思路是:

  1. 自动识别集群所有主节点(从节点无需清理,数据同步主节点);
  2. 基于 SCAN 非阻塞遍历主节点 Key,筛选目标后删除;
  3. 通过 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,核心优势是:

  1. Lua 脚本在 Redis 服务端原子执行,减少网络往返(相比 Shell/Python 遍历更高效);
  2. 结合 SCAN 非阻塞遍历,避免阻塞主线程;
  3. 适配集群特性(仅清理主节点,从节点同步删除结果)。

创建 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
相关推荐
清水白石00817 小时前
解构异步编程的两种哲学:从 asyncio 到 Trio,理解 Nursery 的魔力
运维·服务器·数据库·python
资生算法程序员_畅想家_剑魔17 小时前
Mysql常见报错解决分享-01-Invalid escape character in string.
数据库·mysql
冰冰菜的扣jio17 小时前
Redis缓存中三大问题——穿透、击穿、雪崩
java·redis·缓存
PyHaVolask17 小时前
SQL注入漏洞原理
数据库·sql
ptc学习者18 小时前
黑格尔时代后崩解的辩证法
数据库
代码游侠18 小时前
应用——智能配电箱监控系统
linux·服务器·数据库·笔记·算法·sqlite
阿里巴巴P8资深技术专家18 小时前
基于 Spring AI 和 Redis 向量库的智能对话系统实践
人工智能·redis·spring
!chen18 小时前
EF Core自定义映射PostgreSQL原生函数
数据库·postgresql
霖霖总总18 小时前
[小技巧14]MySQL 8.0 系统变量设置全解析:SET GLOBAL、SET PERSIST 与 SET PERSIST_ONLY 的区别与应用
数据库·mysql
马克学长18 小时前
SSM校园食堂订餐系统531p9(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面
数据库·ssm 框架·ssm 校园食堂订餐系统