5种常见的Redis数据类型是怎么实现

5种常见的Redis数据类型是怎么实现

Reference:https://xiaolincoding.com/redis/base/redis_interview.html#%E4%BA%94%E7%A7%8D%E5%B8%B8%E8%A7%81%E7%9A%84-redis-%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E6%98%AF%E6%80%8E%E4%B9%88%E5%AE%9E%E7%8E%B0

五种常见的redis数据类型是:String、List、Set、Zset、hash

1. String类型内部的实现

String类型底层的数据结构实现主要是SDS(Simple dynamic string,简单动态字符串)。SDS和我们认识的C字符串不太一样,之所以没有使用C语言的字符串表示,因为SDS相比于C的原生字符串:

  • SDS获取字符串长度的时间复杂度是O(1)

因为C语言的字符串并不记录本身的长度,所以在获取长度的时候时间复杂度为O(n);而SDS结构里用len属性记录了字符串的长度,所以复杂度为O(1);

  • SDS不仅可以保存文本数据,还可以保存二进制数据

因为SDS使用len属性的值,而不是\0字符串来判断字符串是否结束,并且SDS的所有API都会以处理二进制的方式来处理SDS存放在buf[]数组里的数据。所以SDS不光能存放文本数据,而且能保存图片、音频、视频、压缩文件这样的二进制数据

  • Redis的SDS是 API安全的,进行分strcat等操作的时候,不会造成缓冲区溢出

SDS在拼接字符串之前会检查SDS空间是否满足要求,如果空间不够会自动扩容,所以不会导致缓冲区溢出问题

2. List类型内部实现

List类型的底层数据结构,是由双向链表或压缩列表实现的:

  • 如果列表元素小于512个,列表的每个元素值都小于64字节,redis会使用压缩列表作为list类型的底层数据结构(小数据规模才使用压缩列表,因为压缩列表的修改是一个额外的消耗)

  • 如果列表的元素不满足上述条件,redis会用双向链表作为list类型的底层数据结构

  • 在最新版本,List数据结构底层就只由quicklist实现

quicklist补充

感觉是综合了双向链表和压缩列表

其实 quicklist 就是「双向链表 + 压缩列表」组合,因为一个 quicklist 就是一个链表,而链表中的每个元素又是一个压缩列表。

在前面讲压缩列表的时候,我也提到了压缩列表的不足,虽然压缩列表是通过紧凑型的内存布局节省了内存开销,但是因为它的结构设计,如果保存的元素数量增加,或者元素变大了,压缩列表会有「连锁更新」的风险,一旦发生,会造成性能下降。

quicklist 解决办法,通过控制每个链表节点中的压缩列表的大小或者元素个数,来规避连锁更新的问题。因为压缩列表元素越少或越小,连锁更新带来的影响就越小,从而提供了更好的访问性能。

image-20221207225806566

在向quicklist添加一个元素的时候,不会像普通的链表那样,直接新建一个链表节点。而是会检查插入位置的压缩列表是否能容纳该元素,如果能容纳就直接保存到quicklistNode结构里的压缩列表,如果不能容纳,才会创建一个新的quicklistNode结构

quicklist会控制quicklistNode结构里的压缩列表的大小或者元素个数,来规避潜在的连锁更新风险,但是并没有完全解决连锁更新的问题。

3. Hash类型内部实现

Hash类型的底层数据结构是由压缩列表或哈希表实现的:

  • 如果哈希元素数小于512个,所有值小于64字节,redis会使用压缩列表作为hash类型的底层数据结构
  • 如果哈希类型元素不满足上面的条件,redis会用哈希表作为hash类型底层的数据结构

在新版redis中,压缩列表被丢弃了,改成listpack数据结构实现

listpack数据结构补充

image-20221207230215344

  • encoding:定义该元素的编码类型,会对不同长度的整数和字符串进行编码
  • data:实际存放的数据
  • len:encoding+data的总长度

可以看到,listpack没有压缩列表中记录前一个节点长度的字段了,listpack只记录当前节点的长度,当我们向listpack加入一个新元素的时候,不回应系那个其他节点的长度字段的变化,从而避免了压缩列表的连锁更新

4. Set类型内部实现

Set类型的底层数据结构是由哈希表或整数集合来实现的:

  • 如果集合中的元素都是整数,且元素个数小于512,redis会使用整数集合作为Set类型的底层数据结构
  • 如果集合中的元素不满足上面条件,则redis使用哈希表作为Set类型底层的数据结构

整数集合补充

哈希表补充

5. ZSet类型内部实现

ZSet类型的底层数据结构是由压缩列表或跳表来实现的:

  • 如果有序集合元素个数小于128个,并且每个元素值小于64个字节的时候,redis会使用压缩列表作为ZSet底层的数据结构
  • 如果不满足上面的条件,会用跳表作为Zset类型的底层数据结构

在新版本redis中,压缩列表被丢弃了,改成listpack数据结构实现

跳表补充

链表在查找元素的时候,因为需要逐一查找,所以查询效率非常低,时间复杂度是O(N)

于是就出现了跳表,在链表基础上改进,实现了一种「多层」的有序链表

image-20221207230710499

图中头节点有 L0~L2 三个头指针,分别指向了不同层级的节点,然后每个层级的节点都通过指针连接起来:

  • L0 层级共有 5 个节点,分别是节点1、2、3、4、5;
  • L1 层级共有 3 个节点,分别是节点 2、3、5;
  • L2 层级只有 1 个节点,也就是节点 3 。

如果我们要在链表中查找节点 4 这个元素,只能从头开始遍历链表,需要查找 4 次,而使用了跳表后,只需要查找 2 次就能定位到节点 4,因为可以在头节点直接从 L2 层级跳到节点 3,然后再往前遍历找到节点 4。

可以看到,这个查找过程就是在多个层级上跳来跳去,最后定位到元素。当数据量很大时,跳表的查找复杂度就是 **O(logN)**。


5种常见的Redis数据类型是怎么实现
http://example.com/2022/12/07/develop/redis/5种常见的Redis数据类型是怎么实现/
作者
Curious;
发布于
2022年12月7日
许可协议