【SDS结构】
Redis中字符串的结构叫SDS,即Simple Dynamic String。它的结构是一个带长度信息的字节数组。

struct SDS<T>{
T capacity;
T len;
byte flags;
byte[] content;
}

1.capacity>=len
content中存储了真正的字符串内容,capacity和len表示分配数组的长度和字符串的实际长度。
如图所示,除了包含当前的len长度字符串的字节内容,还会冗余一部分空间为后续的追加准备:

但需要注意的是,创建字符串的时候,len=capacity,不会留冗余空间,因为绝大多数场景下,我们不会使用append操作来修改字符串。
2.扩容规则
之前提到过字符串的长度在1M以下是翻倍扩容,大于1M时扩容每次增加1M,到512M封顶。
当字符串长度增加时(append新内容),直接追加到冗余的空间,当冗余空间不够时,就会分配新数组,然后将旧的数组分配到新数组中。
3.T
SDS结构中使用了泛型,也是为了节省空间。当字符串比较短时,这里分配的记录长度的结构是byte(1个字节)或者short(2个字节)...不同长度的字符串用不同的结构体表示。

【embstr VS raw】
长度特别短时,使用embstr形式存储,而长度超过44个字节时,使用raw形式存储。

要解释为什么长度超过44个字节就要变换存储形式,我们要了解这两种存储形式的真正面貌。
先来看Redis对象头,这个对象头是所有的Redis对象都会有的结构,这个结构也是会占用空间的:

struct RedisObject{
int4 type; //4bits
int4 encoding; //4bits
int24 lru; //24bits
int32 refcount; //4bytes
void *ptr; //8bytes, 64-bit system
} robj;

不同对象具有不同的类型type(4bit)。同一个类型的type会有不同的存储形式encoding(4bit)。为了记录对象的LRU信息,使用了24个bit来记录信息。每个对象都有个引用计数,当引用计数为零时,对象就会被销毁,内存被回收。ptr指针将指向对象内容(body)的具体存储位置。这样一个RedisObject对象头结构需要占据16个字节的存储空间。
而SDS结构体(body)中,算capacity和len的类型为byte,flags类型也为byte所以就算byte[] content为0,最少也是3个字节,所以和对象头加起来最小空间占用为19个字节。
内存分配器jemalloc,tcmalloc等分配内存大小的单位是2的幂次方,所以为了能够容纳一个完整的最小embstr结构,最少分配32个字节。如果字符串稍微长一点,就是64个字节。整体超过64个字节,Redis就会认为这是一个大字符串,即使用raw结构存储。
而64个字节,除开头结构加body中的信息记录字段19个字节,另外content字符串数组的结尾必须为NULL占用1个字节,所以embstr结构中最多能够容纳44个字节的字符串。结构如图:

【参考】

《Redis深度历险 核心原理与应用实践》

 

 

posted on 2022-01-14 09:03  长江同学  阅读(145)  评论(0编辑  收藏  举报