Redis——SDS解析

前言

SDS 在 Redis 中有着举足轻重的地位,Redis 没有使用 C 语言中提供的字符串,而是通过 SDS 存储字节序列。

它相比较与字符串有以下几个好处:

  1. SDS 使用二进制存储数据,不但可以存储字符串,还是可以存储图片、视频、其他二进制数据。
  2. SDS 的所有函数都是二进制安全的。
  3. SDS 可以自动扩容。
  4. SDS 可以直接获取字符串长度。

结构

首先我们看 SDS 在 redis 中是这么定义的:

typedef char *sds;

把 SDS 说的这么强大,居然只是一个 char 类型的指针!char 在 C 语言中占用 1 字节。那么说明通过 SDS 访问数据时,都是一个一个字节去访问的,这也说明为什么 SDS 存储的是字节序列。

Redis 为 SDS 定义了几种不同的结构体,用于表示不同大小的字符串:

它们的结构如下:

image.png

从图中可以看出,每一种结构可以记录字符串的长度都不相同:

类型 数据长度
sdshdr5 2^5 – 1
sdshdr8 2^8 – 1
sdshdr16 2^16 – 1
sdshdr32 2^32 – 1
sdshdr64 2^64 – 1

Redis 这么设计的原因是为了节省内存,假设有 2000 万个字符串,如果都可以使用 sdshdr8 存储下,那么使用 sdshdr8 就可以比 sdshdr16 节省 38 M,比 sdshdr32 节省 76 M,比 sdshdr64 节省 152 M。随着存储的字符串越来越多,能够节省的内存就会越多。

创建 SDS

_sdsnewlen(const void *init, size_t initlen, int trymalloc) 这是创建 SDS 的函数签名,它有三个参数:

  • init:需要存储的字符串
  • initlen:初始化的长度
  • trymalloc:是否尝试分配内存。如果开启的话,内存分配失败,则只会返回 null,错误由调用方处理。如果不开启,则会直接将错误输出并中断进程。

创建 SDS 总共分为 3 个步骤:

  1. 根据 initlen 确定需要使用的类型
  2. 申请内存,内存大小 = 类型头部大小 + 数据大小 + 1
  3. 在申请的内存中写入数据

例子

假设现在要创建一个字符串 “abcdefghijkabcdefghijkabcdefghijk”,_sdsnewlen("abcdefghijk", 33)

执行第一步,确定需要使用的类型,这里长度为 33,使用类型为 sdshdr8。

执行第二步,申请内存空间。sdshdr8 头部的大小为 3byte,数据大小为 33 byte,额外大小 1byte,总共需要申请 37 byte。

image.png

第三步,写入数据。

第一个字节写入的是数据的长度 len,长度为 33。

image.png

第二个字节写入申请的内存空间大小,这里跟 len 相等。

image.png

第三个字节写入 flag,因为我们是使用 sdshdr8,类型为 1,所以低三位是 1,高 5 位未使用。

image.png

紧接着就是写入数据,直接将 init 中的数据拷贝过去即可。

image.png

最后会写入一个 \0,这就是为什么会多申请一个字节的原因。

整个 sds 的内存空间

image.png

扩容

扩容有两种模式,贪婪模式和非贪婪模式。贪婪模式下,会预先分配更多的内存,便于后续使用;非贪婪模式,只会分配刚好足够使用的内存。

_sdsMakeRoomFor(sds s, size_t addlen, int greedy),有三个参数:

  • s,它指向原字符串
  • addlen,需要增加的长度
  • greedy,是否开启贪婪模式

执行步骤:

  1. 检查剩余可用空间是否大于 addlen,如果大于则直接返回,不需要进行扩容。

  2. 接着计算新的长度,计算公式:原字符串长度 + addlen = newlen

    1. 贪婪模式,会增大计算的 newlen。如果 newlen < 1M,则将 new * 2;如果 newlen >= 1M,则 newlen + 1M。
    2. 非贪婪模式,则直接使用上述计算的 newlen。
  3. 确定 newlen 需要使用的类型。每一种类型可以分配的字符串长度空间都是不一样的。

  4. 判断新类型与原类型是否相同

    1. 相同,则直接使用 realloc 重新分配内存空间。

    2. 不相同,则需要使用 malloc 申请新的空间。

      1. 拷贝原数据到新的内存空间,释放原内存空间
      2. 设置 flag 为新的类型
      3. 设置 len 字段
  5. 记录 alloc 字段。

realloc 与 malloc 的区别?

realloc 申请内存空间,会先查看后面是否有足够的内存空间可以使用,如果有则直接使用即可;如果没有,就会寻找到一块足够的内存空间,并将原地址的内存拷贝过来。

malloc 则是直接找到一块新的内存空间。

为什么需要判断新类型是否与原类型相同?因为如果类型相同,那么表示 len、alloc、flag 这三个属性占用的内存长度是相同,只需要修改内部的数据即可。如果类型不相同,那么这三个属性占用内存长度不同,如果直接在原内存空间中进行修改,会造成数据错乱。

例子

image.png

总结

  1. Redis 使用不同类型的结构去存储不同长度的字符串,目的是为了节省内存。
  2. Redis 会记录下存储字符串的长度,可以快速获取到字符串的长度,而不需要去遍历字符串。
  3. Redis 可以通过扩容,适配字符串长度的增加。

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYwopgWG' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片