Redis String原理
一、Redis 字符串存储分为两层逻辑
层级 | 说明 | 举例 |
---|---|---|
逻辑层(对象层) | Redis 把字符串包装成一个 redisObject ,这个对象有 type 和 encoding 字段 | type=string,encoding=int/embstr/raw |
物理层(底层实现) | 实际的字符串数据由 SDS(Simple Dynamic String) 存储 | SDS 结构定义在 sds.h |
也就是说:
String 类型的编码方式(int、embstr、raw)属于对象层;
SDS(sdshdr结构)属于物理层的存储结构。
二、Redis 3.2 前后 编码方式(type/encoding) 没有变化
三种编码方式在 Redis 5.0 前后是一致的:
encoding | 说明 | 使用场景 |
---|---|---|
int | 整数直接保存为 long long | 如 SET key 123 |
embstr | 字符串较短(≤ 44 字节),redisObject 和 SDS 一次性分配 | 如 SET key "hello" |
raw | 字符串较长(> 44 字节),redisObject 和 SDS 分开分配 | 长字符串、大文本 |
🔸 Redis 3.2 没有改动这些编码方式的逻辑。
它仍然会根据字符串的长度来判断使用 embstr
还是 raw
。
三、Redis 3.2 改动的其实是 SDS(Simple Dynamic String)结构实现
这一层属于 底层内存结构优化,不影响上层的 encoding
判定逻辑。
📜 Redis 3.2 之前的 SDS(单结构版)
早期版本使用固定结构:
struct sdshdr {int len; // 已使用字节数int free; // 剩余空间char buf[]; // 实际字符串
};
问题:
- 所有字符串都要占用 8 字节元数据(两个 int)
- 对于短字符串非常浪费内存(Redis 里很多 key 只有几字节)
📜 Redis 3.2 ~ 6.0 的 SDS(多结构分级优化)
Redis 引入了 多版本 SDS(sdshdr5/8/16/32/64):
struct sdshdr8 {//unit8_t固定宽度无符号整型,1字节,8表示位数uint8_t len;//len 存储的是 已使用的字节数uint8_t alloc;//alloc 存储的是 buf 数组总分配空间(包含已用 + 剩余空间)unsigned char flags;char buf[];
};
以及极致节省版:
struct sdshdr5 {unsigned char flags; // 高5位存len,低3位存类型char buf[];
};
1️⃣sdshdr5 为什么可以减少内存碎片化:
🔹内存碎片化来源
在动态内存分配中,碎片化主要有两种:
-
内部碎片(internal fragmentation)
- 已分配的内存块中,有未使用空间
- 例如:申请 2 字节,用了 16 字节内存 → 剩余 14 字节浪费
-
外部碎片(external fragmentation)
- 多个小块散落在内存中,无法连续使用
- 主要发生在大量不同大小对象频繁分配/释放时
在 Redis 里,大部分 key/value 是短字符串,如果用固定头部的 SDS(len+free = 8B):
- 2B 字符串 → 占用 10B(2B 数据 + 8B 头)
- 内部碎片占比 80%
- 如果有上百万条短字符串,这些浪费累积起来非常可观 → 导致内存碎片化
2️⃣ sdshdr5 的优化方式
🔹 核心思路:
把头部压缩到 1 字节,只用于极短字符串(≤31B)
- 高 5 位存 len,低 3 位存类型
- buf[] 直接存储字符串
- 总内存占用 = 头部 1B + 数据 1~31B
🔹 好处:
-
减少内部碎片
- 短字符串的头部从 8B → 1B
- 例如
"OK"
2B 数据,头部 1B → 总共 3B - 内部碎片从 8B → 1B,浪费内存大幅下降
-
增加连续分配空间
- 内存占用小 → 相同内存页可以存放更多对象
- Redis 对象多,连续存放更多对象 → 外部碎片减少
-
缓存友好
- 头部 + 数据占用内存更小 → CPU 缓存命中率高
- 频繁访问短字符串效率更高
3️⃣ 举例对比
字符串 | 旧版 SDS | sdshdr5 | 说明 |
---|---|---|---|
"OK" (2B) | 10B | 3B | 内部碎片减少 7B |
"Hello" (5B) | 13B | 6B | 内部碎片减少 7B |
"Redis" (5B) | 13B | 6B | 内存利用率提升 ~50% |
对百万级短 key,总共可以节省几十 MB 到几百 MB 内存,碎片化明显下降。
这种设计根据字符串长度动态选择合适的结构,从而:
- 小字符串用小结构(节省内存)
- 大字符串用大结构(支持更长长度)
🔹 这是 Redis 3.2 前后最大的底层变化之一,但它属于 SDS 层,而非对象层。
四、总结对比
层级 | Redis 2.6 ~ 3.0 | Redis 3.2~ | 是否变化 |
---|---|---|---|
编码方式 (encoding) | int / embstr / raw | int / embstr / raw | ❌ 无变化 |
SDS 实现结构 | 单一结构体(sdshdr) | 多层结构体(sdshdr5/8/16/32/64) | ✅ 有变化 |
影响 | 内存利用率低 | 小字符串节省内存 | ✅ 优化效果显著 |
✅ 总结一句话
Redis 3.2 没有改变字符串的编码方式(int/embstr/raw),
但 重构了 SDS 动态字符串的实现结构 —— 从固定结构变为多级结构(sdshdr5/8/16/32/64),实现了更精细的内存优化。