【从零开始学习Redis | 第八篇】认识Redis底层数据结构(下)

目录

前言:

ZipList:

Ziplist的特性:

QucikList:

QuicList特征:

SkipList:

跳表特征:

RedisObijct:

小心得:

总结:


前言:

在现代软件开发中,数据存储和处理是至关重要的一环。为了高效地管理数据,并实现快速的读写操作,各种数据库技术应运而生。其中,Redis作为一种高性能的内存数据库,广泛应用于缓存、会话存储、消息队列等场景。要深入了解Redis的工作原理,就必须先了解其底层数据结构。

Redis之所以能够在性能上表现出色,部分原因在于其精心设计的数据结构。这些数据结构不仅简单高效,而且能够满足各种复杂的数据处理需求。本文将深入探讨Redis底层数据结构的设计原理,包括字符串哈希列表集合有序集合等,希望能够帮助读者更好地理解Redis的内部机制,为进一步应用和优化Redis提供指导。

上一篇文章我们介绍了动态字符串,intset,Dict这三个底层的数据结构,本文我们再来介绍一下ZipSet ,QuickList ,SkipList ,RedisObject

ZipList:

我们在上一篇文章最后介绍了Dict,他是基于链表去做的Hash表。但是链表有一个缺陷:内存空间不连续,因此容易产生内存碎片。

而我们今天要介绍的这个数据结构,他是一种特殊的'双端链表',由一系列特殊编码的连续内存块组成,可以在任意一端进行压入/弹出操作,而且该操作的复杂度为(O)1

其实ZipList不是链表,我们只是为了方便描述才这么说。

我们可以在Redis的源码中看一看官方作者对ZipList的数据结构的描述:

我们用自己的图来解释一下各个部分的意思:

(下方此图来自黑马程序员,如有侵权,立即删除)

Ziplist的优势就在于:他的entry的字节大小是不固定的。

而虽然我们这么做可以减少内存的浪费,但是他存在一个问题:当节点的长度不固定的时候,我们是如何遍历节点的?毕竟已经无法通过索引下标进行遍历了。要想知道答案,就得先从他的结点结构看起:

结点的结构数据如图所示:

Pervious:前一个结点的长度,占一个或者5个字节。

如果前一个结点的长度小于254,则采用一个字节来保存这个长度值。

如果前一个结点的长度大于254,则采用五个字节来保存这个长度值,第一个字节为0xfe,后 面四个才是真实的长度数据

encoding:编码属性,记录content的数据类型(字符串还是整数)以及长度,占用1个2个或5个字节。

contents: 负责保存结点的数据,可以是字符串或整数

通过了解Ziplist的结点结构之后,其实我们就可以知道:如果我们想要知道下一个结点的长度,只需要用起始地址+当前结点三部分的字节数,那就是下一个结点的位置了

如果我们想要找到上一个结点的位置,也只需要拿当前结点的起始位置-上一个结点的长度就好了。

而ZipList会有连锁更新的问题,很简单:每一个结点都存储着上一个结点的信息,如果当前结点的长度发生变化,那么下一个结点中存储上一个结点长度的字节数也能会发生变化,比如以前长度是5,一个字节存储,现在变成了255,五个字节存储。如果每一个结点都会发生类似的情况,就会引发连锁更新问题

但是目前Redis的作者没有解决这个问题,因为他的概率很低。

其实我觉得解决这个问题的思路很简单,如果我们的节点有100万条数据,那么我们如果触发连锁更新的话,就会导致线程阻塞。那我觉得解决思路也很简单,可以和Dict的Rehash的处理方案一样,用两张表,一张进行正常的读取,不阻塞线程,另外一张表进行渐进式的连锁更新。

Ziplist的特性:

1.压缩列表可以看作是一种连续空间的"双向链表"。

2.节点之间不是通过指针相连接,而是记录上一个节点和本节点的长度来寻址,内存占用低。

3.如果列表数据过多,导致链表长度过长,会影响查询性能。

4.增加或删除较大数据的时候可能会引发连续更新问题。

QucikList:

ZipList虽然节省空间,但是他必须是连续的空间,如果占用内存比较多的话,申请效率就会很低。

为了解决这个问题,我们想到了ZipList分片,创建多个ZipList来存储数据:

而如何把这多个ZipList建立联系呢?

可以把这几个ZipList用指针连接起来。

而QuickList就是这种结构,他是一个双端链表,不过每一个节点是ZipList。

为了避免QuickList中的每一个 ZipList中entry过多,Redis提供了一个配置项:list-max-ziplist-size来限制。

如果值为正,则标识ZipList中允许的最大entry个数的最大值。

如果值为负,则代表ZipList的最大内存大小,分五种情况:

  • -1:每个Zip的内存大小不能超过4kb
  • -2:每个Zip的内存大小不能超过8kb
  • -3:每个Zip的内存大小不能超过16kb
  • -4:每个Zip的内存大小不能超过32kb
  • -5:每个Zip的内存大小不能超过64kb

这个参数的默认值为-2。

除了对每一个ZipList限制大小之外,QuicList还可以对节点的ZipList做压缩 ,通过配置项list-compress-depth来控制,因为链表一般都是从首尾访问比较多,所以首位是不压缩的。而这个配置的参数就是控制首尾不压缩的节点个数。

常见参数:

  • 0:所有节点都不压缩
  • 1:标识首尾各有一个结点不压缩,中间结点压缩
  • 2:标识QuickList的首尾各有两个结点不压缩,中间结点压缩。

讲了这么多,我们来看一看Redis中的源码:

quicklist源码:

quicklistNode源码:

用黑马的图来看一看ZipList的数据结构:

QuicList特征:

1.是一个结点为ZipList的双端链表。

2.结点采用了ZipList,解决了传统链表的内存占用问题。

3.控制了ZipList的大小,解决了连续内存空间的申请效率 。

4.中间节点可以压缩,进一步节省了内存。

SkipList:

虽然前面的几个List在空间方面做了优化,但是他的查询效率却很低,都需要遍历查询。

SkipList(跳表)就是为了提高查询效率而创建的一种数据结构

SkipList首先是链表,但是他和传统的链表比起来,有以下区别:

1.元素按照升序排列。

2.允许一个结点包含多个指针,指针跨度不同。

其实这个很好理解,既然是链表,那么我们要寻找一个元素就必须遍历链表。这是因为链表的每一个结点都存储的下一个结点的位置,没有跨越的指针。

而我们如果可以让一个结点的指针指向的距离不再是1,那不就在一定程度上优化了搜索效率。

传统链表:

跳表思想:

这样的话,传统链表我们如果要寻找4,就要遍历4个结点,但是现在基于跳表这种思想我们就只用遍历三个结点。

而这也就是跳表需要元素有序排列的意义,如果元素是乱序排放的,那么他往哪里跳就毫无头绪。

接下来我们看一看Redis中的跳表的数据结构:

我们来看一下一个成熟的跳表结构:

跳表特征:

1.跳表是一个双向链表。

2.跳表每一个接单都可以包含多层指针,层数在1-32之间。

3.不同层指针到下一个结点的跨度不同,层级越高,跨度越大。

4.增删改查效率和红黑树一样,但是实现更加简单。

RedisObijct:

Redis中任意数据类型的键和值都会被封装成为一个RedisObject,也叫做Redis对象,一个键对象,一个值对象。

cpp 复制代码
typedef struct redisObject{
//对象类型,分别是string,set,zet,list,hash
    unsigned type:4;
//编码方式
    unsigned encoding:4;
//当前对象最后一次被访问的时间
    unsighed lru:LRU_BITS;
//引用计数器,如果为0说明当前对象没有被引用,可以被回收
    int  refcount;
//指向五种数据类型对应的内存空间
    void * ptr;
} robj;

小心得:

一个redis对象的头部,不包含引用对象的指针就已经占据了16个字节的大小。所以我们在实际使用redis的时候,尽量避免直接使用String 这种数据结构,因为他是每一个字符串都需要一个键,那么就会多占用对象头,会造成很大的性能浪费。

这张图就很好的表述了我的意思。

RedisObject常见的编码有:

总结:

本文介绍完了Redis的底层数据机构,从下一篇来时,我们就要开始学习Redis我们表层使用的五种数据结构:String ,set,zet,hash,list。

如果我的内容对你有帮助,请点赞,评论,收藏。创作不易,大家的支持就是我坚持下去的动力!

相关推荐
xiaoyaolangwj4 分钟前
高翔【自动驾驶与机器人中的SLAM技术】学习笔记(十三)图优化SLAM的本质
学习·机器人·自动驾驶
静止了所有花开1 小时前
SpringMVC学习笔记(二)
笔记·学习
爱吃生蚝的于勒1 小时前
C语言内存函数
c语言·开发语言·数据结构·c++·学习·算法
L_cl3 小时前
Python学习从0到1 day26 第三阶段 Spark ④ 数据输出
学习
Mephisto.java3 小时前
【大数据学习 | HBASE】hbase的读数据流程与hbase读取数据
大数据·学习·hbase
红中马喽4 小时前
JS学习日记(webAPI—DOM)
开发语言·前端·javascript·笔记·vscode·学习
尘浮生6 小时前
Java项目实战II基于微信小程序的移动学习平台的设计与实现(开发文档+数据库+源码)
java·开发语言·数据库·spring boot·学习·微信小程序·小程序
Young_202202026 小时前
学习笔记——KMP
笔记·学习
行然梦实7 小时前
学习日记_20241110_聚类方法(K-Means)
学习·kmeans·聚类
马船长7 小时前
制作图片木马
学习