redis 底层数据结构

概述

Redis 6 和 Redis 7 之间对比:

 Redis6 和 Redis7 最大的区别就在于 Redis7 已经用 listpack 替代了 ziplist.

以下是基于 Redis 7基础分析。

RedisObject

Redis是⼀个<k,v>型的数据库,其中key通常都是string类型的字符串对象,⽽value在底层就统⼀是redisObject对象。Redis中的任意数据类型都会被封装为⼀个RedisObject,也叫做Redis对象, redisObject结构,实际上就是Redis内部抽象出来的⼀个封装所有底层数据结构的统⼀对象。这就类似于Java的⾯向对象的设计⽅式。

源码如下:

这⾥⾯⼏个核⼼字段意义如下:

  • type:Redis的上层数据类型。⽐如string,hash,set等,可以使⽤指令type key查看。

127.0.0.1:6379> set userid 1594
OK
127.0.0.1:6379> type userid
string

  • encoding: Redis内部的数据类型。可以使⽤指令 object encoding key查看。

127.0.0.1:6379> set userid 1594
OK
127.0.0.1:6379> object encoding userid
"int"

  • lru:当内存超限时会采⽤LRU算法清除内存中的对象。关于LRU与LFU,在redis.conf中有描述

# LRU means Least Recently Used

# LFU means Least Frequently Used

  • refcount:表示对象的引⽤次数。可以使⽤OBJECT REFCOUNT key 指令查看。
  • ptr:这是⼀个指针,指向真正底层的数据结构。encoding只是⼀个类型描述。实际数据是保存在ptr指向的具体结构⾥。

encoding 底层编码方式如下:

编码方式说明
0BJ_ENCODING_RAWraw编码动态字符串
OBJ_ENCODING_INTlong类型的整数的字符串
OBJ_ENCODING_HThash表(字典dict)
OBJ_ENCODING_ZIPMAP已废弃
OBJ_ENCODING_LINKEDLIST双端链表 (已废弃)
0BJ_ENCODING_ZIPLIST压缩列表(7.0 已废弃)
OBJ_ENCODING_INTSET 整数集合
OBJ_ENCODING_SKIPLIST跳表
0BJ_ENCODING_EMBSTRembstr的动态字符串
0BJ_ENCODING_QUICKLIST快速列表
0BJ_ENCODING_STREAMStream流
OBJ_ENCODING_LISTPACK紧凑列表

String 数据结构

SDS

Redis 没有直接使⽤ C 语⾔中的字符串,因为 C 语⾔字符串存在很多问题:
  • 获取字符串⻓度的需要通过运算
  • ⾮⼆进制安全
  • 不可修改
Redis 构建了⼀种新的字符串结构,称为简单动态字符串 (Simple Dynamic String)SDS
例如,我们执行命令:
127.0.0.1:6379> set name hi
OK

那么Redis将在底层创建两个SDS,其中一个是包含“name”的SDS,另一个是包含“hi”的SDS。

Redis是C语言实现的,其中SDS是一个结构体,源码如下:

例如,一个包含字符串“name”的sds结构如下:

1653984648404

SDS之所以叫做动态字符串,是因为它具备动态扩容的能力,例如一个内容为“hi”的SDS:

1653984787383

假如我们要给SDS追加一段字符串“,Amy”,这里首先会申请新内存空间:

如果新字符串小于1M,则新空间为扩展后字符串长度的两倍+1;

如果新字符串大于1M,则新空间为扩展后字符串长度+1M+1。称为内存预分配。

1653984822363

优点:

  1. 获取字符串长度的时间复杂度为O(1)
  2. 支持动态扩容
  3. 减少内存分配次数
  4. 二进制安全

String底层结构之int

如果存储的字符串是整数值,并且⼤⼩在LONG_MAX范围内,则会采⽤INT编码:直接将数据保存在RedisObject的ptr指针位置(刚好8字节),不再需要SDS了。

String底层结构之embstr

如果存储的SDS⻓度⼩于44字节,则会采⽤EMBSTR编码,此时object head与SDS是⼀段连续空间。申请内存时只需要调⽤⼀次内存分配函数,效率更⾼。

String底层结构之Raw

基本编码⽅式是RAW基于简单动态字符串( SDS)实现,存储上限为512mb。

Hash 数据结构

Hash 结构默认采⽤ listpack 编码,⽤以节省内存。
当数据量较⼤时, Hash 结构会转为 HT 编码,也就是 Dict ,触发条件有两个:
listpack 中的元素数量超过了hash-max-listpack-entries (默认512
listpack 中的任意entry⼤⼩超过了hash-max-listpack-value (默认64 字节)
127.0.0.1:6379> config get has*
3) "hash-max-listpack-value"
4) "64"
7) "hash-max-listpack-entries"
8) "512"

Hash数据结构之 listpack

listpack(紧凑列表)可以理解为一个替代版本的ziplist,由于ziplist中有着致命的缺陷-连锁更新,在极端条件下会有着极差的性能,导致整个redis响应变慢。因此在redis5中引入了新的数据结构listpack,作为ziplist的替代版。listpack在6以后已经作为t_hash的基础底层结构。

listpack的整体结构如下:


和 ziplist 列表项类似,listpack 列表项也包含了元数据信息和数据本身。不过,为了避免 ziplist 引起的连锁更新问题,listpack 中的每个列表项不再像 ziplist 列表项那样,保存其前一个列表项的长度,它只会包含三个方面内容:

  1.     当前元素的编码类型(entry-encoding)
  2.     元素数据 (entry-content)
  3.     编码类型和元素数据这两部分的长度 (entry-length)
核⼼是entry中原本记录前⼀个entry的⻓度,现在改为记录⾃⼰的⻓度。这样,就不会再因为前⼀个entry变化⽽影响⾃⼰的⻓度。这样也就没有了连锁更新的问题。

Hash数据结构之 hashtable

在Redis中hashtable就是字典dict,Dict由三部分组成,分别是:哈希表(DictHashTable)、哈希节点(DictEntry)、字典(Dict)。

代码

 结构图

当我们向 Dict 添加键值对时, Redis ⾸先根据 key 计算出 hash (h ),然后利⽤ h& sizemask来计算元素应该存储到数组中的哪个索引位置。
Dict 中的 HashTable 就是数组结合单向链表的实现,当集合中元素较多时,必然导致哈希冲突增多,链表过⻓,则效率会⼤⼤降低。
Dict 在每次新增键值对时都会检查负载因⼦( LoadFactor =used/size ),满⾜以下两种情况时会触发哈希表扩容:
哈希表的 LoadFactor >= 1 ,并且服务器没有执⾏ SAVE REWRITEAOF
等进程
哈希表的 LoadFactor>5

Dict 除了扩容以外,每次删除元素时,也会对负载因⼦做检查,当 LoadFactor 0.1 时,会做哈希表收缩:
if个⼤于等于minimal的 2 n

 

不管是扩容还是收缩,必定会创建新的哈希表,导致哈希表的 size sizemask 变化,⽽ key 的查询与 sizemask 有关。因此必须对哈希表中的每⼀个 key 重新计 算索引,插⼊新的哈希表,这个过程称为 rehash 。过程是这样的:
  1.  计算新hash表的realsize,即第⼀个⼤于等于dict.ht[0].used2n
  2.  按照新的realsize申请内存空间,创建dictht,并赋值给dict.ht[1]
  3. 设置rehashidx= 0,标示开始rehash
  4. dict.ht[0]中的每⼀个dictEntryrehashdict.ht[1]每次执⾏新增、查询、修改、删除操作时,都检查⼀下dict.rehashidx是否⼤于 -1,如果是则将ht[0].table[rehashidx]entry链表rehashdict.ht[1],井且将 rehashidx++。直⾄dict.ht[0]的所有数据都rehashdict.ht[1]
  5. dict.ht[1]赋值给dict.ht[0],给dict.ht[1]初始化为空哈希表
  6. rehashidx赋值为-1,代表rehash结束
  7. rehash过程中,新增操作,则直接写⼊ht[1],查询、修改和删除则会在dict.ht[0]dict.ht[1]依次查找并执⾏。这样可以确保h[0]的数据只减不增,随着 rehash最终为空

RedisObject

List 数据结构

List 结构默认采⽤ listpack 编码,⽤以节省内存。
• 当数据量较⼤时,List 结构会转为 quicklist,触发条件有1个:
  list-max-listpack-size 每个 list 中包含的节点⼤⼩或个数。正数表示个数,负数 -1 -5 表示⼤⼩。
  •  - 5:每个quicklist节点上的listpack大小不能超过64Kb(1kb=1024 bytes,即64*1024 bytes)
  •  -4:每个quicklist节点上的listpack大小不能超过32Kb(1kb=1024 bytes,即32*1024 bytes)
  •  -3:每个quicklist节点上的listpack大小不能超过16Kb(1kb=1024 bytes,即16*1024 bytes)
  •  -2:默认值,每个quicklist节点上的listpack大小不能超过8Kb(1kb=1024 bytes,即8*1024                 bytes)
  • -1:每个quicklist节点上的listpack大小不能超过4Kb(1kb=1024 bytes,即4*1024 bytes)
127.0.0.1:6379> config get lis*
3) "list-compress-depth"
4) "0"
5) "list-max-listpack-size"
6) "-2"

List 数据结构之 listpack

同Hash数据结构之 listpack 一样,参考上文。

List 数据结构之 quicklist

listpack 虽然节省内存,但申请内存必须是连续空间,如果内存占用较多,申请内存效率很低。但是我们要存储大量数据,超出了listpack最佳的上限,那怎么办?

Redis在3.2版本引入了新的数据结构QuickList,它是一个双端链表,只不过链表中的每个节点都是一个 listpack。我们就可以将数据存到多个 listpack中。

quicklistNode中间保存的数据结构。 在Redis6以前是ziplist,到Redis7中改为了listpack。

总结:

QuickList的特点:

  • 是一个节点为listpack的双端链表
  • 节点采用listpack,解决了传统链表的内存占用问题
  • 控制了listpack大小,解决连续内存空间申请效率问题
  • 中间节点可以压缩,进一步节省了内存

Set 数据结构

  1. 如果set的数据都是不超过64位的数字(⼀个long数字).就使⽤intset存储 IntSet 。
  2. 如果set的数据不是数字,并且数据的数量没有大于128且数据⼤⼩小于64,就⽤listpack存储。
  3. 如果数据的数量大于128或数据⼤⼩小于64,就改为使⽤hashtable存储。
127.0.0.1:6379> config get set*
3) "set-max-listpack-value"
4) "64"
5) "set-max-listpack-entries"
6) "128"
7) "set-max-intset-entries"
8) "512"

Set 数据结构之 IntSet

IntSet是Redis中set集合的一种实现方式,基于整数数组来实现,并且具备长度可变、有序等特征。
结构如下:

其中的encoding包含三种模式,表示存储的整数大小不同:

 为了方便查找,Redis会将intset中所有的整数按照升序依次保存在contents数组中,结构如图:

总结:

Intset可以看做是特殊的整数数组,具备一些特点:

  • Redis会确保Intset中的元素唯一、有序
  • 具备类型升级机制,可以节省内存空间
  • 底层采用二分查找方式来查询


Set 数据结构之 listpask

同Hash数据结构之 listpack 一样,参考上文。

Set 数据结构之 hashtable

同Hash数据结构之 hashtable一样,参考上文。

ZSet 数据结构

  1. 如果set 数据的数量没有大于128且数据⼤⼩小于64,就⽤listpack存储。
  2. 如果数据的数量大于128或数据⼤⼩小于64,就改为使⽤skiplist 存储。
127.0.0.1:6379> config get zset*
1) "zset-max-listpack-entries"
2) "128"
7) "zset-max-listpack-value"
8) "64"

ZSet 数据结构之 listpask

同Hash数据结构之 listpack 一样,参考上文。

ZSet 数据结构之 skiplist

zset类型的数据,底层需要先按照score进⾏排序。排序过程中是需要移动内存的。如果节
点数据不是太多,将这些内存移动完后,重新整理成⼀个类似数据Array的listpack结果是
可以接受的。但是如果数据量太⼤(节点数和数据⼤⼩),那么频繁移动内存,开销就⽐较⼤
了。这时,显然以链表这种零散的数据结构是⽐较合适的。
但是,对于⼀个单链表结构来说,要检索链表中的某⼀个数据,只能从头到尾遍历链表。
时间复杂度是O(N),性能是⽐较低的。
如何对链表结构进⾏优化呢?skiplist跳表就是⼀种思路。skiplist的优化思路是构建多层逐
级缩减的⼦索引,⽤更多的索引来提升搜索的性能。
skiplist是⼀种典型的⽤空间换时间的解决⽅案,优点是数据检索的性能⽐较⾼。时间复杂
度是O(logN),空间复杂度是O(N)。但是他的缺点也很明显,就是更新链表时,维护索引
的成本相对更⾼。因此,skiplist适合那些数据量⽐较⼤,且是读多写少的应⽤场景。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/924239.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

arm rk3588 onnx转rknn

一、环境部署&#xff1a; https://github.com/airockchip/rknn_model_zoo/tree/main/examples/yolo11 从该网址下载yolo11的模型。支持80种类型检测 二、下载模型 进入examples/yolo11/model文件夹&#xff0c;执行 ./download_model.sh 如图&#xff1a; 三、模型转换…

Flutter 3.24.5安装配置——2024年11月26日

目录 1️⃣前置安装使用环境配置步骤安装Flutter SDK安装Android SDK修改文件默认安装位置&#xff08;.gradle, AVD&#xff09;开始项目 2️⃣执行结果&#x1fab2;Bug找不到**.jar文件 &#x1f517;参考链接 1️⃣前置安装 使用环境 Windows 11IDEA 2024.2.3Flutter 3.2…

Pytest-Bdd-Playwright 系列教程(13):钩子(hooks)

Pytest-Bdd-Playwright 系列教程&#xff08;13&#xff09;&#xff1a;钩子&#xff08;hooks&#xff09; 前言一、什么是钩子&#xff1f;二、Pytest-Bdd 提供的钩子一览三、钩子用法详解1. pytest_bdd_before_scenario2. pytest_bdd_after_scenario3. pytest_bdd_before_s…

23种设计模式-生成器(Builder)设计模式

文章目录 一.什么是生成器设计模式&#xff1f;二.生成器模式的特点三.生成器模式的结构四.生成器模式的优缺点五.生成器模式的 C 实现六.生成器模式的 Java 实现七.代码解析八. 总结 类图&#xff1a; 生成器设计模式类图 一.什么是生成器设计模式&#xff1f; 生成器模式&am…

HCIP——堆叠技术实验配置

目录 一、堆叠的理论知识 二、堆叠技术实验配置 三、总结 一、堆叠的理论知识 1.1堆叠概述&#xff1a; 是指将两台交换机通过堆叠线缆连接在一起&#xff0c;从逻辑上变成一台交换设备&#xff0c;作为一个整体参与数据的转发。 1.2堆叠的基本概念 堆叠系统中所有的单台…

Python - 函数(四)

函数&#xff1a;在编写程序的过程中&#xff0c;有某一功能代码块出现多次&#xff0c; 但是为了提高编写的效率以及代码的重用&#xff0c;所以把具有独立功能的代码块组织为一个小模块&#xff0c;这就是函数 ‌Python中的函数‌是一组被命名的可执行代码&#xff0c;用于完…

豆包MarsCode算法题:三数之和问题

问题描述 思路分析 1. 排序数组 目的: 将数组 arr 按升序排序&#xff0c;这样可以方便地使用双指针找到满足条件的三元组&#xff0c;同时避免重复的三元组被重复计算。优势: 数组有序后&#xff0c;处理两个数和 target - arr[i] 的问题可以通过双指针快速找到所有可能的组…

使用guzzlehttp异步多进程实现爬虫业务

Python和PHP核心技术共享平台 背景 小哥近来在通过动态代理池爬取一些公司需要的大文件pdf规格书的处理。遇到的难点&#xff0c;如何保证服务器CPU、连接数等正常情况下&#xff0c;多进程、异步快速处理这些业务并且保证准确。下面小哥就给看官唠嗑一下&#xff0c;我使用gu…

Chrome和edge浏览器如何为任何网站强制暗模式

前言 因为我的编辑器是黑色&#xff0c;可能是看的时间长了比较喜欢这种颜色了&#xff0c;感觉白色有些刺眼。尤其是看文章时&#xff0c;两边的空白纯白色&#xff0c;所以强迫症搜素设置了谷歌浏览器和edge如何设置成黑色。 Chrome和edge浏览器如何为任何网站强制暗模式 前…

STM32-- keil使用 -设备选择

keil-arm 在project--》manager--》pack installer&#xff0c;更新芯片包&#xff0c; 有些这里不全面&#xff0c;可以在官网下载包进行安装。 比如stm8系列在这里是没有的&#xff0c;因为他的内核是哈弗架构。还有51单片机要在keil c51里面找 keil5中找不到或没有对应的…

K8s内存溢出问题剖析:排查与解决方案

文章目录 一、背景二、排查方案&#xff1a;1. 可能是数据量超出了限制的大小&#xff0c;检查数据目录大小2. 查看是否是内存溢出2.1 排查数据量&#xff08;查看数据目录大小是否超过limit限制&#xff09;2.2 查看pod详情发现问题 三、解决过程 一、背景 做redis压测过程中…

在 Mac ARM 架构(例如 M1 或 M2 芯片)上安装 Node.js

文章目录 方法一&#xff1a;使用 Homebrew 安装 Node.js方法二&#xff1a;使用 Node Version Manager (NVM) 安装 Node.js方法三&#xff1a;从 Node.js 官方网站下载安装包注意事项 在 Mac ARM 架构&#xff08;例如 M1 或 M2 芯片&#xff09;上安装 Node.js 可以通过几种不…

pycharm2021.1汉化失败 “chinese (simplified) language pack“ was not installed

汉化报错&#xff1a;pycharm plugin “chinese (simplified) language pack” was not installed : Invalid filename returned by a server 翻译&#xff1a;pycharm 插件“中文&#xff08;简体&#xff09;语言包”未安装&#xff1a;服务器返回的文件名无效 解决&#…

Java基于 SpringBoot+Vue的口腔管理平台(附源码+lw+部署)

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

图书系统小案例

目前就实现了分页查询&#xff0c;修改&#xff0c;删除功能 这个小案例练习到了很多技能&#xff0c;比如前后端交互、异步请求、三层架构思想、后端连接数据库、配置文件、基础业务crud等等 感兴趣的小伙伴可以去做一个试试 准备工作 1、使用maven构建一个web工程 打开i…

延时系统建模,整数延时与分数延时,连续传函与离散传函,Pade近似与Thiran近似,Matlab实现

连续传递函数 严格建模&#xff1a;指数形式 根据拉普拉斯变换的性质&#xff0c; [ f ( t ) ↔ F ( s ) ] ⇔ [ f ( t − t 0 ) ↔ e − s t 0 F ( s ) ] \left[ {f\left( t \right) \leftrightarrow F\left( s \right)} \right] \Leftrightarrow \left[ {f\left( {t - {t_0…

3.14MayBeSomeStack

栈指针是sp 静态数据在内存中位置不改变 码距就是相邻两个合法的数据之间的差距&#xff0c;如果为2的话&#xff0c;相邻两个合法的数据之间存在一个冗余的数据&#xff0c;这个数据肯定是出错的&#xff0c;但是无法判断是哪个合法的数产生的&#xff1b; 如果码距是3的话&…

NLP 2、机器学习简介

人生的苦难不过伏尔加河上的纤夫 —— 24.11.27 一、机器学习起源 机器学习的本质 —— 找规律 通过一定量的训练样本找到这些数据样本中所蕴含的规律 规律愈发复杂&#xff0c;机器学习就是在其中找到这些的规律&#xff0c;挖掘规律建立一个公式&#xff0c;导致对陌生的数…

springboot视频网站系统的设计与实现(代码+数据库+LW)

摘 要 使用旧方法对视频信息进行系统化管理已经不再让人们信赖了&#xff0c;把现在的网络信息技术运用在视频信息的管理上面可以解决许多信息管理上面的难题&#xff0c;比如处理数据时间很长&#xff0c;数据存在错误不能及时纠正等问题。 这次开发的视频网站系统管理员功…

探索Python网页解析新纪元:requests-html库揭秘

文章目录 **探索Python网页解析新纪元&#xff1a;requests-html库揭秘**1. 背景介绍&#xff1a;为何选择requests-html&#xff1f;2. requests-html库是什么&#xff1f;3. 如何安装requests-html库&#xff1f;4. 五个简单的库函数使用方法4.1 发起HTTP请求4.2 解析HTML内容…