Redis底层数据结构面试题剖析

一、redis的底层数据结构dict有了解吗,如何rehash?

redis是一个支持 key-value 内存存储的数据结构服务器,如下:

基于k,v存储的nosql数据库,其天然的就必须具备一个dict底层数据结构来支撑

Redis的 dict 实现最显著的一个特点,就在于它的重哈希。

我们知道,dict进行扩容时,我们要对列表中的每个元素遍历迁移到新的dict中,这样全量式的迁移开销太大,而且redis还是单线程的,当这个唯一的线程去做迁移去了,那并发的客户端请求不就得一直阻塞等待着,大大降低redis的并发及性能。

然而,redis基于上述痛点是怎样做的呢?

它采用了一种称为增量式重哈希 (也叫渐进式重哈希)的方法,在需要扩展内存时避免一次性对所有key进行重哈希,而是将重哈希操作分散到对于dict的各个增删改查的操作中去。这种方法能做到每次只对一小部分key进行重哈希,而每次重哈希之间不影响dict的操作

整个dict维护着两个数组,一个数组的较小的原数组,一个是正在迁移的大容量数组

在rehash时并不是一次性遍历全部key迁移到大容量数组中,而是一次迁移小部分key,并用rehashidx记录迁移到第几个key了,此后每收到一个增删改请求再接着迁移

二、robj有了解吗,说一说

redis的一个database内的这个映射关系是用一个 dict 来维护的,为了在同一个 dict 内能够存储不同类型的value,这就需要一个顶级的通用的数据结构(类似于Java中的Object),这个通用的数据结构就是robj(全名redisObject)

一个robj包含如下5个字段,图示如下:

这里特别需要仔细察看的是encoding字段。对于同一个type,还可能对应不同的encoding,这说明同样的一个数据类型,可能存在不同的内部表示方式。而不同的内部表示,在内存占用和查找性能上会有所不同。

下图展示了redis 5种常见数据类型在不同编码方式下的数据结构:

比如一条命令,set test_key1 "abc",那么对应的robj的type就指向OBJ_STRING,encoding指向OBJ_ENCODING_RAW,最后,底层实现的数据结构就是简单动态字符串SDS

三、redis的底层数据结构字符串是如何实现的有了解吗?

Redis 中的字符串是一种 动态字符串,即 Simple Dynamic String(SDS),这意味着使用者可以修改,它的底层实现有点类似于 Java 中的ArrayList。

SDS 结构如图所示:

SDS特点

  1. C语言中是没有字符串类型的,字符串是存放在字符数组中的, java中的String内部也是构建的字符数组
  2. C字符串获取字符串长度是O(N)的而 SDS 是O(1)的,直接通过len属性获取
  3. SDS 能很好的杜绝缓冲区溢出/内存泄漏 的问题。 因为C字符串不记录自身长度,如果执行拼接 或者 缩短字符串的操作,操作不当就很容易 造成缓冲溢出/内存泄漏问题
  4. SDS 的空间分配策略完全杜绝了发生缓冲区溢出的可能性,当使用 API对 SDS 进行修改时,API会先检查 SDS 的空间是否满足修改所需的要求,如果不满足,它会自动将 SDS 的空间扩展至执行修改所需要的大小

embstr 与 raw两种编码的区别

本质上是redis对内存的优化做到了极致,不同大小的字符串使用不同的 sds 的 header

ebmstr 和 raw 编码是以字符串长度是否在44字节内为分界的。即 embstr编码形式,可以存储最大字符串长度是44字节。(注:redis 3.0 版本之前是以39字节为分界的)

然而 embstr 和 raw 的最大区别在于内存的分配上:

  • embstr 编码,代表 string 的 robj 和 sds 是一块连续的内存,只需分配一次内存
  • raw 编码,代表 string 的 robj 和 sds 是两块内存,需要创建时需要进行两次内存分配,回收也是一样。

四、redis的底层数据结构跳表有了解吗?

skiplist 本质上也是一种查找结构,用于解决算法中的查找问题(Searching),即根据给定的key,快速查到它所在的位置(即对应的value)。

因为 skiplist 是在链表的基础上发展起来的,所以,先来看一个普通有序链表(注意是有序的):

我们要查找某条数据,做法只能是从头节点开始,依次遍历每个节点,比较是否是自己想找的数据,整体的时间复杂度是O(n)的,插入某条数据也是,需要先找到待插入的位置才能插入,那如何提高查找效率呢?

如果让每相邻两个节点间增加一个指针,让指针指向下下个节点,如下图:

那如果按这种方式,我们再添加一层指针呢?如下图:

如果还想查找23,那么沿着最上层链表首先要比较的是19,发现23比19大,接下来我们就知道只需要到19的后面去继续查找,从而一下子跳过了19前面的所有节点。可以想象,当链表足够长的时候,这种多层链表的查找方式能让我们跳过很多下层节点,大大加快查找的速度。

skiplist 正是受这种多层链表的想法的启发而设计出来的。实际上,按照上面生成链表的方式,上面每一层链表的节点个数,是下面一层的节点个数的一半,这样查找过程就非常类似于一个二分查找,使得查找的时间复杂度可以降低到O(log n)。

skiplist 为了避免这一问题,它不要求上下相邻两层链表之间的节点个数有严格的对应关系,而是为每个节点随机出一个层数(level)。比如,一个节点随机出的层数是3,那么就把它链入到第1层到第3层这三层链表中。为了表达清楚,下图展示了如何通过一步步的插入操作从而形成一个 skiplist 的过程:

现在我们从跳表中查找23,查找过程如下:

五、简述一下SortedSet底层原理?

当数据多的时候,sorted set是由一个叫 zset 的数据结构来实现的,这个zset 包含一个 dict + 一个 skiplist 。 dict 用来查询数据到分数( score )的对应关系,而 skiplist 用来根据分数查询数据(可能是范围查找)。

而 zskiplist 定义了真正的 skiplist 结构,如下图所示:

相关推荐
姜行运26 分钟前
数据结构【栈和队列附顺序表应用算法】
android·c语言·数据结构·算法
2301_794461571 小时前
详解七大排序
数据结构·算法·排序算法
OpenVINO生态社区1 小时前
【汽车功能安全:软件与硬件缺一不可】
数据库·安全·汽车
蒙奇D索大2 小时前
【数据结构】图论进阶:生成树、生成森林与权值网络的终极解析
数据结构·考研·图论·改行学it
爱coding的橙子2 小时前
蓝桥杯备赛 Day16 单调数据结构
数据结构·c++·算法·蓝桥杯
wuqingshun3141592 小时前
经典算法 约数之和
数据结构·c++·算法·蓝桥杯
程序猿阿伟3 小时前
《打破SQL与AI框架对接壁垒,解锁融合新路径》
数据库·人工智能·sql
点燃大海3 小时前
MySQL表结构导出(Excel)
java·数据库·python·mysql·spring
一切皆有迹可循3 小时前
IntelliJ IDEA中Spring Boot 3.4.x+集成Redis 7.x:最新配置与实战指南
spring boot·redis·intellij-idea
꧁坚持很酷꧂3 小时前
Qt远程连接数据库,注册,登录
开发语言·数据库·qt