Redis字符串键的底层原理

before

  • C语言基础
  • Redis基础

导入

redis的命令以下:redis

            set x "hello";
            get x;
            hello    

  Redis做为一种存储字符串的缓存结构,其具体实现是由C语言完成,在C语言中,字符串是经过字符数组实现的,即char[],那么Redis对于字符串的实现是否是也是基于字符数组吗?不是的,Redis对字符串的处理是经过SDS(Simple Dynamic String)实现的。算法

SDS介绍

  SDS(Simple Dynamic String)简单动态字符串,它是由C语言完成,以下是其具体实现segmentfault

Redis做为一种存储字符串的缓存结构,其具体实现是由C语言完成,在C语言中,字符串是经过字符数组实现的,即char[],那么Redis对于字符串的实现是否是也是基于字符数组吗?不是的,Redis对字符串的处理是经过SDS(Simple Dynamic String)实现的。数组

struct sdshdr{
    //记录buf数组已使用字节的数量
    //等于SDS所保存字符串的长度
    int length; 
    //记录buf数组未使用字节的数量
    int free;
    //buf数组
    char[] buf;
};
 
看看redis的示例:
    sdshdr
    free 0
    length 5
    buf     -->|'R'|'e'|'d'|'i'|'s'|'\0'|
解释:
        - free为0,表示这个SDS没有分配任何未使用的空间
        - length为5,表示这个SDS保存了一个长度为5的字符串    
        - buf数组中保存着“Redis”字符串

  SDS遵循C字符串以空字符串结尾的惯例,保存空字符串的1字节空间不计算在SDS的len属性之中。缓存

  再看看SDS的free不为0的状况:安全

   sdshdr
    free 3
    length 5
    buf      -->|'R'|'e'|'d'|'i'|'s'| | | |

  free的值为3,表示这个SDS分配了三个空闲的空间函数

SDS与字符串的区别

  C语言使用简单的字符串表示方式,并不能知足Redis对字符串在安全性,效率,以及功能方面的要求,SDS更使用Redis。性能

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

C字符串:
  由于C语言并不记录自身的长度信息,因此获取一个C字符串的长度,程序必须遍历整个字符串,对遇到的,每一个字符进行计数,直到遇到表明字符串结尾的空字符串为止,这个操做的复杂度为O(n)。优化

SDS:
  与C语言不一样的是,SDS结构中的属性length记录了SDS自己的长度,因此获取一个SDS长度的复杂度为O(1)。有人疑问那么SDS的length值是哪来的?这里的length值是SDS API在设置和更新SDS时自动完成的。编码

总结1:经过使用SDS而不是C字符串,Redis获取字符串长度的复杂度由O(N)降为O(1),这确保了字符串长度的获取的工做不会成为Redis的性能瓶颈。

@ 2杜绝缓冲区溢出

C字符串:
  因为C自身不记录字符串的长度带来一个问题是容易形成缓冲区溢出(buffer overflow)。在<string.h>/strcat函数中,能够将一个字符串拼接到另一个字符串的末尾。

`char *strcat(char *dest,const char *src)`

  理想状态下,用户在使用这个函数时,假定C为dest分配了足够多的内存,能够容纳src字符串中的全部内容,而一旦这个假定不成立,就会产生缓冲区溢出。举个例子,假定内存中有相邻的两个字符串s1,s2,如图:

    s1                        s2
     |                         |
...|'R'|'e'|'d'|'i'|'s'|'\0'||'g'|'o'|'o'|'d'|'\0'|...

  若是执行strcat(s1," cluster");将Redis改成”Redis cluster“,可是粗心的却忘了在执行这句以前为s1分配足够的空间,那么在执行以后,s1的数据将会溢出到s2所在的空间,致使s2保存的内容意外的被修改。

SDS:

  与C语言不一样的是,SDS空间分配政策彻底杜绝了发生缓冲区溢出的可能性:当SDS API须要对字符串进行修改时,首先会检查SDS的空间是否知足修改所需的要求,由于SDS自身有对字符串长度记录的属性length和空闲空间属性free,能够借助这两个参数进行检查。SDS会在执行动做以前判断SDS的空间大小,再去执行操做,若是空间不够的话,SDS API会自动扩展空间。

  

@ 3减小修改字符串时带来的内存重分配次数

C字符串:

  由于C字符串不记录自身长度,每次增加或者缩短字符串长度时,程序都要对这个C字符串数组进行一次内存从新分配操做,否则容易形成内存益出。由于内存,分配设计复杂的算法,而且可能须要执行系统调用,因此它一般是一个比较耗时和耗能的操做。可是Redis做为缓存,追求速度,因此不能常常发生内存分配操做。

SDS:

  SDS数组中的未使用空间字节数量由SDS的属性free记录,经过free记录,SDS实现了空间预分配和惰性释放两种优化策略。
1. 空间预分配
  空间预分配用于优化SDS的字符串增加操做:当SDS的API对一个SDS进行修改,而且须要对SDS的空间进行扩展时,程序不只会为SDS分配修改所须要的空间,并且还会为SDS分配额外的空间。额外的空间分配规则以下:

(1)若是修改SDS以后,SDS的长度小于1MB,那么程序会给SDS分配和length同样大的额外空间,这是SDSlength和free的值相等。举个例子,若是修改后的字符串长度为13k,那么SDS的空间将会占据13+13+1=27k(额外的一个字节用于保存空字符串)。

(2)若是修改SDS以后,SDS的长度大于1MB,那么程序会给SDS分配额外的1MB空间,举个例子,好比修改后的SDS有30MB的大小,那么程序会分配1MB的未使用空间,SDS的buf数组实际大小将是30MB+1MB+1byte。

2.惰性释放

  惰性释放用于优化SDS的字符串缩短操做:当SDS的API要缩短SDS保存的字符串时,程序并不须要当即使用内存重分配策略来回收缩短后多出来的字节,而是使用free属性将这些字节记录起来,并等待使用。

@4 二进制安全

  C字符串中的字符必须符合某种编码(好比ASCII),而且除了字符串末尾以外,字符串里面不能包含空字符串,不然最早被程序读入的空字符串将被误认为是字符串结尾。

SDS API都是二进制安全的,全部SDS API都会以处理二进制的方式来处理存放在SDS buf中的数据,数据写什么样,它被读取时就是什么样子。

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

  SDS的API总会以SDS保存的数据的末尾设置为空字符串,而且在分配SDS空间时会多分配一个字节的空间来容纳空字符串,这是为了那些保存的数据能够重用一部分<string.h>库中的函数。

总结

字符串和SDS之间的区别总结以下:

 

 

 

原文连接:Redis字符串键的底层原理

相关文章
相关标签/搜索