(一)Redis数据结构之SDS

1 前言

    在开始学习Redis时,除了知道Redis的用处之外,对于Redis的底层数据结构进行深入了解,显得尤为重要。

    如果不了解底层数据结构,也将错失体验Redis高性能的美妙之处。

 

2 什么是SDS

    SDS即简单动态字符串(Simple Dynamic String),是redis自定义的,用于存储字符串的数据结构。这里需要补充说明,C中的字符串只是以字面量的形式出现在redis的使用业务场景之中

 

3 SDS数据结构定义

    为了对SDS有更进一步的了解,还需要对其数据结构进行了解。查看Redis源码可知,SDS的数据结构的设计非常巧妙,具体如下

SDS的数据结构中有三个属性:

    1 len :表示buf中已经占用的空间,也就是SDS中实际占用了的字符串的长度

    2 free:表示buf中剩余可用空间的长度

    3 buf[] : 表示实际字符串存储的空间

    举个例子:

    a. 当free=0时,表示当前sds的buf[]没有任何未使用的数据空间

    b. free = 5 ,len = 5,表示带有5个未使用空间的SDS

    从上述数据结构可知,相对于C语言中String,SDS多了2个属性,len和free,用来标记buf[]中已用和尚未使用的空间长度。

    Redis作为一个Key-Value型的数据库,在读写方面性能有极高的要求。这样设置,使得Redis读取字符串长度所需要的时间复杂度从O(N)降低为O(1),同时也就完美解决了读取字符串长度时,时间复杂度为O(n)而引起的性能瓶颈。

    因为C语言在读取字符串长度时,计算过程如下

    而Redis则只需要直接读取len即可。

 

    注: SDS采用与C语言String一样的字符串结尾方式“\0” 这样的好处是能够复用一部分C语言自带的String函数

 

4 为什么要自定义SDS

    首先,先明确一点,Redis是用C语言进行编写,而C语言已经自带了字符串这一数据结构,那么既然已经自带了字符串,为何Redis还需要重新定义SDS?

理由如下:

    1 常数复杂度获取字符串长度: 从Redis的SDS数据结构上看,当客户端尝试获取某个字符串长度时,只需要开销O(1)的常数时间复杂度即可完成。而C语言中的String,在性能上存在诸多瓶颈,只能一个个去遍历计数,时间复杂度为O(N)。

    2 杜绝缓冲区溢出:C语言中的String,在对字符串进行写操作时,会存在缓冲区溢出。而Redis的SDS则不存在这样的问题,具体的原因,还是和SDS的数据结构有关。

由于SDS记录了字符串空间(buf[])的使用情况,因此当SDS API需要对SDS进行修改时,首先会检查SDS的空间是否满足所需的要求,如果不满足,则API会自动对SDS进行扩容,扩容至执行修改所需要的大小,而在C语言中,则需要手动扩容,极易发生缓冲区溢出的情况。

    3 二进制安全:C语言中的String,由于严格按照ASCII码的编码规范,所以只能读写文本类型数据,而无法存储图片等二进制文件,而这在Redis场景中将受到很大限制

    4 减少修改字符串长度时所需的内存重新分配:C语言中的String,在进行内存重分配时,性能较低,因为没有采用空间预分配策略,每次进行字符串拼接操作时,一旦空间不足,必然执行空间重分配操作,很消耗性能。(Redis采用空间预分配和惰性空间释放策略,使得空间分配从最少N次,变为最多N次)

对比C中的字符串,当C字符串需要增长或缩短时,程序总需要对C字符串的数组进行内存重新分配的动作,即假如有N次对C字符串的写操作,那么至少需要N次对内存进行重分配动作。而主要的问题在于:内存分配是一个比较耗时的操作,其中涉及较为复杂的算法并可能需要执行系统调用(用户态和系统态的切换等),操作一旦频繁,则会对性能产生严重影响。

基于以上情况,SDS实现了空间预分配和惰性空间释放两种优化策略。

 

1 空间预分配

    空间预分配用于优化SDS的字符串增长操作:当SDS API对一个SDS进行修改,并需要对SDS进行扩容时,程序会同时为SDS分配额外的未使用空间。

    具体规则如下:

    当len < 1MB 时,程序会为当前SDS分配和len一样大小的free。

      例如:经过修改后,len = 45字节,则SDS的buf[]数组实际长度为 len + free + 1 = 91字节(多出的1字节,是'\0',用于字符串结束标志,这样设计的目的是为了复用部分的C字符串API)

    当len > 1MB时,程序会为当前SDS分配固定1MB长度的free

      例如:经过修改后,len = 30MB,则SDS的buf[] 实际长度 = len + free + 1 byte 即 30MB + 1MB + 1byte

      Redis采用空间预分配的策略,使得在增加SDS操作的场景下,修改SDS的次数从原先C字符串的最少N次变为最多N次

 

2 惰性空间释放

    有对SDS增长操作的优化策略,那必然也会有对SDS缩短操作的策略,而惰性空间释放,就是Redis所采用的策略。

    原理很简单,即当去除SDS中的部分字符时,多余的空间不会被立马回收,而是会进行保留。与此同时,SDS也提供了相应的API,在有需要时,真正释放SDS未使用的空间,所以不用担心内存浪费。

 

5 兼容部分C字符串函数。

    SDS的API都是二进制安全的,但SDS同样遵循C字符串以空字符结尾的惯例,并总会在为buf[]数组分配空间时,多分配一个字节来容纳空字符,以便重用<string.h>库中定义的函数,从而避免不必要的代码重复。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值