Redis使用SDS的原因

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

说明:SDS为了能够使用部分C字符串函数,遵循了C字符串以空字符结尾的惯例,保存空字符的1字节不计算在SDSlen属性中,并且为空字符分配额外的1字节空间,以及添加空字符到字符串末尾等操作,都是由SDS函数自动完成的,可以说空字符对使用者是不可见的

  • 为什么使用SDS(simple dynamic string)
    举个简单例子:一个简单的C字符串 “Redis”
    在这里插入图片描述
    C语言使用这种简单的字符串表示方法,并不能满足Redis对字符串在安全性、效率性以及功能方面的要求。

**

1、常数复杂度获取字符串长度

**
C语言中实现字符串长度计数的复杂度为O(N)。
而反观SDS中,因为其结构中保存了len这一属性,所以获取SDS长度的复杂度仅为O(1)。
并且我们不需要手动修改len属性,设置和更新SDS长度的工作是由SDS的API在执行时自动完成的。

2、杜绝缓冲区溢出

C语言由于不记录自身长度,所以又带来一个问题:容易造成缓冲区溢出(buffer overflow)。
举个栗子!当我们拥有两个C字符串s1,s2。而恰好s1、s2的内存是紧挨着的,那么当对s1执行某些操作,例如字符串拼接函数strcat(char* dest,char* src),那么毫无疑问,s2的数据会被覆盖掉!这样就造成了数据的丢失!
示意图:
在这里插入图片描述
执行strcat(s1,“dlrow”);
在这里插入图片描述

所以,SDS与C字符串不同,由于其记录了长度,所以在API需要对SDS进行修改时,API会先检查SDS的空间是否满足修改所需要的空间,如果不满足,API会自动将SDS的空间扩展至执行修改所需的大小,然后才执行操作,因此避免了缓冲区溢出的问题!
依旧以上述例子为例:
当SDS执行 sdscat(s1,“dlrow”)时,首先检测到长度是否满足:
Redis使用SDS的原因_第1张图片
不满足,于是去拓展空间:
在这里插入图片描述
注意此处又为SDS分配了10字节未使用空间,并且拼接后的字符串也是10!this is not a coincidence!它与SDS的空间分配策略有关!

3、减少修改字符串时带来的内存重分配次数(接上点)

上点中说到C字符串可能发生缓冲区溢出,实际上,C字符串在处理增长或者缩短操作时,程序会对这个C字符串的数组进行一次内存重分配操作:
(1)如果程序执行的是增长字符串的操作,比如拼接操作(append),那么在执行这个操作之前,程序需要先通过内存重分配来扩展底层数组的空间大小----如果忘了这一步就会产生缓冲区溢出
(2)如果程序执行的是缩短字符串的操作,比如截断操作(trim),那么在执行这个操作之后,程序需要通过内存重分配来释放字符串不再使用的那部分空间----如果忘了这一步就会产生内存泄漏

而因为内存重分配设计复杂的算法,甚至可能需要执行系统调用,所以它通常是一个比较耗时的操作:
对于一般程序而言,对于修改字符串的长度的情况可能不太频繁,所以每次修改都执行一次内存重分配也还是可以接受的。
但是Redis作为数据库,经常被用于速度要求严苛、数据被频繁修改的场合,那么显然,如果每次都要执行一次内存重分配,开销简直惊为天人!

所以SDS对于这种情况的处理是:SDS的free属性!
SDS通过未使用空间解除了字符串长度和底层数组长度之间的关联:在SDS中,buf数组的长度不一定就是保存的字符长度加一(这个在之前的结构体中可以看出),数组里可以包含未使用的字节,而这些字节的数量就由SDS的free属性记录。
通过free属性(未使用空间),SDS实现了空间预分配和惰性空间释放两种优化策略!
(1)空间预分配
空间预分配用于优化SDS的字符串增长操作:当SDS的API对一个SDS进行修改,并且需要对SDS进行空间拓展时,程序不仅为SDS分配修改所需要的空间,还会为SDS分配额外的未使用空间,这与cache中的一些算法思想相类似,都是认为最近使用过的数据可能会被再次访问或者修改,于是便给其一个预留的未使用空间!
分配原则一般如下:
如果对SDS进行修改之后,SDS的长度(也就是len属性的值)将小于1MB,那么程序分配和len属性同样大小的未使用空间。反之,如果SDS的长度将大于1MB,那么程序会分配1MB的未使用空间。
(但是值得注意的是,SDS总占字节数 = len + free + 1,1是末尾空字符)
显然,通过使用空间预分配是可以有效减少内存重分配次数的。
如果不采用此预分配策略,当SDS需要连续增长N次时,内存重分配为N次,而应用此策略,则变成了最多N次!

(2)惰性空间释放
同(1)所述,当面对字符串缩短操作:如果没有特殊操作,SDS的API需要缩短SDS保存的字符串,那么就需要使用内存重分配来回收多余的字节。
而当我们有了free属性,可以将多余字节调配给free记录下来,并等待将来使用!这也就为将来可能存在的增长操作提供了优化!

4、二进制安全

Redis作为数据库,就必须确保能够在各种场景中使用,所以SDS的API都是二进制安全的!这里的各种各样场景指的是,保存的音频、图片、视频等等数据。
所以SDS的buf属性被称为字节数组——Redis保存的数据不是“字符”,而是二进制数据!

内容参考自《Redis设计与实现》

你可能感兴趣的:(数据库)