Redis中的sdshdr的len和alloc那块的知识点详解
文章目录
- 核心比喻:一个可以伸缩的水瓶
- 场景一:创建一个新字符串
- 场景二:追加字符串(触发“空间预分配”)
- 场景三:再次追加字符串(利用空闲空间)
- 场景四:缩短字符串(惰性空间释放)
- 总结
我们用一个非常形象的比喻和几个实际的例子,来彻底搞懂
len
和
alloc
这两个概念。
核心比喻:一个可以伸缩的水瓶
想象一下,Redis 的一个字符串(SDS)就是一个“智能水瓶”,而 sdshdr
就是瓶身上的刻度标签。
len
(length): 代表瓶子里当前装了多少水。这是字符串的实际、有效长度。alloc
(allocation): 代表这个瓶子的最大容量。这是系统为这个字符串总共分配了多大的内存空间(不含头部和末尾的\0
)。- 空闲空间 (free space):
alloc - len
,就代表瓶子里还能装多少水,也就是预留的空闲内存。
关键点:瓶子的容量 (alloc
) 永远大于或等于瓶子里的水量 (len
)。
这个设计的核心目的,就是避免频繁地“换瓶子”。因为“换瓶子”(内存重分配)是一个非常耗时的操作。如果每次只加一滴水,你都要去找一个大小刚刚好的新瓶子,效率会非常低下。
场景一:创建一个新字符串
当你执行 SET mykey "hello"
时,Redis 创建了一个 SDS 字符串。
- 字符串 “hello” 的长度是 5。
- Redis 内部会创建一个
sdshdr
和一块内存来存放它。假设它选择sdshdr8
类型(头部占3字节)。 - 为了效率,它可能不会只分配刚刚好的5字节,但我们为了简化,先假设它就是这么做的。
内存状态:
len = 5
(瓶里有5个单位的水)alloc = 5
(瓶子总容量是5)- 内存布局大致如下:
此时,空闲空间[sdshdr8: len=5, alloc=5] ['h','e','l','l','o','\0']
alloc - len
为 0。瓶子是满的。
场景二:追加字符串(触发“空间预分配”)
现在,我们来追加内容,执行 APPEND mykey " world"
。
-
检查空间:
- 要追加的字符串 " world" 长度为 6。
- API 首先检查空闲空间:
alloc(5) - len(5) = 0
。 0 < 6
,空间不够!需要“换个大瓶子”(重新分配内存)。
-
执行空间预分配(The Magic):
- 新的字符串总长度将是
len(5) + 6 = 11
。 - Redis 不会只分配 11 字节的新空间,它会想:“你既然开始追加了,很可能等下还会再追加。”
- 于是它启动空间预分配策略:新分配的容量
alloc
会是 新长度的两倍 (在字符串总长小于1MB时)。 - 新的
alloc
=11 * 2 = 22
。 - Redis 会申请一块能容纳 22 个字符的新内存空间,并把旧内容 “hello” 和新内容 " world" 拷贝进去。
- 新的字符串总长度将是
内存状态更新:
len = 11
(现在瓶里有11个单位的水)alloc = 22
(但瓶子的总容量是22!)- 内存布局大致如下:
现在,[sdshdr16: len=11, alloc=22] ['h','e','l','l','o',' ','w','o','r','l','d','\0', ...11 bytes free... ]<---- len = 11 ----> <---- free = 11 ----><----------------- alloc = 22 ----------------->
alloc(22) - len(11) = 11
,我们有 11 个字节的空闲空间。
场景三:再次追加字符串(利用空闲空间)
我们继续追加,执行 APPEND mykey "!!!"
。
-
检查空间:
- 要追加的 “!!!” 长度为 3。
- API 检查空闲空间:
alloc(22) - len(11) = 11
。 11 >= 3
,空间足够!不需要重新分配内存!
-
原地修改:
- 直接在
buf
的末尾把 “!!!” 写进去。 - 只更新头部的
len
字段。
- 直接在
内存状态更新:
len = 11 + 3 = 14
(水变多了)alloc = 22
(瓶子还是那个瓶子,容量没变)- 内存布局大致如下:
这个操作非常快,因为它避免了最耗时的内存分配和数据拷贝。[sdshdr16: len=14, alloc=22] ['h','e','l','l','o',' ','w','o','r','l','d','!','!','!','\0', ...8 bytes free... ]<------ len = 14 ------> <---- free = 8 ----><------------------- alloc = 22 ------------------->
场景四:缩短字符串(惰性空间释放)
现在,我们把这个 key 的值改成一个很短的字符串,执行 SET mykey "Hi"
。
- Redis 会直接用 “Hi” 覆盖掉
buf
开头的内容。 - 然后,它只会更新
len
字段。
内存状态更新:
len = 2
(水变得很少)alloc = 22
(但瓶子还是那个大瓶子!)- 内存布局大致如下:
[sdshdr16: len=2, alloc=22] ['H','i','\0','l','o',' ','w','o','r','l','d', ...garbage data..., ...free space... ]<len=2> <----------------- free = 20 -----------------><------------------- alloc = 22 ------------------->
- 这被称为惰性空间释放。Redis 不会立即把多余的 20 字节空间还给操作系统。它会保留这些空间,因为你可能马上又会执行
APPEND
操作,这样就又可以利用上这些预留空间了。
总结
len
和 alloc
的设计哲学,是典型的用空间换时间的思想。
len
提供了 O(1)O(1)O(1) 时间复杂度的长度获取能力,并且是二进制安全的基础。alloc
配合空间预分配和惰性释放策略,极大地减少了内存重分配的次数,这是 Redis 字符串追加(APPEND)操作如此高效的关键所在。它将多次可能发生的、耗时的内存操作,均摊到了一次操作中。