Redis 底层数据结构原理

注:本篇按照Redis设计与实现这本书来写。基于Redis3.0版本

redis使用了 SDS、链表、字典(哈希表)、跳跃表、整数集合、压缩列表 几种数据类型,我们操作的api是对这几个数据结构的封装

SDS 简单动态字符串

Redis是用c语言写的,自然而言遵循c语言的特性。c语言字符串就有一堆坑,比如缓冲区溢出,获取字符串长度o(n)复杂度。

对于c语言来说,字符串存储在字符数组里面,每次字符串的变更导致数组的变更,从而进行内存重新分配。因为内存重分配涉及复杂的算法, 并且可能需要执行系统调用, 所以它通常是一个比较耗时的操作。
为了避免 C 字符串的这种缺陷, SDS 通过未使用空间解除了字符串长度和底层数组长度之间的关联: 在 SDS 中, buf 数组的长度不一定就是字符数量加一, 数组里面可以包含未使用的字节, 而这些字节的数量就由 SDS 的 free 属性记录。


通过未使用空间, SDS 实现了空间预分配和惰性空间释放两种优化策略。

空间预分配
空间预分配用于优化 SDS 的字符串增长操作: 当 SDS 的 API 对一个 SDS 进行修改, 并且需要对 SDS 进行空间扩展的时候, 程序不仅会为 SDS 分配修改所必须要的空间, 还会为 SDS 分配额外的未使用空间。
如果对 SDS 进行修改之后, SDS 的长度(也即是 len 属性的值)将小于 1 MB , 那么程序分配和 len 属性同样大小的未使用空间, 这时 SDS len 属性的值将和 free 属性的值相同。    通过这种预分配策略, SDS 将连续增长 N 次字符串所需的内存重分配次数从必定 N 次降低为最多 N 次。

惰性空间释放
惰性空间释放用于优化 SDS 的字符串缩短操作: 当 SDS 的 API 需要缩短 SDS 保存的字符串时, 程序并不立即使用内存重分配来回收缩短后多出来的字节, 而是使用 free 属性将这些字节的数量记录起来, 并等待将来使用。
比如一个  free 为5  buf[]= redis 的sds对象,变成re 这样就是 free=3   buf[]=re 此时不会立刻释放掉free的内存,而是提供api等你啥时候缺了再释放,所以不用担心惰性空间释放策略会造成内存浪费。比如内存灌满了等等、

二进制安全
C语言字符串还有一个缺点是 \0表示结尾,那么字符中有空格肯定会有问题。这些限制使得C 字符串只能保存文本数据, 而不能保存像图片、音频、视频、压缩文件这样的二进制数据。

所有 SDS API 都会以处理二进制的方式来处理 SDS 存放在 buf 数组里的数据, 程序不会对其中的数据做任何限制、过滤、或者假设 —— 数据在写入时是什么样的, 它被读取时就是什么样。因为 SDS 使用 len 属性的值而不是空字符来判断字符串是否结束

C 字符串与SDS  区别

  1. 获取字符串长度的复杂度为 O(N) 。                                   获取字符串长度的复杂度为 O(1) 。
  2. API 是不安全的,可能会造成缓冲区溢出。                        API 是安全的,不会造成缓冲区溢出。
  3. 修改字符串长度 N 次必然需要执行 N 次内存重分配。       修改字符串长度 N 次最多需要执行 N 次内存重分配。
  4. 只能保存文本数据。                                                            可以保存文本或者二进制数据。
  5. 可以使用所有 <string.h> 库中的函数。                               可以使用一部分 <string.h> 库中的函数。

 

链表

链表提供了高效的节点排重能力,以及顺序的节点访问方式。

链表,存储了next和prev指针。有头结点和尾节点的指针,访问头尾是o(1) 其他位置是o(n).存储了链表的长度o(1)

使用adlist.h/list对链表进行封装如下。链表的查找、删除、修改是o(n),其余操作是o(1)的

字典

字典又称关联数组、map。 Redis中的字典实际上就是哈希表数组。   Redis中所有的key是唯一的,实际上就是一个字典。

Redis的数据库就是用字典作为底层实现的,对数据库的crud也是构建在字典上的,所以很快的。set a "hello world"

对key进行hash,存储的是hash之后的值,如果发生冲突就链地址法,冲突的位置连成一个链表。

如下为dictEntry的具体内部结构。next指针就是连成链表用的。

字典的数据结构如下:

rehash 当hash进行扩展时需要进行rehash操作,扩展2^n
在进行拓展或者压缩的时候,可以直接将所有的键值对rehash 到ht[1]中,这是因为数据量比较小。在实际开发过程中,这个rehash 操作并不是一次性、集中式完成的,而是分多次、渐进式地完成的。
渐进式rehash 的详细步骤:

  1. 为ht[1] 分配空间,让字典同时持有ht[0]和ht[1]两个哈希表
  2. 在几点钟维持一个索引计数器变量rehashidx,并将它的值设置为0,表示rehash 开始
  3. 在rehash 进行期间,每次对字典执行CRUD操作时,程序除了执行指定的操作以外,还会将ht[0]中的数据rehash 到ht[1]表中,并且将rehashidx加一
  4. 当ht[0]中所有数据转移到ht[1]中时,将rehashidx 设置成-1,释放ht[0]。然后将ht[1]改成ht[0],重新分配ht[1]
     

字典除了释放所有键值对是o(n),其余操作都是o(1)

跳跃表

skiplist是一种有序数据结构,通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。

如果一个有序集合包含的元素数量比较多,又或者有序集合中元素的成员是比较长的字符串时,Redis就会使用跳跃表作为有序结合键的底层实现。

和字典、链表不同的是,跳跃表只是在两个地方用到了,有序集合键&&集群节点中作为内部数据结构。

上图是一个节点的数据结构。跳跃表肯定是多个这种节点组成然后又包了一下的。

每新增一个上图数据结构的节点就会根据幂次定律生成一个介于1-32之间的值作为level数组的大小。这个大小就是层的高度。整体的排序根据score的大小来进行排序,可以理解为高端的平衡树。其中层里面的span是记录两个节点之间的距离。

跳跃表就是多个跳跃节点组成的,持有最高层、长度等的一个数据结构、

其中level是存储的节点最高的。如上图就是o3所在的L5  

length就是遍历到尾部指针需要的遍历层度,如上就是o1---o2---o3 所在的层,一共三层。

可以看出不是每层都一样多,是1-32随机的,而多少个节点值就是多少的length,只不过访问的时候由于层指针的原因会遍历的相对链表来说更少。

其中level[] 就是上图中的 L(n)数组,对应节点所在的L(n)相连,存储到*forward前进指针,其中span存储的是与其余相连节点的距离

所以跳跃表是有平均复杂度o(logn)和最坏复杂度的o(n)

 

整数集合

整数集合是集合键(set)的底层实现之一,当一个集合只包括整数值元素。并且这个集合的元素不多时,就采用整数集合进行存储。

升级:当加入一个新元素,int32比int8要数据类型长时,需要将之前的元素全都升级为int32

降级:整数集合不支持降级的操作。

压缩列表

压缩列表是列表键和哈希键的底层实现之一。当一个列表键只包含少量列表项,并且每个列表项要么就是小整数值,要么就是长度比较短的字符串,那么Redis就会使用压缩列表来做列表键的底层实现。

压缩列表是Redis为了节约内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型数据结构。

每个压缩列表节点可以保存一个字节数组或者一个整数值。

压缩列表的组成previous_entry_length  encoding  content   

  1. previous_entry_length前一个节点的长度。
  2. encoding记录了节点的content数据所保存的数据类型和长度。
  3. content属性负责保存节点的值,节点值可以是一个字节数组或者整数。值得类型和长度由节点encoding决定。

压缩列表的时间复杂度查、删除、增加、修改都是o(n)相当于节约内存牺牲时间。

对象

就是我们直接用的string  list  set  zset  hash  

encoding存储的是对象使用了什么数据结构作为对象的底层实现。下面是数据结构与数据类型的对应关系。

其中还包括 lru 属性,记录对象最后一次被命令程序访问的时间。

内存回收

redis在自己的对象系统中构建了一个引用计数信息,在适当的时候自动释放对象并进行内存回收。

Java是把引用计数回收这个方式给否了的。因为循环引用,但是在Redis并不存在循环引用。

对象共享

如果一个对象例如 “100”   在多个地方使用,那么就引用+1,然后指针指向这个redisObject对象的内存地址。

object  refcount  key 看引用计数数量。

Redis会在初始化服务器是创建一万个字符串对象  0-9999

 

object idletime 展示 当前时间 - lru时间

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章