Redis底层原理

对比另一个著名的缓存中间件memcached,redis提供了更加丰富的数据结构和命令,它支持基于列表、集合、哈希表等多种数据结构。这些结构在redis中是由6种底层数据结构来实现:

  • 简单动态字符串
  • (SDS)
  • 链表
  • 字典
  • 跳跃表
  • 整数
  • 集合
  • 压缩列表

Redis底层原理_第1张图片

SDS

用来保存redis数据库中的字符串,它是一个结构体:

Redis底层原理_第2张图片

结构体中的三个属性分别用来表示已使用的字节数量、未使用的字节数量、字节数组,redis之所以设计这个结构而不使用C语言中的字符串,是因为SDS结构相比C语言中的字符串有如下优点:

  • 记录了字符串的长度,执行strlen操作时,复杂度为O(1),而普通字符串为O(n)。
  • SDS采用预先分配和惰性释放的优化策略,减少重新分配空间的次数,其中有free字段记录未使用空间字节数。
  • 二进制安全,除了可以存储字符还可以存储任意二进制。
  • SDS操作函数会自动检查空间是否足够并且空间不足时自动扩展空间,从而防止内存溢出。

链表

用来存储链表结构数据,它被用来实现SADD等集合命令:

Redis底层原理_第3张图片

字典

字典在Redis中应用非常广泛,Redis数据库本身就是使用字典来作为底层实现的,比如我们执行命令:SET msg "hello world",redis就会在数据库中创建一个键为msg值为"hello world"的字典。

字典在实现上包含两个hash表,常规状态下,只有第一个hash表存储数据,当发生rehash时,会给第二个hash表分配空间,并且把第一个hash表中的数据拷贝到第二个hash表中。如果hash表中的数据量比较大时,会采用渐进式rehash,分多次完成,字典中有个rehashindex字段记录rehash进度(如果当前未进行rehash值为-1),这样的话在字典中查询数据时,可能会查找两个hash表,先查第一个表,如果查不到再查第二个,但是新增的数据都会添加到第二个hash表中,发生rehash之后会由第二hash表负责查询客户端请求数据。当服务器正在执行BGSAVE、BGREWRITEAOF等耗CPU的命令时,会尽量避免rehash操作。

Redis底层原理_第4张图片

跳跃表

redis实现了一个跳跃表结构,跳跃表被用来实现缓存中的有序集合如ZADD等命令。

Redis底层原理_第5张图片

Redis底层原理_第6张图片

整数集合

当集合中只包含整数时,并且集合中的元素数量不多时,redis会采用整数集合当作底层实现。

Redis底层原理_第7张图片

其中contents中的元素有可能是16、32、64字节,redis只会分配合适的内存,比如当前添加的时16位的整数,那么只会给contents元素分配16字节,后续往数组中再次添加16字节以上的整数时,会给该数组升级重新分配32或64字节内存,数组只能升级不能降级,能从16字节升级到32或64字节,但是不能从64字节降级到16或32字节。

压缩列表

当列表、集合、hash表、有序集合等结构中的元素数量小于某个阀值,redis会采用压缩列表当作这些结构的底层实现,目的是为了节省内存,压缩列表包含了下列各个组成部分:

  • zlbytes 4字节,纪录整个压缩列表占用的内存字节数:在对压缩列表进行内存重分配,或计算zlend时使用。
  • zltail 4字节,纪录压缩列表表尾节点距离压缩列表的起始地址有多少个字节,通过这个偏移量程序可以无须遍历整个压缩列表就可以确定表尾节点的地址。
  • zllen 2字节,纪录压缩列表包含的节点数量,当属性值等于UINT16_MAX时,结点的真实数量需要遍历整个压缩列表才能计算得出。
  • entryX,列表节点,长度由结点中的内存结点,结点中有个字段previous_entry_length记录了前一个几点的长度,当结点的长度在254左右时,对某结点的修改可能会引发所有结点的连锁更新。

压缩列表会节约内存,但是访问或修改结构中的元素时会损失一些性能,所以当元素数量或者元素大小超过了某个阀值之后会对值对象进行编码转换,转换成集合、hash表、有序集合等结构,并重新分配内存。

对象

Redis并没有直接使用上面那些数据结构来实现键值对数据库,而是在这些数据接口的基础上创建了一个对象系统,每次在Redis数据库中新创建一个键值对时,至少会创建两个对象,一个键对象,一个值对象。每个对象都包含指定的类型和编码。

对象类型

对象的type属性设置,可以通过TYPE命令来输出对象的类型,对象有下面几种类型:

  • 字符串对象:类型常量为REDIS_STRING,输出值string。
  • 列表对象:类型常量为REDIS_LIST,输出值list。
  • 哈希对象:类型常量为REDIS_HASH,输出值hash。
  • 集合对象:类型常量为REDIS_SET,输出值hash。
  • 有序集合表对象:类型常量为REDIS_ZSET,输出值zset。

对象编码

对象的encoding属性设置,可以通过OBJECT ENCODING命令查看对象的编码,对象有下面几种编码:

  • long类型整数,REDIS_ENCODING_INT,输出intembstr编码的SDS,REDIS_ENCODING_EMBSTR,输出embstr。
  • embstr编码是专门用于保存短字符串的一种优化编码方式,跟正常的字符编码相比,字符编码会调用两次内存分配函数来分别创建redisObject和sdshdr结构,而embstr编码则通过调用一次内存分配函数来分配一块连续的空间,空间中一次包含redisObject和sdshdr两个结构
  • SDS,REDIS_ENCODING_RAW,输出raw
  • 字典,REDIS_ENCODING_HT,输出hashtable
  • 双端链表,REDIS_ENCODING_LINKEDLIST,输出linkedlist
  • 压缩列表,REDIS_ENCODING_ZIPLIST,输出ziplist
  • 整数集合REDIS_ENCODING_INISET,输出intset
  • 跳跃表和字典,REDIS_ENCODING_SKIPLIST,输出skiplist

每种类型的对象至少使用了两种不同的编码,在不同的条件下redis会对对象进行编码转换,比如如果对象保存的全部都是整数,那么他的编码是int,但是当执行了一些命令之后对象中存储的已经不全部是整数了,那么会把该对象的编码从int变为raw,其它类型的对象同理。

你可能感兴趣的:(redis)