清理三主三从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
相关推荐
科技小花2 小时前
数据治理平台架构演进观察:AI原生设计如何重构企业数据管理范式
数据库·重构·架构·数据治理·ai-native·ai原生
一江寒逸2 小时前
零基础从入门到精通MySQL(中篇):进阶篇——吃透多表查询、事务核心与高级特性,搞定复杂业务SQL
数据库·sql·mysql
D4c-lovetrain2 小时前
linux个人心得22 (mysql)
数据库·mysql
阿里小阿希2 小时前
CentOS7 PostgreSQL 9.2 升级到 15 完整教程
数据库·postgresql
荒川之神2 小时前
Oracle 数据仓库雪花模型设计(完整实战方案)
数据库·数据仓库·oracle
做个文艺程序员2 小时前
MySQL安全加固十大硬核操作
数据库·mysql·安全
不吃香菜学java3 小时前
Redis简单应用
数据库·spring boot·tomcat·maven
一个天蝎座 白勺 程序猿3 小时前
Apache IoTDB(15):IoTDB查询写回(INTO子句)深度解析——从语法到实战的ETL全链路指南
数据库·apache·etl·iotdb
不知名的老吴3 小时前
Redis的延迟瓶颈:TCP栈开销无法避免
数据库·redis·缓存
YOU OU3 小时前
三大范式和E-R图
数据库