【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深度历险 核心原理与应用实践》