Redis 内存溢出(OOM)排查与恢复实战

前言

💡 痛点 :Redis 突然报错 OOM command not allowed when used memory > 'maxmemory'?服务开始报错,缓存数据丢失?不知道是哪个 key 占用了大量内存?

🎯 解决方案 :掌握 Redis 内存排查与恢复 --- 从内存监控、key 分析、到问题定位与解决。

Redis 内存问题排查流程图:
#mermaid-svg-oD2IGacKk5BT4gMV{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-oD2IGacKk5BT4gMV .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-oD2IGacKk5BT4gMV .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-oD2IGacKk5BT4gMV .error-icon{fill:#552222;}#mermaid-svg-oD2IGacKk5BT4gMV .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-oD2IGacKk5BT4gMV .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-oD2IGacKk5BT4gMV .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-oD2IGacKk5BT4gMV .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-oD2IGacKk5BT4gMV .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-oD2IGacKk5BT4gMV .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-oD2IGacKk5BT4gMV .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-oD2IGacKk5BT4gMV .marker{fill:#333333;stroke:#333333;}#mermaid-svg-oD2IGacKk5BT4gMV .marker.cross{stroke:#333333;}#mermaid-svg-oD2IGacKk5BT4gMV svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-oD2IGacKk5BT4gMV p{margin:0;}#mermaid-svg-oD2IGacKk5BT4gMV .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-oD2IGacKk5BT4gMV .cluster-label text{fill:#333;}#mermaid-svg-oD2IGacKk5BT4gMV .cluster-label span{color:#333;}#mermaid-svg-oD2IGacKk5BT4gMV .cluster-label span p{background-color:transparent;}#mermaid-svg-oD2IGacKk5BT4gMV .label text,#mermaid-svg-oD2IGacKk5BT4gMV span{fill:#333;color:#333;}#mermaid-svg-oD2IGacKk5BT4gMV .node rect,#mermaid-svg-oD2IGacKk5BT4gMV .node circle,#mermaid-svg-oD2IGacKk5BT4gMV .node ellipse,#mermaid-svg-oD2IGacKk5BT4gMV .node polygon,#mermaid-svg-oD2IGacKk5BT4gMV .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-oD2IGacKk5BT4gMV .rough-node .label text,#mermaid-svg-oD2IGacKk5BT4gMV .node .label text,#mermaid-svg-oD2IGacKk5BT4gMV .image-shape .label,#mermaid-svg-oD2IGacKk5BT4gMV .icon-shape .label{text-anchor:middle;}#mermaid-svg-oD2IGacKk5BT4gMV .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-oD2IGacKk5BT4gMV .rough-node .label,#mermaid-svg-oD2IGacKk5BT4gMV .node .label,#mermaid-svg-oD2IGacKk5BT4gMV .image-shape .label,#mermaid-svg-oD2IGacKk5BT4gMV .icon-shape .label{text-align:center;}#mermaid-svg-oD2IGacKk5BT4gMV .node.clickable{cursor:pointer;}#mermaid-svg-oD2IGacKk5BT4gMV .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-oD2IGacKk5BT4gMV .arrowheadPath{fill:#333333;}#mermaid-svg-oD2IGacKk5BT4gMV .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-oD2IGacKk5BT4gMV .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-oD2IGacKk5BT4gMV .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oD2IGacKk5BT4gMV .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-oD2IGacKk5BT4gMV .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oD2IGacKk5BT4gMV .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-oD2IGacKk5BT4gMV .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-oD2IGacKk5BT4gMV .cluster text{fill:#333;}#mermaid-svg-oD2IGacKk5BT4gMV .cluster span{color:#333;}#mermaid-svg-oD2IGacKk5BT4gMV div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-oD2IGacKk5BT4gMV .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-oD2IGacKk5BT4gMV rect.text{fill:none;stroke-width:0;}#mermaid-svg-oD2IGacKk5BT4gMV .icon-shape,#mermaid-svg-oD2IGacKk5BT4gMV .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oD2IGacKk5BT4gMV .icon-shape p,#mermaid-svg-oD2IGacKk5BT4gMV .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-oD2IGacKk5BT4gMV .icon-shape .label rect,#mermaid-svg-oD2IGacKk5BT4gMV .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oD2IGacKk5BT4gMV .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-oD2IGacKk5BT4gMV .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-oD2IGacKk5BT4gMV :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} OOM 发生
查看错误日志
检查内存使用
分析大 Key
定位内存占用
检查过期策略
优化内存配置
调整淘汰策略
清理内存
恢复服务

Redis 内存问题类型:

类型 说明 影响
内存溢出 使用超过 maxmemory 命令执行失败
内存泄漏 内存持续增长不释放 最终 OOM
大 Key 单个 Key 占用过大 阻塞主线程
Key 堆积 过期 key 未及时删除 内存持续增长

一、Redis 内存机制

1.1 内存使用概览

#mermaid-svg-FuBdtwbGIkfkNkp8{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-FuBdtwbGIkfkNkp8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-FuBdtwbGIkfkNkp8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-FuBdtwbGIkfkNkp8 .error-icon{fill:#552222;}#mermaid-svg-FuBdtwbGIkfkNkp8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-FuBdtwbGIkfkNkp8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-FuBdtwbGIkfkNkp8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-FuBdtwbGIkfkNkp8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-FuBdtwbGIkfkNkp8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-FuBdtwbGIkfkNkp8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-FuBdtwbGIkfkNkp8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-FuBdtwbGIkfkNkp8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-FuBdtwbGIkfkNkp8 .marker.cross{stroke:#333333;}#mermaid-svg-FuBdtwbGIkfkNkp8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-FuBdtwbGIkfkNkp8 p{margin:0;}#mermaid-svg-FuBdtwbGIkfkNkp8 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-FuBdtwbGIkfkNkp8 .cluster-label text{fill:#333;}#mermaid-svg-FuBdtwbGIkfkNkp8 .cluster-label span{color:#333;}#mermaid-svg-FuBdtwbGIkfkNkp8 .cluster-label span p{background-color:transparent;}#mermaid-svg-FuBdtwbGIkfkNkp8 .label text,#mermaid-svg-FuBdtwbGIkfkNkp8 span{fill:#333;color:#333;}#mermaid-svg-FuBdtwbGIkfkNkp8 .node rect,#mermaid-svg-FuBdtwbGIkfkNkp8 .node circle,#mermaid-svg-FuBdtwbGIkfkNkp8 .node ellipse,#mermaid-svg-FuBdtwbGIkfkNkp8 .node polygon,#mermaid-svg-FuBdtwbGIkfkNkp8 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-FuBdtwbGIkfkNkp8 .rough-node .label text,#mermaid-svg-FuBdtwbGIkfkNkp8 .node .label text,#mermaid-svg-FuBdtwbGIkfkNkp8 .image-shape .label,#mermaid-svg-FuBdtwbGIkfkNkp8 .icon-shape .label{text-anchor:middle;}#mermaid-svg-FuBdtwbGIkfkNkp8 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-FuBdtwbGIkfkNkp8 .rough-node .label,#mermaid-svg-FuBdtwbGIkfkNkp8 .node .label,#mermaid-svg-FuBdtwbGIkfkNkp8 .image-shape .label,#mermaid-svg-FuBdtwbGIkfkNkp8 .icon-shape .label{text-align:center;}#mermaid-svg-FuBdtwbGIkfkNkp8 .node.clickable{cursor:pointer;}#mermaid-svg-FuBdtwbGIkfkNkp8 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-FuBdtwbGIkfkNkp8 .arrowheadPath{fill:#333333;}#mermaid-svg-FuBdtwbGIkfkNkp8 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-FuBdtwbGIkfkNkp8 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-FuBdtwbGIkfkNkp8 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FuBdtwbGIkfkNkp8 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-FuBdtwbGIkfkNkp8 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FuBdtwbGIkfkNkp8 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-FuBdtwbGIkfkNkp8 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-FuBdtwbGIkfkNkp8 .cluster text{fill:#333;}#mermaid-svg-FuBdtwbGIkfkNkp8 .cluster span{color:#333;}#mermaid-svg-FuBdtwbGIkfkNkp8 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-FuBdtwbGIkfkNkp8 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-FuBdtwbGIkfkNkp8 rect.text{fill:none;stroke-width:0;}#mermaid-svg-FuBdtwbGIkfkNkp8 .icon-shape,#mermaid-svg-FuBdtwbGIkfkNkp8 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FuBdtwbGIkfkNkp8 .icon-shape p,#mermaid-svg-FuBdtwbGIkfkNkp8 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-FuBdtwbGIkfkNkp8 .icon-shape .label rect,#mermaid-svg-FuBdtwbGIkfkNkp8 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FuBdtwbGIkfkNkp8 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-FuBdtwbGIkfkNkp8 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-FuBdtwbGIkfkNkp8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Redis 内存
数据内存
子进程内存
缓冲内存
管理内存
碎片内存
String 数据
Hash 数据
List 数据
Set 数据
Zset 数据
AOF 重写
RDB 保存
客户端缓冲
AOF 缓冲

bash 复制代码
# ===== 查看 Redis 内存信息 =====

# 查看内存使用概况
INFO memory

# 关键指标解读
# used_memory: Redis 分配的内存总量(字节)
# used_memory_human: 人类可读格式
# used_memory_rss: Redis 进程实际占用的物理内存
# used_memory_peak: 内存使用峰值
# used_memory_peak_rss: 物理内存峰值
# mem_fragmentation_ratio: 内存碎片率(used_memory_rss / used_memory)
# mem_clients_normal: 普通客户端内存
# mem_clients_slaves: 从节点客户端内存
# mem_aof_buffer: AOF 缓冲区内存
# mem_allocator: 内存分配器(jemalloc)

# 查看详细内存统计
INFO memory | grep -E "used|peak|fragment|clients"

1.2 内存分配策略

bash 复制代码
# ===== 内存配置 =====

# 查看当前 maxmemory 配置
CONFIG GET maxmemory

# 设置 maxmemory(示例:1GB)
CONFIG SET maxmemory 1073741824

# 设置 maxmemory(永久配置 redis.conf)
# maxmemory 1gb
# maxmemory 10240000  # 单位:字节

# 查看内存淘汰策略
CONFIG GET maxmemory-policy

# 淘汰策略说明:
# - noeviction: 不淘汰,返回错误(默认)
# - volatile-lru: LRU 算法淘汰有过期时间的 key
# - allkeys-lru: LRU 算法淘汰所有 key
# - volatile-lfu: LFU 算法淘汰有过期时间的 key
# - allkeys-lfu: LFU 算法淘汰所有 key
# - volatile-random: 随机淘汰有过期时间的 key
# - allkeys-random: 随机淘汰所有 key
# - volatile-ttl: 淘汰 TTL 最短的 key

# 设置淘汰策略
CONFIG SET maxmemory-policy allkeys-lru

1.3 内存碎片

bash 复制代码
# ===== 内存碎片处理 =====

# 查看内存碎片率
INFO memory | grep mem_fragmentation_ratio
# mem_fragmentation_ratio: 1.45

# 碎片率说明:
# 1.0 - 1.5: 正常范围
# > 1.5: 内存碎片较多,需要清理
# < 1.0: 使用了 swap

# 手动整理内存碎片(Redis 4.0+)
MEMORY PURGE

# 查看碎片详情
INFO memory

# 内存碎片原因:
# 1. 数据频繁更新(set 操作)
# 2. 大 Key 删除后空间不释放
# 3. 内存分配器(jemalloc)碎片

二、OOM 错误分析

2.1 OOM 错误类型

bash 复制代码
# ===== OOM 错误类型 =====

# 错误 1: 命令不允许
# OOM command not allowed when used memory > 'maxmemory'
# 原因:内存达到 maxmemory 且淘汰策略为 noeviction

# 错误 2: Lua 脚本内存超限
# BUSY Redis is busy running a script
# 原因:Lua 脚本占用超过 lua-time-limit

# 错误 3: 复制内存超限
# BUSY Redis is replica syncing with diskless loading disabled
# 原因:从节点复制时内存不足

# 错误 4: 子进程内存超限
# MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk
# 原因:fork 子进程时内存不足(Linux overcommit_memory)

2.2 错误日志分析

bash 复制代码
# ===== 查看 Redis 错误日志 =====

# 默认日志位置
# /var/log/redis/redis-server.log
# 或通过 CONFIG GET logfile 查看

# 查看最近错误日志
TAIL -100 /var/log/redis/redis-server.log

# 常见 OOM 相关日志
# 1. Out of memory
# 2. Client asked for something bigger than maxmemory
# 3. Memory usage reached maxmemory set
# 4. Cannot allocate memory

# 实时监控错误日志
TAIL -f /var/log/redis/redis-server.log | grep -i "oom\|error\|memory"

2.3 客户端错误捕获

bash 复制代码
# ===== 客户端连接 OOM =====

# Redis CLI 捕获 OOM 错误
redis-cli INFO stats | grep -E "total_commands_processed|rejected_commands"

# 查看被拒绝的命令
redis-cli INFO stats | grep -i "rejected"

# total_connections_received: 总连接数
# total_commands_processed: 总命令数
# rejected_commands: 被拒绝的命令数

# 实时监控命令拒绝率
redis-cli --latency-history

# 查看慢查询
redis-cli SLOWLOG GET 10

2.4 生产环境 OOM 案例

bash 复制代码
# ===== 案例 1: 大 Key 导致 OOM =====

# 现象:突然 OOM,重启后正常但很快再次 OOM
# 排查:查看大 Key
redis-cli --bigkeys

# 输出示例:
# Scanning 50,000 keys in 0.73 seconds
# Big key (hash): 12,345 bytes, 10,000 fields
# Big key (string): 5,432,100 bytes
# Big key (list): 100,000 items, 2,000,000 bytes
# Big key (set): 50,000 members, 1,500,000 bytes

# ===== 案例 2: 内存泄漏 =====

# 现象:内存持续增长,不回落
# 排查:监控内存变化
redis-cli INFO memory | grep used_memory
# 每分钟记录一次,观察趋势

# 查看持久化状态
redis-cli INFO persistence
# aof_current_size: AOF 文件大小
# aof_base_size: AOF 重写基础大小

# ===== 案例 3: 淘汰策略不当 =====

# 现象:设置了过期时间但内存仍然增长
# 排查:检查过期策略
redis-cli CONFIG GET maxmemory-policy
# 如果是 noeviction,需要修改

# 检查过期 key 数量
redis-cli DBSIZE
redis-cli KEYS "*" | wc -l

三、大 Key 排查

3.1 查找大 Key

bash 复制代码
# ===== 查找大 Key 工具 =====

# 方法 1: redis-cli --bigkeys
redis-cli --bigkeys

# 输出示例:
# Scanning 100,000 keys in database 0
# Big key (string): key:string:large (10000000 bytes)
# Big key (hash): key:hash:users (5000000 bytes, 100000 fields)
# Big key (list): key:list:messages (3000000 bytes, 10000 items)
# Big key (set): key:set:tags (2000000 bytes, 50000 members)
# Big key (zset): key:zset:rankings (4000000 bytes, 30000 members)

# Big key (stream): key:stream:events (8000000 bytes, 20000 entries)

# 方法 2: memory usage
redis-cli --scan | head -100 | xargs -I {} redis-cli MEMORY USAGE {}

# 方法 3: SCAN + MEMORY
redis-cli SCAN 0 MATCH "*" COUNT 1000 | while read key
do
    size=$(redis-cli MEMORY USAGE "$key" 2>/dev/null)
    if [ "$size" -gt 1048576 ]; then  # > 1MB
        echo "$key: $size bytes"
    fi
done

# 方法 4: Python 脚本
```python
import redis
import sys

r = redis.Redis(host='localhost', port=6379, db=0)

def find_big_keys(threshold_bytes=10485760):
    """查找大于 threshold_bytes 的 key"""
    big_keys = []
    
    cursor = 0
    while True:
        cursor, keys = r.scan(cursor, count=1000)
        
        for key in keys:
            key_type = r.type(key).decode()
            if key_type == 'string':
                size = r.memory_usage(key) or 0
            elif key_type in ('list', 'set', 'zset', 'hash', 'stream'):
                size = r.memory_usage(key) or 0
            else:
                size = 0
            
            if size > threshold_bytes:
                big_keys.append({
                    'key': key.decode(),
                    'type': key_type,
                    'size': size,
                    'size_mb': round(size / 1048576, 2)
                })
        
        if cursor == 0:
            break
    
    # 按大小排序
    big_keys.sort(key=lambda x: x['size'], reverse=True)
    
    # 输出
    print(f"Found {len(big_keys)} big keys (> {threshold_bytes / 1048576}MB)")
    print("-" * 80)
    for item in big_keys[:20]:
        print(f"{item['key']}: {item['type']}, {item['size_mb']}MB")
    
    return big_keys

if __name__ == '__main__':
    threshold = int(sys.argv[1]) if len(sys.argv) > 1 else 10485760  # 默认 10MB
    find_big_keys(threshold)

3.2 分析 Key 类型

bash 复制代码
# ===== 分析 Key 类型分布 =====

# 方法 1: redis-cli --type
redis-cli --type string | wc -l  # String 类型数量
redis-cli --type hash | wc -l    # Hash 类型数量
redis-cli --type list | wc -l    # List 类型数量
redis-cli --type set | wc -l     # Set 类型数量
redis-cli --type zset | wc -l    # Zset 类型数量

# 方法 2: 统计每种类型数量和内存
redis-cli INFO keyspace

# 输出示例:
# db0:keys=100000,expires=50000,avg_ttl=3600000

# 方法 3: 使用 TYPE 统计
redis-cli --scan | xargs -I {} redis-cli TYPE {} | sort | uniq -c

# 方法 4: Python 统计
```python
import redis
from collections import Counter

r = redis.Redis(host='localhost', port=6379, db=0)

def analyze_key_types():
    """统计 Key 类型分布"""
    type_counts = Counter()
    cursor = 0
    
    while True:
        cursor, keys = r.scan(cursor, count=1000)
        for key in keys:
            key_type = r.type(key).decode()
            type_counts[key_type] += 1
        
        if cursor == 0:
            break
    
    print("Key Type Distribution:")
    print("-" * 40)
    for key_type, count in type_counts.most_common():
        print(f"{key_type}: {count}")
    
    return type_counts

analyze_key_types()

3.3 大 Key 危险操作

bash 复制代码
# ===== 大 Key 危险操作警告 =====

# ❌ 危险操作:这些命令会阻塞 Redis
# KEYS pattern          # 全表扫描,阻塞所有请求
# FLUSHDB / FLUSHALL    # 清空数据库,阻塞所有请求
# SMEMBERS key          # 获取 Set 所有成员,可能阻塞
# LRANGE key 0 -1       # 获取 List 所有元素,可能阻塞
# HGETALL key           # 获取 Hash 所有字段,可能阻塞
# DEL key               # 删除大 Key,阻塞主线程

# ✅ 安全操作:异步删除
# UNLINK key            # 异步删除,不阻塞
# UNLINK key1 key2 key3 # 批量异步删除

# ✅ 安全操作:渐进式删除
# SCAN + DEL

# ✅ 安全操作:渐进式遍历
# SCAN cursor [MATCH pattern] [COUNT count]
# SSCAN key cursor [MATCH pattern] [COUNT count]
# HSCAN key cursor [MATCH pattern] [COUNT count]
# ZSCAN key cursor [MATCH pattern] [COUNT count]

3.4 大 Key 安全删除

python 复制代码
# ===== 大 Key 安全删除脚本 =====

import redis
import time

r = redis.Redis(host='localhost', port=6379, db=0)

def safe_delete_key(key, batch_size=1000):
    """安全删除大 Key"""
    key_type = r.type(key).decode()
    
    if key_type == 'none':
        print(f"Key {key} does not exist")
        return
    
    print(f"Deleting key: {key}, type: {key_type}")
    
    if key_type == 'string':
        # String 类型直接删除
        r.unlink(key)
        print(f"Deleted {key}")
    
    elif key_type == 'list':
        # List 类型:从右弹出直到为空
        while r.llen(key) > 0:
            r.ltrim(key, 0, -batch_size-1)
        r.unlink(key)
        print(f"Deleted {key}")
    
    elif key_type == 'set':
        # Set 类型:批量删除成员
        cursor = 0
        while True:
            cursor, members = r.sscan(key, cursor, count=batch_size)
            if members:
                r.srem(key, *members)
            if cursor == 0:
                break
        r.unlink(key)
        print(f"Deleted {key}")
    
    elif key_type == 'hash':
        # Hash 类型:批量删除字段
        while r.hlen(key) > 0:
            cursor = 0
            fields_to_delete = []
            while len(fields_to_delete) < batch_size:
                cursor, fields = r.hscan(key, cursor, count=100)
                fields_to_delete.extend(fields.keys())
                if cursor == 0:
                    break
            if fields_to_delete:
                r.hdel(key, *fields_to_delete[:batch_size])
        r.unlink(key)
        print(f"Deleted {key}")
    
    elif key_type == 'zset':
        # Zset 类型:批量删除
        while r.zcard(key) > 0:
            r.zremrangebyrank(key, 0, batch_size-1)
        r.unlink(key)
        print(f"Deleted {key}")
    
    elif key_type == 'stream':
        # Stream 类型:删除所有条目
        while r.xlen(key) > 0:
            r.xtrim(key, maxlen=0, approximate=False)
        r.unlink(key)
        print(f"Deleted {key}")


def batch_delete_keys(pattern, batch_size=1000):
    """批量删除匹配 pattern 的 Key"""
    cursor = 0
    deleted = 0
    
    while True:
        cursor, keys = r.scan(cursor, match=pattern, count=batch_size)
        
        if keys:
            # 使用 UNLINK 异步删除
            r.unlink(*keys)
            deleted += len(keys)
            print(f"Deleted {len(keys)} keys, total: {deleted}")
        
        if cursor == 0:
            break
    
    print(f"Total deleted: {deleted} keys")
    return deleted

四、内存分析工具

4.1 Redis 内置命令

bash 复制代码
# ===== 内存分析命令 =====

# 1. MEMORY USAGE - 查看 Key 内存占用
redis-cli MEMORY USAGE user:12345
# 返回字节数

# 2. MEMORY STATS - 查看详细内存统计
redis-cli MEMORY STATS

# 输出示例:
# peak.allocated: 1048576000
# total.allocated: 524288000
# startup.allocated: 10485760
# replication.allocated: 0
# clients.allocated: 1048576
# aof.allocated: 0
# overhead.allocated: 5242880
# client.normal.size: 1048576
# client.slaves.size: 0
# aof.buffer.size: 0

# 3. MEMORY DOCTOR - 内存诊断
redis-cli MEMORY DOCTOR

# 输出建议:
# 1. Peak memory: 1GB
# 2. High memory fragmentation: consider using MEMORY PURGE
# 3. Consider using smaller keys or memory-efficient data types

# 4. OBJECT ENCODING - 查看编码方式
redis-cli OBJECT ENCODING user:12345
# int: 整数
# embstr: 短字符串
# raw: 长字符串
# hashtable: Hash
# skiplist: Zset
# quicklist: List

# 5. OBJECT FREQ - 查看 Key 访问频率
redis-cli OBJECT FREQ hot:key:12345

4.2 RDB 离线分析

bash 复制代码
# ===== RDB 离线分析 =====

# 方法 1: redis-rdb-tools
# 安装: pip install rdbtools

# 导出内存分析报告
rdb --command memory /var/redis/dump.rdb -c json > memory_report.json

# 查看 Top N 大 Key
rdb --bigkeys /var/redis/dump.rdb

# 导出 CSV 格式
rdb -c memory /var/redis/dump.rdb > memory.csv

# 方法 2: 分析 CSV
cat memory.csv | head -20

# key,database,type,size_in_bytes,len
# user:1000,0,string,1048576,1048576
# session:abc123,0,hash,524288,100

# 方法 3: Python 分析脚本
```python
import rdbtools
from rdbtools.parser import RdbParser
import sys

def analyze_rdb(rdb_file):
    """分析 RDB 文件"""
    
    parser = RdbParser()
    stats = parser.parse(rdb_file)
    
    # Top 20 大 Key
    print("Top 20 Big Keys:")
    print("-" * 80)
    
    sorted_keys = sorted(stats.key_sizes.items(), 
                        key=lambda x: x[1], reverse=True)[:20]
    
    for key, size in sorted_keys:
        size_mb = size / 1048576
        print(f"{key}: {size_mb:.2f}MB")
    
    # 类型分布
    print("\nKey Type Distribution:")
    print("-" * 40)
    type_counts = {}
    for key, key_type in stats.key_types.items():
        type_counts[key_type] = type_counts.get(key_type, 0) + 1
    
    for key_type, count in sorted(type_counts.items(), 
                                 key=lambda x: x[1], reverse=True):
        print(f"{key_type}: {count}")

analyze_rdb(sys.argv[1])

4.3 在线监控工具

python 复制代码
# ===== 在线内存监控脚本 =====

import redis
import time
import psutil
from datetime import datetime

r = redis.Redis(host='localhost', port=6379, db=0)

def monitor_memory(interval=5, duration=300):
    """内存监控"""
    start_time = time.time()
    prev_memory = None
    
    print("Redis Memory Monitor")
    print("=" * 80)
    print(f"{'Time':<20} {'Used':<15} {'Peak':<15} {'RSS':<15} {'Frag':<10}")
    print("-" * 80)
    
    while time.time() - start_time < duration:
        info = r.info('memory')
        
        now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        used = info['used_memory_human']
        peak = info['used_memory_peak_human']
        rss = info['used_memory_rss_human']
        frag = f"{info['mem_fragmentation_ratio']:.2f}"
        
        # 计算增长率
        if prev_memory:
            growth = info['used_memory'] - prev_memory
            growth_rate = growth / (interval * 1024 * 1024)
            print(f"{now} {used:<15} {peak:<15} {rss:<15} {frag:<10} +{growth_rate:.2f}MB/s")
        else:
            print(f"{now} {used:<15} {peak:<15} {rss:<15} {frag:<10}")
        
        prev_memory = info['used_memory']
        time.sleep(interval)

def detect_memory_leak():
    """检测内存泄漏"""
    samples = []
    
    print("Memory Leak Detection (10 samples)")
    print("=" * 80)
    
    for i in range(10):
        info = r.info('memory')
        used = info['used_memory']
        samples.append(used)
        
        if i > 0:
            growth = used - samples[i-1]
            growth_mb = growth / 1048576
            trend = "↑" if growth > 0 else "↓"
            print(f"Sample {i+1}: {used/1048576:.2f}MB, change: {trend}{abs(growth_mb):.2f}MB")
        else:
            print(f"Sample {i+1}: {used/1048576:.2f}MB")
        
        time.sleep(2)
    
    # 分析趋势
    if samples[-1] > samples[0] * 1.1:
        print("\n⚠️ Warning: Memory is growing!")
    else:
        print("\n✓ Memory is stable")

# 运行监控
if __name__ == '__main__':
    import sys
    
    if len(sys.argv) > 1 and sys.argv[1] == 'leak':
        detect_memory_leak()
    else:
        monitor_memory(interval=5, duration=60)

4.4 Prometheus 监控

yaml 复制代码
# ===== Prometheus 监控配置 =====

# prometheus.yml
scrape_configs:
  - job_name: 'redis'
    static_configs:
      - targets: ['localhost:9121']  # redis_exporter

# redis_exporter 启动
# redis_exporter --redis.addr redis://localhost:6379
promql 复制代码
# ===== Prometheus 查询 =====

# 内存使用率
redis_memory_used_bytes / redis_memory_max_bytes * 100

# 内存增长率
rate(redis_memory_used_bytes[5m])

# 内存碎片率
redis_mem_fragmentation_ratio

# Key 数量
redis_db_keys

# 命令拒绝数
rate(redis_commands_processed_total{rejected="true"}[5m])

# 告警规则
groups:
  - name: redis-alerts
    rules:
      - alert: RedisOOM
        expr: redis_memory_used_bytes > redis_memory_max_bytes * 0.9
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Redis 内存使用率超过 90%"
          description: "Redis 内存使用率 {{ $value }}%,即将 OOM"
      
      - alert: RedisHighFragmentation
        expr: redis_mem_fragmentation_ratio > 1.5
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Redis 内存碎片率过高"
          description: "内存碎片率 {{ $value }}"

五、内存恢复方案

5.1 紧急恢复

bash 复制代码
# ===== 紧急恢复步骤 =====

# 1. 临时扩大内存(紧急)
redis-cli CONFIG SET maxmemory 2gb

# 2. 修改淘汰策略(紧急)
# 如果当前是 noeviction,改为 allkeys-lru
redis-cli CONFIG SET maxmemory-policy allkeys-lru

# 3. 手动清理内存
# 清理过期 key
redis-cli --scan --pattern "*:session:*" | xargs -I {} redis-cli EXPIRE {} 1
# 一分钟后过期

# 4. 执行内存碎片整理
redis-cli MEMORY PURGE

# 5. 清理大 Key
# 识别大 Key
redis-cli --bigkeys
# 删除不需要的大 Key
redis-cli UNLINK big:string:key
redis-cli UNLINK big:hash:users

# 6. 调整 AOF 持久化
# 如果使用 AOF rewrite,可能占用大量内存
redis-cli BGREWRITEAOF  # 异步执行,不会阻塞

# 7. 重启 Redis(最后手段)
redis-cli SHUTDOWN NOSAVE  # 不保存数据
# 启动 Redis
# redis-server /etc/redis/redis.conf

5.2 永久配置

bash 复制代码
# ===== 永久配置修改 =====

# 编辑 redis.conf
vim /etc/redis/redis.conf

# 设置合理的 maxmemory
# maxmemory 2gb
maxmemory 2147483648  # 2GB

# 设置淘汰策略
# maxmemory-policy allkeys-lru
maxmemory-policy allkeys-lru

# 开启内存碎片自动清理(Redis 4.0+)
# activedefrag yes
activedefrag yes

# 设置碎片清理阈值
# active-defrag-ignore-bytes 100mb
active-defrag-ignore-bytes 100mb
# active-defrag-threshold-lower 10
active-defrag-threshold-lower 10
# active-defrag-threshold-upper 100
active-defrag-threshold-upper 100

# 重启生效
systemctl restart redis

5.3 内存优化策略

bash 复制代码
# ===== 内存优化策略 =====

# 1. 使用高效编码
# 查看 Key 编码
redis-cli OBJECT ENCODING user:12345

# 设置小数字为 int 编码(自动)
SET user:123:score 100  # 自动使用 int 编码

# 2. 合理使用数据结构
# ❌ 不要存储大字符串
SET cache:page:123 "very long content..."

# ✅ 使用 Hash 代替多个 String
HSET cache:page:123 title "xxx" content "xxx" author "xxx"

# 3. 设置合理的过期时间
# 查看 TTL
redis-cli TTL user:12345

# 设置过期时间
EXPIRE user:session:123 3600  # 1小时

# 设置随机过期(避免 key 集中过期)
EXPIRE user:session:123 $((3600 + RANDOM % 600))  # 1小时 + 0-10分钟随机

# 4. 使用压缩(Redis 6.0+)
# 注意:压缩会消耗 CPU
SET key "large_content" KOMPRESS

# 5. 使用 Pipeline 减少连接数
redis-cli --pipe <<EOF
SET key1 value1
SET key2 value2
SET key3 value3
GET key1
EOF

5.4 客户端优化

python 复制代码
# ===== 客户端内存优化 =====

import redis

r = redis.Redis(host='localhost', port=6379, db=0)

# 1. 使用连接池
pool = redis.ConnectionPool(host='localhost', port=6379, max_connections=50)
r = redis.Redis(connection_pool=pool)

# 2. 使用 Pipeline 批量操作
pipe = r.pipeline()
for i in range(1000):
    pipe.set(f'user:{i}', f'value:{i}')
pipe.execute()

# 3. 使用 Scan 代替 KEYS
cursor = 0
while True:
    cursor, keys = r.scan(cursor, match='user:*', count=100)
    # 处理 keys
    if cursor == 0:
        break

# 4. 使用 UNLINK 代替 DEL
r.unlink('big:key:1', 'big:key:2', 'big:key:3')

# 5. 合理设置客户端超时
r = redis.Redis(host='localhost', port=6379, socket_timeout=5, 
                socket_connect_timeout=5)

# 6. 使用 Lua 脚本减少网络往返
LUA_SCRIPT = """
local key = KEYS[1]
local value = redis.call('GET', key)
if value then
    redis.call('DEL', key)
end
return value
"""

result = r.eval(LUA_SCRIPT, 1, 'key:name')

六、生产案例

6.1 案例:缓存雪崩

python 复制代码
# ===== 案例:缓存雪崩导致 OOM =====

# 问题:大量 key 同时过期,瞬间压力打到数据库
# 现象:Redis 内存突然增长,CPU 飙升

# 错误代码
def get_user(user_id):
    # 直接从 Redis 获取,没有就查数据库
    user = redis.get(f'user:{user_id}')
    if not user:
        user = db.query('SELECT * FROM users WHERE id = ?', user_id)
        redis.set(f'user:{user_id}', json.dumps(user))
    return json.loads(user)

# 问题:
# 1. 没有设置过期时间或过期时间相同
# 2. 同时过期导致雪崩
# 3. 数据库压力导致响应慢,Redis 连接堆积

# ===== 解决方案 =====

# 1. 随机过期时间
def get_user(user_id):
    user = redis.get(f'user:{user_id}')
    if not user:
        user = db.query('SELECT * FROM users WHERE id = ?', user_id)
        user_json = json.dumps(user)
        # 基础过期时间 + 随机偏移
        expire = 3600 + random.randint(0, 600)
        redis.setex(f'user:{user_id}', expire, user_json)
    return json.loads(user)

# 2. 多级缓存
def get_user(user_id):
    # 先从 L1 缓存获取(本地缓存)
    local_cache = app_context.local_cache
    if user_id in local_cache:
        return local_cache[user_id]
    
    # 再从 Redis 获取
    user = redis.get(f'user:{user_id}')
    if not user:
        user = db.query('SELECT * FROM users WHERE id = ?', user_id)
        user_json = json.dumps(user)
        redis.setex(f'user:{user_id}', 3600, user_json)
        local_cache[user_id] = json.loads(user_json)
        return local_cache[user_id]
    
    local_cache[user_id] = json.loads(user)
    return local_cache[user_id]

# 3. 分布式锁保护
def get_user(user_id):
    key = f'user:{user_id}'
    
    # 尝试获取锁
    lock_key = f'lock:{key}'
    lock = redis.set(lock_key, '1', nx=True, ex=10)
    
    if lock:
        try:
            user = db.query('SELECT * FROM users WHERE id = ?', user_id)
            user_json = json.dumps(user)
            redis.setex(key, 3600, user_json)
        finally:
            redis.delete(lock_key)
        return user
    
    # 没获取到锁,等待后重试
    time.sleep(0.1)
    return get_user(user_id)

6.2 案例:热 Key 打穿

python 复制代码
# ===== 案例:热 Key 打穿 Redis =====

# 问题:某个 key 被大量请求
# 现象:单 Key 内存占用过高,CPU 单核打满

# 错误代码
def get_hot_product(product_id):
    # 每次都查 Redis
    return redis.get(f'hot:product:{product_id}')

# 问题:
# 1. 单个 key 访问量巨大
# 2. Redis CPU 单线程,可能打满
# 3. 内存被这个 key 占满

# ===== 解决方案 =====

# 1. Key 分片
def get_hot_product_sharded(product_id):
    # 将单个 key 分成多个 shard
    shard_id = hash(product_id) % 10
    key = f'hot:product:{product_id}:shard:{shard_id}'
    return redis.get(key)

# 2. 本地缓存 + Redis
from functools import lru_cache

@lru_cache(maxsize=10000)
def get_hot_product_local(product_id):
    value = redis.get(f'hot:product:{product_id}')
    return value

def get_hot_product(product_id):
    # 先查本地缓存
    value = get_hot_product_local(product_id)
    if value:
        return value
    
    # 本地缓存没有,查 Redis
    value = redis.get(f'hot:product:{product_id}')
    if value:
        get_hot_product_local.cache_clear()
        get_hot_product_local(product_id)
    return value

# 3. 热点 Key 监控
def monitor_hot_keys():
    """监控热点 Key"""
    samples = {}
    
    while True:
        keys = redis.scan_iter(match='hot:*', count=100)
        for key in keys:
            # 记录访问频率
            access_count = redis.object('FREQ', key) or 0
            samples[key] = access_count
        
        # 分析热点
        hot_keys = sorted(samples.items(), key=lambda x: x[1], reverse=True)[:10]
        print(f"Hot keys: {hot_keys}")
        
        time.sleep(60)

6.3 案例:内存泄漏

python 复制代码
# ===== 案例:内存泄漏 =====

# 问题:内存持续增长,不回落
# 排查:没有设置过期时间,或者有引用未释放

# 错误代码
class SessionManager:
    def __init__(self):
        self.redis = redis.Redis()
    
    def create_session(self, user_id):
        session_id = str(uuid.uuid4())
        # 没有设置过期时间!
        self.redis.set(f'session:{session_id}', json.dumps({
            'user_id': user_id,
            'created_at': time.time()
        }))
        return session_id
    
    def delete_session(self, session_id):
        self.redis.delete(f'session:{session_id}')
    
    # 等等,delete_session 根本没被调用!

# 问题:
# 1. Session 没有设置过期时间
# 2. 删除逻辑有 bug,session 永远不会被清理
# 3. 内存只增不减

# ===== 解决方案 =====

# 1. 设置过期时间
def create_session(self, user_id):
    session_id = str(uuid.uuid4())
    self.redis.setex(f'session:{session_id}', 86400, json.dumps({  # 24小时
        'user_id': user_id,
        'created_at': time.time()
    }))
    return session_id

# 2. 使用 TTL 监控过期
def cleanup_expired_sessions(self):
    """清理过期 session"""
    cursor = 0
    deleted = 0
    
    while True:
        cursor, keys = self.redis.scan(cursor, match='session:*', count=100)
        
        for key in keys:
            ttl = self.redis.ttl(key)
            if ttl == -1:  # 没有过期时间
                self.redis.expire(key, 86400)  # 设置过期时间
        
        if cursor == 0:
            break
    
    return deleted

# 3. 使用 Sorted Set 管理过期
def create_session_zset(self, user_id):
    session_id = str(uuid.uuid4())
    now = time.time()
    
    # 使用 Sorted Set,score 为过期时间戳
    self.redis.zadd('active_sessions', {session_id: now + 86400})
    self.redis.hset(f'session:{session_id}', mapping={
        'user_id': user_id,
        'created_at': now
    })
    
    return session_id

def cleanup_expired_sessions_zset(self):
    """清理过期 session"""
    now = time.time()
    # 删除过期的 session
    expired = self.redis.zrangebyscore('active_sessions', '-inf', now)
    
    for session_id in expired:
        self.redis.delete(f'session:{session_id}')
        self.redis.zrem('active_sessions', session_id)
    
    return len(expired)

七、总结

7.1 排查流程

#mermaid-svg-u2Gp95jSo2WYgVKo{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-u2Gp95jSo2WYgVKo .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-u2Gp95jSo2WYgVKo .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-u2Gp95jSo2WYgVKo .error-icon{fill:#552222;}#mermaid-svg-u2Gp95jSo2WYgVKo .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-u2Gp95jSo2WYgVKo .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-u2Gp95jSo2WYgVKo .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-u2Gp95jSo2WYgVKo .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-u2Gp95jSo2WYgVKo .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-u2Gp95jSo2WYgVKo .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-u2Gp95jSo2WYgVKo .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-u2Gp95jSo2WYgVKo .marker{fill:#333333;stroke:#333333;}#mermaid-svg-u2Gp95jSo2WYgVKo .marker.cross{stroke:#333333;}#mermaid-svg-u2Gp95jSo2WYgVKo svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-u2Gp95jSo2WYgVKo p{margin:0;}#mermaid-svg-u2Gp95jSo2WYgVKo .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-u2Gp95jSo2WYgVKo .cluster-label text{fill:#333;}#mermaid-svg-u2Gp95jSo2WYgVKo .cluster-label span{color:#333;}#mermaid-svg-u2Gp95jSo2WYgVKo .cluster-label span p{background-color:transparent;}#mermaid-svg-u2Gp95jSo2WYgVKo .label text,#mermaid-svg-u2Gp95jSo2WYgVKo span{fill:#333;color:#333;}#mermaid-svg-u2Gp95jSo2WYgVKo .node rect,#mermaid-svg-u2Gp95jSo2WYgVKo .node circle,#mermaid-svg-u2Gp95jSo2WYgVKo .node ellipse,#mermaid-svg-u2Gp95jSo2WYgVKo .node polygon,#mermaid-svg-u2Gp95jSo2WYgVKo .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-u2Gp95jSo2WYgVKo .rough-node .label text,#mermaid-svg-u2Gp95jSo2WYgVKo .node .label text,#mermaid-svg-u2Gp95jSo2WYgVKo .image-shape .label,#mermaid-svg-u2Gp95jSo2WYgVKo .icon-shape .label{text-anchor:middle;}#mermaid-svg-u2Gp95jSo2WYgVKo .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-u2Gp95jSo2WYgVKo .rough-node .label,#mermaid-svg-u2Gp95jSo2WYgVKo .node .label,#mermaid-svg-u2Gp95jSo2WYgVKo .image-shape .label,#mermaid-svg-u2Gp95jSo2WYgVKo .icon-shape .label{text-align:center;}#mermaid-svg-u2Gp95jSo2WYgVKo .node.clickable{cursor:pointer;}#mermaid-svg-u2Gp95jSo2WYgVKo .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-u2Gp95jSo2WYgVKo .arrowheadPath{fill:#333333;}#mermaid-svg-u2Gp95jSo2WYgVKo .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-u2Gp95jSo2WYgVKo .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-u2Gp95jSo2WYgVKo .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-u2Gp95jSo2WYgVKo .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-u2Gp95jSo2WYgVKo .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-u2Gp95jSo2WYgVKo .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-u2Gp95jSo2WYgVKo .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-u2Gp95jSo2WYgVKo .cluster text{fill:#333;}#mermaid-svg-u2Gp95jSo2WYgVKo .cluster span{color:#333;}#mermaid-svg-u2Gp95jSo2WYgVKo div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-u2Gp95jSo2WYgVKo .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-u2Gp95jSo2WYgVKo rect.text{fill:none;stroke-width:0;}#mermaid-svg-u2Gp95jSo2WYgVKo .icon-shape,#mermaid-svg-u2Gp95jSo2WYgVKo .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-u2Gp95jSo2WYgVKo .icon-shape p,#mermaid-svg-u2Gp95jSo2WYgVKo .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-u2Gp95jSo2WYgVKo .icon-shape .label rect,#mermaid-svg-u2Gp95jSo2WYgVKo .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-u2Gp95jSo2WYgVKo .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-u2Gp95jSo2WYgVKo .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-u2Gp95jSo2WYgVKo :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是

OOM 发生
查看错误日志
检查内存使用
是否超过 maxmemory
检查淘汰策略
检查内存碎片
修改策略或扩大内存
执行 MEMORY PURGE
清理大 Key
恢复正常

7.2 排查命令速查表

命令 用途
INFO memory 查看内存使用概况
MEMORY STATS 详细内存统计
MEMORY USAGE key 查看 Key 内存占用
MEMORY DOCTOR 内存诊断
MEMORY PURGE 清理内存碎片
OBJECT ENCODING key 查看 Key 编码
--bigkeys 查找大 Key
SCAN 安全遍历 Key
UNLINK 异步删除 Key

7.3 解决方案汇总

问题 解决方案
内存溢出 扩大 maxmemory / 修改淘汰策略
大 Key 分片 / 删除 / 优化结构
内存碎片 MEMORY PURGE / activedefrag
Key 堆积 设置过期时间 / 清理过期 Key
缓存雪崩 随机过期 / 多级缓存 / 分布式锁
热 Key 分片 / 本地缓存 / 热点监控
内存泄漏 设置 TTL / 定期清理

7.4 最佳实践

实践 说明
合理设置 maxmemory 保留 20% 冗余
选择合适淘汰策略 根据业务选择
设置过期时间 防止 Key 永不过期
随机过期时间 避免雪崩
使用 UNLINK 异步删除大 Key
开启碎片自动清理 activedefrag yes
监控内存指标 Prometheus + Grafana
定期分析 Key 使用 rdb-tools

本文基于 Redis 7.0 编写。如有问题欢迎评论区讨论!

相关推荐
swordbob4 分钟前
Redis 3 大问题 + 5 大扩展问题
redis
ai_coder_ai23 分钟前
论 NoSQL 数据库技术及其应用
数据库·nosql
AOwhisky2 小时前
Redis 学习笔记(第一期):概述、安装配置与核心理论
运维·数据库·redis·笔记·学习·云计算
ytttr8732 小时前
C# 定时数据库备份工具
开发语言·数据库·c#
睡不醒男孩0308232 小时前
自建 Prometheus+Grafana 与 CLUP 深度监控 PG 集群有什么区别?
数据库·oracle
AOwhisky2 小时前
Redis 学习笔记(第四期):高可用与集群(哨兵 + Cluster + 容器化)
linux·运维·数据库·redis·笔记·学习·缓存
猫猫聚会Ing3 小时前
数据库设计 Prompt 提示词 - 构建与迭代
数据库
上海云盾-小余3 小时前
源站隐藏实战:规避裸 IP 被直接攻击的完整方案
数据库·网络协议·tcp/ip
叫我:松哥3 小时前
基于Flask框架的校园二手书籍交易平台,注重校园场景的特殊需求,通过学号认证保障用户真实性
后端·python·sqlite·flask·bootstrap
微学AI3 小时前
时序大模型 TimechoAI 赋能工业时序数据底层技术优势与实操
数据库·大模型·时序大模型