Redis String 原理与设计

  • String 实现原理
  • 三种编码
  • 编码的转换
  • SDS 定义


String 实现原理

推荐书籍: Redis 设计与实现

推荐博客:

字符串是Redis最基本的数据类型,不仅所有key都是字符串类型,其它几种数据类型构成的元素也是字符串。注意字符串的长度不能超过512M。

为什么字符串长度不能超过 512M?

// 源码定义(检查字符串长度)
static int checkStringLength(redisClient *c, long long size) {
    if (size > 512*1024*1024) {
        addReplyError(c,"string exceeds maximum allowed size (512MB)");
        return REDIS_ERR;
    }
    return REDIS_OK;
}
三种编码
  1. int 编码:保存的是可以用 long 类型表示的整数值。
  2. raw 编码:保存长度大于44字节的字符串(redis3.2版本之前是39字节,之后是44字节)
  3. embstr 编码:保存长度小于44字节的字符串(redis3.2版本之前是39字节,之后是44字节)
127.0.0.1:6379> set str1 121
OK
127.0.0.1:6379> set str2 qweqweqwe
OK
127.0.0.1:6379> set str3 qweqweqweqweqweqweqweqdhjajdskasjhdiqweuyaasddsa
OK
127.0.0.1:6379> object encoding str1
"int"
127.0.0.1:6379> object encoding str2
"embstr"
127.0.0.1:6379> object encoding str3
"raw"

区别:

embstr 使用只分配一次内存空间(因此redisObject和sds是连续的),raw 需要分配两次内存空间(分别为redisObject和sds分配空间)

因此与raw相比,embstr的好处在于创建时少分配一次空间,删除时少释放一次空间,以及对象的所有数据连在一起,寻找方便。而embstr的坏处也很明显,如果字符串的长度增加需要重新分配内存时,整个redisObject和sds都需要重新分配空间,因此redis中的embstr实现为只读。

编码的转换

当 int 编码保存的值不再是整数,或大小超过了long的范围时,自动转化为raw。

对于 embstr 编码,由于 Redis 没有对其编写任何的修改程序(embstr 是只读的),在对embstr对象进行修改时,都会先转化为raw再进行修改,因此,只要是修改embstr对象,修改后的对象一定是raw的,无论是否达到了44个字节。

SDS 定义
struct sdshdr{
    // 记录 buf 数组中已使用字节的数量
    // 等于 SDS 所保存字符串的长度
    int len;
    // 记录 buf 数组中未使用字节的数量
    int free;
    // 字节数组,用于保存字符串
    char buf[];
}

为什么要有 len ?

  • 复杂度:在 C 中获取一个字符串的长度复杂度为 O(N),定义 len 后复杂度为 O(1);
  • 二进制安全:用 len 判断字符串结束,可以保存除文本数据以外的图片、音频等数据

在 C 中使用 ‘\0’ 来标志一个字符串的结束,不能保存空字符否则会被认为是字符串的结尾。如果有一个以空字符来分割单词的特殊特殊数据,则 C 只能读取第一个单词。

而 Redis 用 buf[] 存储二进制数据,以 len 来判断结束,不会出现以上问题

为什么要有 free ?

  1. 空间预分配

在 C 中分配空间是一个比较耗时的工作,但 Redis 作为缓存数据库,数据的频繁修改是常态,而每次增长字符串都需要执行一次内存重分配(C 语言字符串处理特性),对性能产生很大影响。

  • 修改后 SDS 长度小于 1MB
  • 分配和 len 属性同样大小的未使用空间,这时 len 和 free 的值相同,下次修改的值长度如果小于 free 就不用重新分配空间。此时 buf 长度:free + len + 1 Byte
  • 修改后 SDS 长度大于 1MB
  • 分配 1 MB 未使用空间,此时 buf 实际长度 1Mb + len + 1 Byte
  1. 惰性空间释放

当字符串缩短时,程序并不立即使用内存重分配来回收多余的空间,而是用 free 记录下来,以便以后使用。

SDS 提供了相应的 API 可以在我们需要的时候真正地释放 SDS 的未使用的空间。所以不必担心惰性空间释放策略会浪费内存。

为什么SDS 中 buf 数组仍然在一串字符串后面添加 ‘\0’ 标志 ?

SDS 中保存的文本数据可以根据这个标志调用 C 语言原有的函数(<string.h>),如 strcasecmp(sds->buf, “hello world”)进行字符串对比,strcat(c_string, sds->buf)进行字符串追加等。