Redis 内存模型基础
Redis 作为内存数据库,其性能与内存管理息息相关。理解 Redis 的内存数据结构、消耗构成以及碎片化机制,是进行缓存容量规划、性能调优和故障排查的前提。本章将深入剖析 Redis 的内存模型,为后续的淘汰策略与命中率测试奠定理论基础。
2.1 Redis 内存数据结构
Redis 对外暴露了五种核心数据类型,但底层实际使用了 8 种不同的高效数据结构来实现。这些底层结构的选择直接影响内存占用和操作时间复杂度。
| 外部类型 | 底层编码(数据结构) | 触发条件 | 内存效率 |
|---|---|---|---|
| String | int / embstr / raw | 整数 / 短字符串(≤44字节) / 长字符串 | 极高(int 无额外开销) |
| List | quicklist(ziplist + linkedlist) | 默认使用,兼顾内存与性能 | 高(节点压缩存储) |
| Hash | ziplist / hashtable | 字段数 < 512 且每个字段值 < 64 字节时用 ziplist | 极高(ziplist 连续内存) |
| Set | intset / hashtable | 所有元素为整数且数量 < 512 时用 intset | 极高(intset 有序紧凑数组) |
| Sorted Set | ziplist / skiplist + dict | 元素数 < 128 且每个元素 < 64 字节时用 ziplist | 高(ziplist 节省指针开销) |
面试高频点: 当 Hash 或 Sorted Set 使用 ziplist 编码时,内存占用可降低 60% 以上,但修改操作会触发编码转换(升级为 hashtable 或 skiplist),转换过程是 O(n) 的,需注意大 Key 的突发延迟。
2.2 内存消耗构成
Redis 进程的内存消耗并非仅由存储的数据决定,还包括大量辅助结构。一个完整的 Redis 实例内存占用由以下四部分构成:
- 数据内存(主体): 存储键值对、过期字典、主从复制缓冲区等。通常占总内存的 70%–90%。
- 进程自身内存: Redis 主进程代码、共享对象(如整数池 0–9999)、Jemalloc 分配器元数据。
- 缓冲区内存: 客户端输入/输出缓冲区(client query buffer / output buffer)、复制积压缓冲区(repl_backlog)、AOF 重写缓冲区。
- 内存碎片: 由于内存分配器(Jemalloc)与数据生命周期不匹配导致的不可用空闲空间。
实战经验: 使用
INFO memory 命令查看 used_memory(数据内存)、used_memory_rss(实际物理内存)、mem_fragmentation_ratio(碎片率)。当碎片率 > 1.5 时,建议重启或使用 MEMORY PURGE 命令。
2.3 内存碎片与优化
内存碎片是 Redis 内存管理中不可忽视的问题。碎片产生的主要原因是 数据频繁增删 导致内存分配器无法有效复用释放的内存块。Jemalloc 分配器按固定大小类(如 8、16、32、64 字节等)分配内存,当存储的数据大小与分配器大小类不匹配时,就会产生内部碎片。
碎片产生场景
- 频繁更新 String 类型: 每次 SET 新值可能改变底层编码(int → raw),导致旧内存释放、新内存申请,产生碎片。
- 大量过期 Key 删除: 惰性删除或定时删除释放的内存块大小不一,难以被后续分配完全复用。
- 主从切换或 AOF 重写: 子进程退出后,父进程的内存布局可能被打乱,碎片率短期上升。
碎片优化策略
| 策略 | 操作方式 | 适用场景 | 风险 |
|---|---|---|---|
| 重启实例 | SAVE + SHUTDOWN + 启动 | 碎片率 > 1.8 且无法自动回收 | 停机时间,需主从切换 |
| MEMORY PURGE | 执行 MEMORY PURGE 命令 |
碎片率 1.5–1.8,在线操作 | 短暂阻塞(毫秒级) |
| 调整 Jemalloc 配置 | 启动参数 --jemalloc-bg-thread |
后台线程异步整理碎片 | 增加少量 CPU 开销 |
| 数据对齐写入 | 尽量使用固定大小(如 64 字节)的 Value | 缓存场景,Value 长度可控 | 灵活性降低 |
注意: 不要频繁执行
MEMORY PURGE,它本质上是强制内存分配器进行合并操作,在高并发写入场景下可能引起性能抖动。建议监控碎片率,仅在超过阈值时手动触发。
2.4 内存模型与淘汰策略的关联
Redis 的内存淘汰策略(如 allkeys-lru、volatile-ttl)直接作用于数据内存部分。理解内存模型有助于:
- 容量规划: 根据数据结构编码和碎片率估算实际可用内存,设置合理的
maxmemory。 - 淘汰效率: 使用 ziplist 编码的 Hash 在淘汰时需遍历整个列表,性能低于 hashtable;大 Key 会阻塞淘汰线程。
- 命中率优化: 碎片率过高会导致有效缓存空间减少,间接降低命中率。定期整理碎片可释放 5%–15% 的可用内存。
测试建议: 在性能测试前,先通过
MEMORY STATS 命令获取详细的内存分配情况,重点关注 allocator.allocated、allocator.active、allocator.resident 三个指标,它们分别代表分配器已分配、已使用、已驻留的内存,差值即为碎片。