强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

12 - 内存管理

内存管理

12.1 内存使用分析

# 查看内存使用概况
redis-cli INFO memory

# 关键指标
redis-cli INFO memory | grep -E "used_memory_human|used_memory_peak_human|mem_fragmentation_ratio|mem_allocator"
# used_memory_human:2.50G
# used_memory_peak_human:3.20G
# mem_fragmentation_ratio:1.15
# mem_allocator:jemalloc-5.3.0

内存指标说明

指标说明正常范围
used_memoryRedis 分配器分配的内存总量
used_memory_rss操作系统看到的内存占用
mem_fragmentation_ratio内存碎片率(rss / used)1.0 - 1.5
used_memory_peak历史内存使用峰值
mem_allocator内存分配器jemalloc

⚠️ 注意:碎片率 > 1.5 说明内存碎片严重;< 1.0 说明使用了 swap,性能会急剧下降。

单个 Key 内存分析

# 查看 Key 的内存占用
MEMORY USAGE user:1001
# (integer) 128

# 采样估算(大 Key 推荐)
MEMORY USAGE user:1001 SAMPLES 100

12.2 内存淘汰策略

当内存达到 maxmemory 限制时,Redis 根据配置的淘汰策略决定如何处理新写入。

八种淘汰策略

策略范围算法说明
noeviction不淘汰任何 Key,新写入返回错误
allkeys-lru所有 KeyLRU淘汰最近最少使用的 Key
allkeys-lfu所有 KeyLFU淘汰最不经常使用的 Key
allkeys-random所有 Key随机随机淘汰 Key
volatile-lru有 TTL 的 KeyLRU淘汰有 TTL 的最近最少使用 Key
volatile-lfu有 TTL 的 KeyLFU淘汰有 TTL 的最不经常使用 Key
volatile-ttl有 TTL 的 KeyTTL淘汰 TTL 最小的 Key
volatile-random有 TTL 的 Key随机随机淘汰有 TTL 的 Key

策略选择决策树

所有 Key 都是缓存吗?
├── 是 → allkeys-lru / allkeys-lfu
│        │
│        ├── 热点数据明显 → allkeys-lfu
│        └── 访问均匀 → allkeys-lru
│
└── 否(混合缓存和持久数据)
    └── volatile-lru / volatile-lfu
         │
         ├── 缓存有 TTL → volatile-lru
         └── 需要更精确 → volatile-lfu
# 查看当前淘汰策略
redis-cli CONFIG GET maxmemory-policy

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

# 设置最大内存
redis-cli CONFIG SET maxmemory 4gb

LRU vs LFU

特性LRULFU
全称Least Recently UsedLeast Frequently Used
淘汰依据最近最少访问访问频率最低
计数器访问时间戳访问频率计数
适合场景访问模式稳定有明显热点数据
冷数据问题不会(看时间)不会(看频率)

LFU 算法使用对数计数器(logarithmic counter),并随时间衰减:

# 查看 Key 的 LFU 访问频率
OBJECT FREQ user:1001
# (integer) 15

# LFU 相关配置
redis-cli CONFIG SET lfu-log-factor 10      # 计数器增长速度
redis-cli CONFIG SET lfu-decay-time 1       # 衰减时间(分钟)

12.3 内存优化技巧

1. 使用 Hash 代替多个 String

# ❌ 低效:每个用户字段一个 Key
SET user:1001:name "张三"
SET user:1001:age "25"
SET user:1001:city "北京"

# ✅ 高效:使用 Hash 存储
HSET user:1001 name "张三" age "25" city "北京"

Hash 在元素较少时使用 listpack 编码,内存效率极高。

2. 使用整数集合(intset)

# Set 元素都是整数且数量少时,使用 intset 编码(极省内存)
SADD myset 1 2 3 4 5
OBJECT ENCODING myset   # "intset"

3. 控制 Key 长度

# ❌ 长 Key
SET user:profile:information:1001:basic "data"

# ✅ 短 Key
SET u:1001 "data"

# 使用 Hash Tag 缩短长度
SET {u:1001}:n "张三"

4. 合理设置过期时间

# 不需要永久保存的数据都设置 TTL
SET session:abc "data" EX 3600
SET cache:hot "data" EX 300

5. 压缩 Value

# 使用压缩算法(如 gzip、snappy、lz4)压缩 Value
# Python 示例
import gzip, json, redis

r = redis.Redis()

data = json.dumps({"large": "data..."})
compressed = gzip.compress(data.encode())
r.set("key", compressed, ex=3600)

# 读取时解压
raw = r.get("key")
original = gzip.decompress(raw).decode()
# DEL 大 Key 会阻塞
DEL big_key

# UNLINK 异步删除,不阻塞
UNLINK big_key

7. 整数共享池

Redis 内部维护了一个 0-9999 的整数对象池,使用这些整数时不会创建新对象:

# 这些整数共享同一个对象
SET counter 100
SET another 100
# 两个 Key 指向同一个 100 的 RedisObject

12.4 大 Key 检测

大 Key 是 Redis 性能杀手之一,可能导致慢查询、主从同步延迟、内存不均等问题。

大 Key 定义

类型大 Key 标准
StringValue > 10 KB
Hash/Set/ZSet元素数 > 5000 或 Value > 10 MB
List元素数 > 10000 或 Value > 10 MB

检测方法

方法一:redis-cli –bigkeys

# 扫描所有 Key,找出每种类型中最大的 Key
redis-cli --bigkeys

# 输出示例:
# [00.00%] Biggest string found so far 'key:123' with 10485760 bytes
# [25.00%] Biggest hash found so far 'user:all' with 50000 fields
# [50.00%] Biggest set found so far 'tags:all' with 100000 members
# [75.00%] Biggest zset found so far 'leaderboard' with 50000 members
# [100.00%] Biggest list found so far 'queue:tasks' with 100000 items

# 慢速扫描(降低对生产环境的影响)
redis-cli --bigkeys --i 0.1    # 每次 SCAN 后休眠 0.1 秒

方法二:MEMORY USAGE 采样

# 对特定 Key 检查内存
MEMORY USAGE big_key SAMPLES 100

方法三:SCAN + MEMORY USAGE 脚本

import redis

r = redis.Redis(host='localhost', port=6379, decode_responses=True)

BIG_KEY_THRESHOLD = 10240  # 10 KB

cursor = 0
big_keys = []

while True:
    cursor, keys = r.scan(cursor, count=100)
    for key in keys:
        try:
            mem = r.memory_usage(key, samples=100) or 0
            if mem > BIG_KEY_THRESHOLD:
                key_type = r.type(key)
                big_keys.append({
                    'key': key,
                    'type': key_type,
                    'memory': mem,
                })
                print(f"Big Key: {key} | Type: {key_type} | Memory: {mem} bytes")
        except Exception as e:
            print(f"Error checking {key}: {e}")
    
    if cursor == 0:
        break

print(f"\nFound {len(big_keys)} big keys")

方法四:RDB 文件离线分析

# 使用 rdb-tools 离线分析 RDB 文件
pip install rdbtools python-lzf

# 生成 CSV 报告
rdb --command memory /var/lib/redis/dump.rdb --bytes 10240 --keys 100 > memory_report.csv

# 按内存排序
sort -t',' -k4 -nr memory_report.csv | head -20

大 Key 治理

# 1. 删除大 Key(使用 UNLINK 异步删除)
UNLINK big_key

# 2. 拆分大 Key
# 大 Hash → 多个小 Hash
HSCAN big_hash 0 COUNT 100
# 将字段分散到多个 Hash

# 3. 压缩 Value
# 对大 Value 进行压缩存储

# 4. 设置过期时间
EXPIRE big_key 3600

12.5 内存碎片整理

Redis 4.0+ 支持在线内存碎片整理:

# 开启碎片整理
redis-cli CONFIG SET activedefrag yes

# 碎片整理配置
redis-cli CONFIG SET active-defrag-ignore-bytes 104857600   # 碎片达到 100MB 才开始整理
redis-cli CONFIG SET active-defrag-threshold-lower 10        # 碎片率达到 10% 开始整理
redis-cli CONFIG SET active-defrag-threshold-upper 100       # 碎片率达到 100% 全力整理
redis-cli CONFIG SET active-defrag-cycle-min 1               # 整理占用 CPU 最小比例
redis-cli CONFIG SET active-defrag-cycle-max 25              # 整理占用 CPU 最大比例

📌 业务场景

场景一:缓存内存告警

# 监控内存使用率
redis-cli INFO memory | grep used_memory_human
# 接近 maxmemory 时触发告警

场景二:热点数据识别

# 使用 LFU 策略,淘汰冷数据
redis-cli CONFIG SET maxmemory-policy allkeys-lfu

场景三:大 Key 导致延迟

# 定期扫描大 Key
redis-cli --bigkeys --i 0.1

# 发现大 Key 后拆分或异步删除

🔗 払展阅读