Redis的数据结构

63 阅读6分钟

先贴一张5种最常用的redis数据类型底层的数据结构类型。

image.png

String:

面试回答:

Redis的String类型底层使用的是简单动态字符串SDS,Redis的底层是用C语言写的嘛,SDS是在C语言的基础上做的改进。C语言的字符串有几个问题,一个是它的结束符号是\0,这种导致它不能存数据中包含这种结束符的数据,SDS改进了这个问题改成了二进制存储,这样它就可以存图片、视频等数据了,还有一个问题是在计算字符串长度时,需要通过O(n)的时间复杂度去计算长度,SDS也做了改进,SDS是一个结构体,它通过一个Long类型的字段存储字符串长度,所以时间复杂度就变成了O(1)。还有一个问题是C语言的字符串需要用户自己分配内存,这样容易导致缓冲区溢出,但是Redis当判断出缓冲区大小不够用时,Redis 会自动将扩大 SDS 的空间大小.

Set:

面试回答:

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

  • 如果集合中的元素都是整数且元素个数小于 512 (默认值,set-maxintset-entries配置)个,Redis 会使用整数集合作为 Set 类型的底层数据结构;
  • 如果集合中的元素不满足上面条件,则 Redis 使用哈希表作为 Set 类型的底层数据结构。

set底层是hash+整数集合。hash他使用的是一个hash桶数组,数组中的每个桶又指向下一个hash表节点,形成链表。它通过将hash值相同的键值对链接起来形成链表,这是redis的hash结构解决的hash冲突的方式,这个方式叫做链式hash。但是redis的hash和java中的hashmap又有些不同,redis的hash没有采用数组加链表/红黑树的结构,因为一个是redis的hash算法比较高效能做到一个hash均匀,其次是redis追求的是一个高效简单,红黑树会占用更大的内存。hash表的扩容是指在负载因子大于1的时候,会进行渐进式rehash。渐进式rehash是指,在进行rehash的时候会有2个哈希表,如果数据量很多的时候,一次性迁移,会阻塞主线程。所以当客户端每次进行增删改查的时候就进行一次顺序性的hash桶迁移。等到全部迁移完成,再将第一个hash表释放掉。负载因子的计算是通过已保存元素/桶的数目计算的。当大于1的时候会进行扩容,小于0.1的时候会进行缩容。Redis 的 set 在元素都是整数且数量不大时会使用整数集合(intset)来节省内存和提高小型集合查找效率,一旦出现非整数或集合变大,就会升级为普通哈希表(dict)存储。

Zset:

面试回答:

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

如果有序集合的元素个数小于 128 个,并且每个元素的值小于 64 字节时,Redis 会使用压缩列表作为Zset 类型的底层数据结构;

如果有序集合的元素不满足上面的条件,Redis 会使用跳表作为 Zset 类型的底层数据结构;但是在redis7.0中压缩列表已经被废弃了,使用Listpack来替代。Listpack是压缩列表的升级版本,它通过在每个节点存储节点大小的方式,解决了压缩列表在元素增加或者元素大小变大,从而导致内存重新分配,引起的连锁更新问题,连锁更新问题是指对中间元素进行插入、删除或扩容时,必须移动它后面的所有元素来腾出或填补空间,从而引发一系列连续的内存操作。

跳表:跳表主要说一下它的整个查询过程,跳表可以理解为一个链表的升级版本,但是相对于只有一层的链表结构,跳表具有多层。跳表在查找一个跳表节点的过程时,跳表会从头节点的最高层开始,逐一遍历每一层。在遍历某一层的跳表节点时,会用跳表节点中的 SDS 类型的元素和元素的权重来进行判断,共有两个判断条件:如果当前节点的权重「小于」要查找的权重时,跳表就会访问该层上的下一个节点。如果当前节点的权重「等于」要査找的权重时,并且当前节点的 SDS 类型数据「小于」要查找的数据时,跳表就会访问该层上的下一个节点。如果上面两个条件都不满足,或者下一个节点为空时,跳表就会使用目前遍历到的节点的level数组里的下一层指针,然后沿着下一层指针继续查找,这就相当于跳到了下一层接着查找。跳表查询的时间复杂度是O(logn),最差的时候是o(n),也就是知道最下面一层存满所有元素的单层链表才查到结果。

List:

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

  • 如果列表的元素个数小于 512 个(默认值,可由 list-max-ziplist-entries 配置),列表每个元素的值都小于 64 字节(默认值,可由 list-max-ziplist-value 配置),Redis 会使用压缩列表作为 List 类型的底层数据结构;
  • 如果列表的元素不满足上面的条件,Redis 会使用双向链表作为 List 类型的底层数据结构;

但是在 Redis 3.2 版本之后,List 数据类型底层数据结构就只由 quicklist 实现了,替代了双向链表和压缩列表

List底层是quickList,quickList的结构其实是双向链表+压缩列表的组合,就是一个双向列表它的每个节点是一个压缩列表,因为压缩列表有连锁更新问题,所以在reids7.0以后改成了Listpack。

hash:

面试回答:

如果哈希类型元素个数小于 512 个(默认值,可由 hash-max-ziplist-entries 配置),所有值小于 64 字节(默认值,可由 hash-max-ziplist-value 配置)的话,Redis 会使用压缩列表作为 Hash 类型的底层数据结构;

如果哈希类型元素不满足上面条件,Redis 会使用哈希表(哈希表上面的结构已经说过了)作为 Hash 类型的底层数据结构。

在 Redis 7.0 中,压缩列表数据结构已经废弃了,交由 listpack 数据结构来实现了