《Redis设计与实现》第一部分

前言

工作中经常会用到Redis,虽说会一些Redis的增删改查的API基本可以满足日常工作的需求,但是在一些时候还是会遇到一些疑惑。比如在插入string类型然后取出来的时候我遇到过解码问题,还有插入二进制数据的一些问题。当然,面试的时候Redis也是经常会被问到的一个问题。听说Redis的实现是非常精妙的,代码质量也非常高。总之是时候了解一下Redis的底层实现了。

第一部分 数据结构与对象

简单动态字符串

Redis不是使用的C语言的传统字符串而是构建了一种简单动态字符串SDS的抽象类型。

SDS用来保存数据库中的字符串值,也被用作缓冲区(buffer)。在这里插入图片描述

  • 记录长度的另外一个好处就是可以杜绝缓冲区溢出
  • sds的另外一个属性free记录的是还有多少空余的空间,这样可以实现空间预分配和惰性空间释放两种优化策略。
  • SDS是二进制安全的,因为不以\0作为结束,而是以长度做判断,所以SDS可以存储任意二进制信息。
  • SDS也可以复用一些C语言的库函数。

链表

当一个列表键包含了很多元素,或者列表中包含的元素都是比较长的字符串时,Redis就会使用链表作为列表键的底层实现。
在这里插入图片描述
在这里插入图片描述

  • 双端,无环,带头尾指针,长度计数器
  • 多态:通过为链接设置不同的类型特定函数,Redis的链表可以用于保存不同类型的值。

字典

Redis的数据库就是使用字典来作为底层实现,增删改查也是构建在对字典的操作之上。
字典使用哈希表作为底层实现。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
看到这里发现Redis的字典的实现其实和Java里面字典的实现非常像,都是一个数组,通过hash值作为索引,然后用链表来解决键的hash冲突。
在这里插入图片描述
关于rehash,我理解是用来扩展大小用的。
https://www.jianshu.com/p/13c650a25ed3

  1. 首先创建一个比现有哈希表更大的新哈希表(expand)
  2. 然后将旧哈希表的所有元素都迁移到新哈希表去(rehash)
    下面这张图就很好的说明了Redis的字典结构了:
    在这里插入图片描述

hash算法

添加键值对时,通过键计算出hash值和索引。
在这里插入图片描述
解决键冲突的方式也适合hashmap一样用一个链表,新加的键放在表头位置,保证O(1)

rehash

看到这里明白dict.ht[2]了,有两个ht是为了rehash时使用的,扩展或缩小时,吧ht[0]的rehash一下,放到ht[1],然后释放0,把1再放到0上。这就完成了dict的缩放。
这一rehash过程也不是一次完成,如果多的话需要渐进式rehash,用dict.trehashidx来表示是否在rehash,如果是-1表示不在rehash。

跳跃表

在每个节点中维持多个指向其他节点的指针,达到快速访问节点的目的。Redis使用跳跃表来作为有序集合键的底层实现。
跳表的效率和红黑树以及 AVL 树不相上下,但跳表的原理相当简单,只要你能熟练操作链表,就能轻松实现一个 SkipList。
关于跳表的概念下面两个博客写得比较好:
https://www.cnblogs.com/thrillerz/p/4505550.html
https://www.cnblogs.com/Leo_wl/p/11557614.html

有序表

考虑一个有序表:
在这里插入图片描述
从该有序表中搜索元素 < 23, 43, 59 > ,需要比较的次数分别为 < 2, 4, 6 >,总共比较的次数
为 2 + 4 + 6 = 12 次。有没有优化的算法吗? 链表是有序的,但不能使用二分查找。类似二叉
搜索树,我们把一些节点提取出来,作为索引。得到如下结构:
在这里插入图片描述
这里我们把 < 14, 34, 50, 72 > 提取出来作为一级索引,这样搜索的时候就可以减少比较次数了。
我们还可以再从一级索引提取一些元素出来,作为二级索引,变成如下结构:
在这里插入图片描述
这里元素不多,体现不出优势,如果元素足够多,这种索引结构就能体现出优势来了。

跳跃表

其中 -1 表示 INT_MIN, 链表的最小值,1 表示 INT_MAX,链表的最大值。
在这里插入图片描述
跳表具有如下性质:
(1) 由很多层结构组成
(2) 每一层都是一个有序的链表
(3) 最底层(Level 1)的链表包含所有元素
(4) 如果一个元素出现在 Level i 的链表中,则它在 Level i 之下的链表也都会出现。
(5) 每个节点包含两个指针,一个指向同一链表中的下一个元素,一个指向下面一层的元素。

跳表的搜索

在这里插入图片描述
例子:查找元素 117
(1) 比较 21, 比 21 大,往后面找
(2) 比较 37, 比 37大,比链表最大值小,从 37 的下面一层开始找
(3) 比较 71, 比 71 大,比链表最大值小,从 71 的下面一层开始找
(4) 比较 85, 比 85 大,从后面找
(5) 比较 117, 等于 117, 找到了节点。

跳表的插入

先确定该元素要占据的层数 K(采用丢硬币的方式,这完全是随机的)
然后在 Level 1 … Level K 各个层的链表都插入元素。
例子:插入 119, K = 2
在这里插入图片描述
如果 K 大于链表的层数,则要添加新的层。
例子:插入 119, K = 4
在这里插入图片描述丢硬币决定 K
插入元素的时候,元素所占有的层数完全是随机的
相当与做一次丢硬币的实验,如果遇到正面,继续丢,遇到反面,则停止,
用实验中丢硬币的次数 K 作为元素占有的层数。显然随机变量 K 满足参数为 p = 1/2 的几何分布,
K 的期望值 E[K] = 1/p = 2. 就是说,各个元素的层数,期望值是 2 层。

跳表的删除

在各个层中找到包含 x 的节点,使用标准的 delete from list 方法删除该节点。

例子:删除 71
在这里插入图片描述

跳跃表节点实现:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

整数集合

当一个集合只包含整数值元素,而且数量不多时,Redis就会使用整数集合作为集合键的底层实现。
在这里插入图片描述

集合升级降级

如果之前都是int16,然后要存一个int32的数字,就要对整数集合进行升级,重新分配空间,然后把现有的元素转型。
整数集合不支持降级操作。

压缩列表

是列表键和hash键的底层实现之一
压缩列表结构
每个压缩列表的节点(entry)都可以保存一个字节数组或者一个整数值。
entry的构成:在这里插入图片描述
这张图片其实比较好的说明了压缩列表。
这篇博客的内容关于Redis压缩列表的介绍更详细:https://www.cnblogs.com/hunternet/p/11306690.html 在这里插入图片描述

对象

前面介绍的几种数据结构,Redis并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统。
一个优点就是可以针对不同的使用场景来为对象设置多种不同的数据结构实现,从而优化对象在不同场景下的使用效率。
Redis还实现了基于引用计数的内存回收机制,程序不再使用某个对象的时候,这个对象占用的内存就会被释放。还有空转市场这个概念。
对象结构:
在这里插入图片描述
type属性有字符串,列表,hash,集合,有序集合。
在这里插入图片描述
ptr指向的是对象的底层实现数据结构。由encoding属性决定。
在这里插入图片描述

字符串对象

字符串对象的编码可以是int,rwa,embstr
例子:在这里插入图片描述

列表对象

底层可以使ziplist或者linkedlist。
使用压缩表的列表对象ptr指向一个ziplist。
双端链表对象每个节点都是一个字符串对象。后面的hash,集合等对象也会嵌套字符串对象。
在这里插入图片描述

哈希对象

编码可以使压缩列表或者hash表。在这里插入图片描述
在这里插入图片描述

集合对象

intset或者hashtable

有序集合

ziplist or skiplist

类型检查

类型检查这个东西其实就是多态,针对不同的类型有不同的处理方式。

内存回收

基于引用计数实现的内存回收机制

对象共享

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值