Redis系列(九)底层数据结构之五种基础数据类型的实现

前言

Redis 已是你们耳熟能详的东西了,平常工做也都在使用,面试中也是高频的会涉及到,那么咱们对它究竟了解有多深入呢?html

我读了几本 Redis 相关的书籍,尝试去了解它的具体实现,将一些底层的数据结构及实现原理记录下来。面试

本文将介绍 Redis 中 五种基础数据类型 的实现方法。 这五种基本类型基本覆盖了咱们业务中使用的 80%的场景,对面试也覆盖至少 90%.(其中重点固然是有序集合以及散列结构咯).redis

定义

在前面的八篇文章中,咱们详细的介绍了 Redis 中的 8 种基本数据结构,可是众所周知,Redis 经常使用的数据类型有五种。包括,字符串,列表,集合,有序集合,哈希。数据库

而这五种数据类型,底层就是用前面介绍的数据结构实现的,固然,并非直接一对一的绑定关系,而是采用了精妙的设计,构建了一个对象系统。编程

熟悉 OOP 编程的读者,可能很快就能想到为何要这么设计了,对象系统带来的好处是很是多的,可是并不在这一篇文章中讲。这里只是提到对象系统,让你们对于五种数据类型为何能够用一些花里胡哨的方法来实现,有一个初步的了解。后端

接下来将逐一分析五种数据类型的底层实现数据结构,及实现方式(编码)之间的切换条件。微信

注:后续提到五种数据类型,用 xx 对象来指代。好比 字符串对象,列表对象。提到的底层数据结构,用全称来说。markdown

字符串对象

涉及到的数据结构,SDS, 强烈建议阅读本系列第一篇文章。数据结构

字符串对象的底层实现有三种可能:int, raw, embstr.oop

int

若是一个字符串对象,保存的值是一个整数值,而且这个整数值在 long 的范围内,那么 redis 用整数值来保存这个信息,而且将字符串编码设置为 int.

好比:

2020-01-12-16-13-35

raw

若是字符串对象保存的是一个字符串, 而且长度大于 32 个字节,它就会使用前面讲过的SDS(简单动态字符串)数据结构来保存这个字符串值,而且将字符串对象的编码设置为raw.

2020-01-12-16-16-26

embstr

若是字符串对象保存的是一个字符串, 可是长度小于 32 个字节,它就会使用embstr来保存了,embstr编码不是一个数据结构,而是对 SDS 的一个小优化,当使用 SDS 的时候,程序须要调用两次内存分配,来给 字符串对象 和 SDS 各自分配一块空间,而embstr只须要一次内存分配,由于他须要的空间不多,因此采用 连续的空间保存,即将 SDS 的值和 字符串对象的值放在一块连续的内存空间上。这样能在短字符串的时候提升一些效率。

好比:

2020-01-12-16-21-20

浮点数如何保存?

redis 的字符串数据类型是支持保存浮点数,而且支持对浮点数进行加减操做,可是 redis 在底层是把浮点数转换成字符串值,以后走上面三种编码的规则的。对浮点数进行操做时,也是从字符串转换成浮点数进行计算,而后再转换成字符串进行保存的。

编码转换条件

这块的知识实际上是很符合咱们的认知的,好比 int编码只能够保存整数,那么当咱们对一个 int 编码的字符串对象,修改它的值,它天然就会使用 raw 编码了。

可是有一个特性,Redis 没有为embstr编码提供任何的修改操做,这也就是为何它只是个编码而不是一个数据结构的缘由。

因此在 Redis 中,embstr编码的值实际上是 只读的,只要发生修改,马上将编码转换成 raw.

总结

字符串对象底层的数据结构或者说编码有三种,分别是 int, raw, embstr. 他们之间的使用条件以下:

编码 使用条件
int 能够用 long 保存的整数
embstr 字符串长度小于 32 字节(或者浮点数转换后知足)
raw 长度大于 32 的字符串

列表对象

涉及到的数据结构,压缩列表, 双向链表, 快速列表, 强烈建议阅读本系列的第 二,三,四 篇文章。

在 Redis 3.2 版本以前,列表对象底层由 压缩列表和双向链表配合实现,当元素数量较少的时候,使用压缩列表,当元素数量增多,就开始使用普通的双向链表保存数据。

可是这种实现方式不够好,双向链表中的每一个节点,都须要保存先后指针,这个内存的使用量 对于 Redis 这个内存数据库来讲极其不友好。

所以在 3.2 以后的版本,做者新实现了一个数据结构,叫作 quicklist. 全部列表的底层实现都是这个数据结构了。它的底层实现基本上就是将 双向链表和压缩列表进行告终合,用双向的指针将压缩列表进行链接,这样不只避免了压缩列表存储大量元素的性能压力,同时避免了双向链表链接指针占用空间过多的问题。

具体的原理讲解请 阅读对应的文章,这里再也不赘述。

总结

编码 使用条件
quicklist 全部数据

集合对象

涉及到的数据结构:intset, dict(hashtable), 强烈建议阅读本系列第五,第六篇文章。

集合对象的编码能够是 intset 或者 hashtable(字典) .

intset

当集合中的全部元素都是整数,且元素的数量不大于 512 个的时候,使用 intset 编码。

2020-01-12-16-46-34

intset 编码时,底层使用 intset数据结构。

hashtable

当元素不符合所有为整数值且元素个数小于 512时,集合对象使用的编码方式为** hashtable**.

字典的每个键都是一个字符串对象,其中保存了集合里的一个元素,字典的值所有被设置为 NULL.

2020-01-12-16-54-33

总结

编码 使用条件
intset 全部元素都是整数且元素个数小于 512
hashtable 其余数据

有序集合对象

涉及到的数据结构,压缩列表, 跳跃表, 字典, 强烈建议阅读本系列 第三篇,第六篇,第七篇文章。

有序集合对象的编码能够是 ziplist 以及skiplist.

ziplist 编码

当使用 ziplist 编码时,有序集合对象的实现数据结构为ziplist(听起来像句废话), 每一个集合的元素 (key-value), 使用两个紧挨着的压缩列表的节点来表示,第一个节点保存集合元素的成员 (member), 第二个节点保存集合元素的分支 (score).

在压缩列表的内部,集合元素按照分值从小到大进行排序。

2020-01-12-17-05-27

skiplist 编码

当使用 skiplist 编码的时候,内部使用zset 来实现数据的保存,zset的定义以下:

typedef struct zset{
  zskiplist *zsl;
  dict *dict;
}zset;
复制代码

为何须要同时使用跳跃表以及字典呢?

其实若是咱们细想,单独使用字典或者跳跃表,都是能够实现有序集合的全部功能的,可是性能太差劲了。

  • 当咱们只使用字典来实现,咱们能够以 O(1) 的时间复杂度获取成员的分值,可是因为字典是无序的,当咱们须要进行范围性操做的时候,须要对字典中的全部元素进行排序,这个时间复杂度至少须要 O(nlogn).
  • 当咱们只使用跳跃表来实现,咱们能够在 O(logn) 的时间进行范围排序操做,可是若是要获取到某个元素的分值,时间复杂度也是 O(logn).

所以,将字典和跳跃表结合进行使用,能够在 O(1) 的时间复杂度下完成查询分值操做,而对一些范围操做,使用跳跃表能够达到 O(logn) 的是缠绵复杂度。

2020-01-12-17-14-17

能够看到,我在上一次的例子中,添加了一个很长的 key 以后,有序集合的编码方式就成为了skiplist.

总结

编码 使用条件
ziplist 元素数量少于 128 且 全部元素成员的长度小于 64 字节
skiplist 不知足上述条件的其余状况

散列对象

涉及到的数据结构,压缩列表, 字典, 强烈建议阅读本系列 第三篇,第六篇文章。

哈希对象的编码能够是ziplist或者hashtable.

ziplist 编码

ziplist 编码下的哈希对象,使用了压缩列表做为底层实现数据结构,用两个连续的压缩列表节点来表示哈希对象中的一个键值对。实现方式相似于上面的有序集合的场景。

2020-01-12-17-21-29

如图中所示,当我放入了两个简单的键值对,此时哈希对象的编码为 ziplist.

hashtable 编码

这是对 hashtable 最直观的应用了~

哈希结构自己在结构上和字典 (hashtable) 就颇为类似,所以哈希对象中的每个键值对都是字典中的一个键值对。

  • 字典的每个键都是一个字符串对象,对象中保存了键值对的键。
  • 字典的每个值都是一个字符串对象,对象中保存了键值对的值。

2020-01-12-17-25-32

如图中所示,当我在上一个示例中额外加入一个很长的值,那么编码方式就来到了hashtable.

总结

编码 使用条件
ziplist 键值对的键和值的长度都小于 64 字节,且 键值对个数小于 512.
hastable 不知足上述条件的其余条件

全文总结

其实在前面的几篇文章写完以后,也就是在全部的底层数据结构介绍完以后,所谓的Redis 的五种基础数据类型的底层实现原理就已经没有了难度。

全部用到的底层数据结构都知道了,剩下的无非是个排列组合问题以及各类实现方式之间的切换条件,而后这个条件也仅仅是了解性知识,强行记住也没有必要。

这里把五种基础数据类型的可能的编码列出来方便理解及记忆。

基础数据类型 可能的编码方式
字符串 int, raw, embstr
列表 以前是 ziplist 和 linkedlist, 如今全是 quicklist 了。
集合 intset 或者 hashtable
有序集合 ziplist 或者 skiplist, skiplist 编码中使用了跳跃表+字典
散列 ziplist 或者 hashtable

至于他们的转换条件,因为我不会用 markdown 画多维表格,可是又不想写 html, 就不作总结了,须要的读者能够点击目录跳转至每个小结的总结~.

参考文章

《Redis 的设计与实现(第二版)》

《Redis 深度历险:核心原理和应用实践》


完。

联系我

最后,欢迎关注个人我的公众号【 呼延十 】,会不按期更新不少后端工程师的学习笔记。 也欢迎直接公众号私信或者邮箱联系我,必定知无不言,言无不尽。


以上皆为我的所思所得,若有错误欢迎评论区指正。

欢迎转载,烦请署名并保留原文连接。

联系邮箱:huyanshi2580@gmail.com

更多学习笔记见我的博客或关注微信公众号 < 呼延十 >------>呼延十

相关文章
相关标签/搜索