图解Redis之数据结构篇——简单动态字符串SDS

文章导航-readme

图解Redis之数据结构篇——简单动态字符串SDS

前言

    相信用过Redis的人都知道,Redis提供了一个逻辑上的对象系统构建了一个键值对数据库以供客户端用户使用。这个对象系统包括字符串对象,哈希对象,列表对象,集合对象,有序集合对象等。可是Redis面向内存并无直接使用这些对象。而是使用了简单动态字符串,链表,字典(散列表),跳跃表,整数集合,压缩列表这些数据结构来操做内存。html

Redis对象结构

系列文章

1、简单动态字符串(SDS)

    Redis默认并未直接使用C字符串(C字符串仅仅做为字符串字面量,用在一些无需对字符串进行修改的地方,如打印日志)。而是以Struct的形式构造了一个SDS的抽象类型。当Redis须要一个能够被修改的字符串时,就会使用SDS来表示。在Redis数据库里,包含字符串值的键值对都是由SDS实现的(Redis中全部的键都是由字符串对象实现的即底层是由SDS实现,Redis中全部的值对象中包含的字符串对象底层也是由SDS实现)。程序员

1.1 SDS

Redis简单动态字符串

struct sdshdr{
    //int 记录buf数组中未使用字节的数量 如上图free为0表明未使用字节的数量为0
    int free;
    //int 记录buf数组中已使用字节的数量即sds的长度 如上图len为5表明未使用字节的数量为5
    int len;
    //字节数组用于保存字符串 sds遵循了c字符串以空字符结尾的惯例目的是为了重用c字符串函数库里的函数
    char buf[];
}

2、为何要使用SDS

SDS与C字符串

    上图表示了SDS与C字符串的区别,关于为何Redis要使用SDS而不是C字符串,咱们能够从如下几个方面来分析。redis

2.1 缓冲区溢出

C字符串内存溢出

    C字符串,若是程序员在字符串修改的时候若是忘记给字符串从新分配足够的空间,那么就会发生内存溢出,如上图所示,忘记给s1分配足够的内存空间, s1的数据就会溢出到s2的空间, 致使s2的内容被修改.。而Redis提供的SDS其内置的空间分配策略则能够彻底杜绝这种事情的发生。当API须要对SDS进行修改时, API会首先会检查SDS的空间是否知足条件, 若是不知足, API会自动对它动态扩展, 而后再进行修改。数据库

Redis SDS字符串拼接

2.2 内存重分配

2.2.1 C字符串内存重分配

    在C字符串中,若是对字符串进行修改,那么咱们就不得不面临内存重分配。由于C字符串是由一个N+1长度的数组组成,若是字符串的长度变长,咱们就必须对数组进行扩容,不然会产生内存溢出。而若是字符串长度变短,咱们就必须释放掉再也不使用的空间,不然会发生内存泄漏。数组

2.2.2 SDS空间分配策略

    对于Redis这种具备高性能要求的内存数据库,若是每次修改字符串都要进行内存重分配,无疑是巨大的性能损失。而Redis的SDS提供了两种空间分配策略来解决这个问题。安全

  1. 空间预分配数据结构

    咱们知道在数组进行扩容的时候,每每会申请一个更大的数组,而后把数组复制过去。为了提高性能,咱们在分配空间的时候并非分配一个刚恰好的空间,而是分配一个更大的空间。Redis一样基于这种策略提供了空间预分配。当执行字符串增加操做而且须要扩展内存时,程序不只仅会给SDS分配必需的空间还会分配额外的未使用空间,其长度存到free属性中。其分配策略以下:并发

    • 若是修改后len长度将小于1M,这时分配给free的大小和len同样,例如修改事后为10字节, 那么给free也是10字节,buf实际长度变成了10+10+1 = 21byte
    • 若是修改后len长度将大于等于1M,这时分配给free的长度为1M,例如修改事后为30M,那么给free是1M.buf实际长度变成了30M+1M+1byte

  2. 惰性空间释放运维

    惰性空间释放用于字符串缩短的操做。当字符串缩短是,程序并非当即使用内存重分配来回收缩短出来的字节,而是使用free属性记录起来,并等待未来使用。函数

    Redis 惰性空间释放

Redis经过空间预分配和惰性空间释放策略在字符串操做中必定程度上减小了内存重分配的次数。但这种策略一样会形成必定的内存浪费,所以Redis SDS API提供相应的API让咱们在有须要的时候真正的释放SDS的未使用空间。

2.3 二进制安全

    C字符串中的字符必须符合某种编码(好比ASCII),而且除了字符串的末尾以外,字符串里面不能包含空字符,不然最早被程序读入的空字符将被误认为是字符串结尾,这些限制使得C字符串只能保存文本数据,而不能保存像图片、音频、视频、压缩文件这样的二进制数据。若是有一种使用空字符来分割多个单词的特殊数据格式,就不能用C字符串来表示,如"Redis\0String",C字符串的函数会把'\0'当作结束符来处理,而忽略到后面的"String"。而SDS的buf字节数组不是在保存字符,而是一系列二进制数组,SDS API都会以二进制的方式来处理buf数组里的数据,使用len属性的值而不是空字符来判断字符串是否结束。

2.4 时间复杂度

    咱们来看几个Redis常见操做的时间复杂度。

  1. 获取SDS长度: 因为SDS中提供了len属性,所以咱们能够直接获取时间复杂度为O(1),C字符串为O(n)。
  2. 获取SDS未使用空间长度: 时间复杂度为0(1),缘由同1。
  3. 清除SDS保存的内容:因为惰性空间分配策略,复杂度为O(1)。
  4. 建立一个长度为N的字符串:时间复杂度为O(n)。
  5. 拼接一个长度为N的C字符串:时间复杂度为O(n)。
  6. 拼接一个长度为N的SDS字符串:时间复杂度为O(n)。

Redis在获取字符串长度上的时间复杂度为常数级O(1)。

2.5 为何要使用SDS

    经过以上分析,咱们能够获得,SDS这种数据结构相对于C字符串有如下优势:

  • 杜绝缓冲区溢出
  • 减小字符串操做中的内存重分配次数
  • 二进制安全
  • 因为SDS遵循以空字符结尾的惯例,所以兼容部门C字符串函数

Redis定位于一个高性能的内存数据库,其面向的就是大数据量,大并发,频繁读写,高响应速度的业务。所以在保证安全稳定的状况下,性能的提高很是重要。而SDS这种数据结构屏蔽了C字符串的一些缺点,能够提供安全高性能的字符串操做。

3、小结

    Redis在互联网项目中的应用愈来愈普遍,会用只是学习Redis中最简单的一步,要想真正的成为Redis高手,了解其底层的实现必不可少。本篇文章简单介绍了Redis中SDS数据结构及其特性,分析了Redis SDS的空间分配策略和其与C字符串相比的优点,后续的文章将继续分享Redis底层实现的其它数据结构。未完待续......

4、参考

《Redis设计与实现》

《Redis开发与运维》

《Redis官方文档》

相关文章
相关标签/搜索