42张图,带你真正搞懂redis数据类型的底层( 三 )


SDS 的定义动态字符串的结构:/** 保存字符串对象的结构*/struct sdshdr {// buf 中已占用空间的长度int len;// buf 中剩余可用空间的长度int free;// 数据空间char buf[];};SDS长这样:

42张图,带你真正搞懂redis数据类型的底层

文章插图
 
SDS示例
  • len 变量,用于记录buf 中已经 使用的空间长度 (这里指出Redis的长度为5);
  • free 变量,用于记录buf修改后的还有 空余的空间,一般初次分配空间的时候,是没有空余的,在对字符串修改的时候,就会有剩余空间出现;
  • buf 字符数组,用来记录 我们的字符串 (记录Redis)
SDS 与 C 字符串的区别那么传统的C字符串使用长度为 N+1的字符串数组 来表示 长度为N的字符串,所以为了获取一个长度为C字符串的长度,必须遍历整个字符串 。
这样做在获取字符串长度的时候,字符串扩展等操作的时候 效率比较低。C语言用这种 简单 的字符串表示方式,但是并不能满足Redis对字符串在安全性、效率以及功能方面的要求:
获取字符串长度(SDS O(1)/C 字符串 O(n))“
和C 字符串不同,SDS的数据结构中,有专门用于 保存字符串长度 的变量,我们可以通过获取len属性的值,如下图的,直接知道字符串长度:
42张图,带你真正搞懂redis数据类型的底层

文章插图
 

杜绝缓冲区溢出C 字符串是不会记录字符串长度的,除了获取的时候复杂度高以外,还容易导致缓冲区溢出 。我们现在假设程序中有两个在内存中 紧邻着的字符串s1和s2,其中s1保存了字符串“ redis ”,而s2 则保存了字符串“ MongoDb ”:
42张图,带你真正搞懂redis数据类型的底层

文章插图
 
如果我们现在将s1的内容修改为 redis cluster,但是我又忘了重新为 s1分配足够的空间,这时候就会出现以下问题:
42张图,带你真正搞懂redis数据类型的底层

文章插图
 
我们可以看到,原本s2中的内容已经 被S1的给占领 了,s2现在是cluster,而不是“Mongodb” 。Redis 中SDS 的 空间分配策略 完全 杜绝 了发生缓冲区溢出的可能性:

我们需要对一个SDS进行修改的时候,redis会在执行 拼接操作 之前,预先检查给定SDS空间是否是足够的,如果不够,会先 拓展SDS 的空间,然后再执行拼接操作;

42张图,带你真正搞懂redis数据类型的底层

文章插图
 

42张图,带你真正搞懂redis数据类型的底层

文章插图
 
减少修改字符串时带来的内存重分配次数C字符串在进行字符串的扩充和收缩的时候,都常常会面临着 内存空间 重新分配的问题 。
  • 字符串拼接会产生字符串的内存空间的 扩充,在拼接的过程中,原来的字符串的大小很可能 小于 拼接后的字符串的大小,那么这样的话,就会导致一旦 忘记申请 分配空间,就会导致内存的溢出 。
  • 字符串在进行收缩的时候,内存空间会 相应的收缩,而如果在进行字符串的切割的时候,没有对内存的空间进行一个重新分配,那么这部分多出来的空间就成为了内存泄露 。*
下面我们对SDS进行拓展,那就需要进行 空间的拓展,redis会将SDS的长度修改为13字节,并且将未使用空间同样修改成1字节;
42张图,带你真正搞懂redis数据类型的底层

文章插图
 

42张图,带你真正搞懂redis数据类型的底层

文章插图
 
因为在上一次修改字符串的时候已经拓展了空间,再次进行修改字符串的时候你会发现空间是足够使用,因此就不要进行空间拓展了 。
42张图,带你真正搞懂redis数据类型的底层

文章插图
 
通过这种 预分配策略,SDS将连续增长N次字符串所需的 内存重分配次数 从必定N次会降低为最多N次
惰性空间释放我们在观察SDS的结构的时候,可以看到里面有free属性,是用于 记录空余空间 的 。我们除了在拓展字符串的时候会使用到free来进行记录空余空间以外,在对字符串进行收缩的时候,我们也可以使用free 属性来进行 记录剩余空间,这样做的好处就是避免下次对字符串进行再次修改的时候,我们 再对 字符串的空间进行拓展 。


推荐阅读