【Redis】Redis的数据结构

news2024/11/24 19:13:59

【Redis】Redis的数据结构

文章目录

  • 【Redis】Redis的数据结构
    • 1. 动态字符串SDS
    • 2. IntSet
      • 2.1 IntSet升级
    • 3. Dict
      • 3.1 Dict的扩容
      • 3.2 Dict的收缩
      • 3.3 Dict的rehash
    • 4. ZipList
      • 4.1 ZipList中的Entry
        • 4.1.1 Encoding编码
      • 4.2 ZipList的连锁更新问题
      • 4.3 特性
    • 5. QuickList

1. 动态字符串SDS

Redis中保存的Key是字符串,value往往是字符串或者字符串的集合。可见字符串是Redis中最常用的一种数据结构。

虽然Redis是用C写的,但是为什么没有直接使用C的字符串?因为C语言字符串存在很多问题:

  1. 获取字符串长度需要通过运算(数组实际长度=数组长度-1)
  2. 非二进制安全\0 是数组结束的标识符,这意味着我们数组中不能存储 \0 元素,否则遍历数组会出现问题
  3. 不可修改

image-20230624113024458

所以,Redis构建了一种新的字符串结构,称为简单动态字符串 (Simple Dynamic String),简称SDS

例如,执行命令 set name 张三,那么Redis将在底层创建两个SDS,一个包含“name“的SDS,一个包含”张三“的SDS。


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

image-20230624164559433

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

image-20230624164700489

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

image-20230624164815408

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

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

这种行为称作内存预分配

image-20230624165109650

SDS相较于C语言字符串的优点:

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

2. IntSet

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

结构如下:

image-20230624180637342

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

image-20230624180802077

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

image-20230624181350976

现在,数组中每个数字都在 int16_t 的范围内,因此采用的编码方式是 INTSET_ENC_INT16 ,每部分占用的字节大小为:

  • encoding:4字节
  • length:4字节
  • contents:2字节 * 3 =6字节

2.1 IntSet升级

假设有一个intset,元素为{5,10,20},采用的编码是 INTSET_ENC_INT16 ,则每个整数占2个字节:

image-20230624184949852

我们向其中添加一个数字:50000,这个数字超出了 int16_t 的范围,intset会自动升级编码方式到合适的大小。

以当前案例来说明流程:

  1. 升级编码为 INTSET_ENC_INT32 ,每个整数占4字节,并按照新的编码方式及元素个数扩容数组
  2. 倒序依次将数组中的元素拷贝到扩容后的正确位置
  3. 将待添加的元素(50000) 这个数字放入数组末尾
  4. 最后,将inset的encoding属性改为 INTSET_ENC_INT32,将length属性改为4

image-20230624185501363

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

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

3. Dict

Redis是一个键值型的数据库,我们可以根据键实现快速的增删改查。而键与值的映射关系正是通过Dict来实现的。

Dict由三部分组成,分别是:哈希表(DictHashTable)哈希节点(DictEntry)字典(Dict)

image-20230625114548062image-20230625114727544

image-20230625115344528

image-20230625163453423

当我们向Dict添加键值对时,Redis首先根据key计算出hash值,然后利用 h & sizemask 来计算元素应该存储到数组中的哪个索引位置。

假设哈希表长度为4,我们存储k1=v1,k1的哈希值h=1,那么 1&3=1,因此k1=v1要存储到数组角标1位

image-20230625115126655

再次存储k2=v2,k2的哈希值h=1,那么k2也要存储到数组角标1位。使用头插法,避免遍历。

image-20230625163603658


3.1 Dict的扩容

Dict中的HashTable就是数组结合单向链表的实现,当集合中元素较多时,必然导致哈希冲突增多,链表过长,那么查询效率会大大降低。

Dict在每次新增键值对时都会检查负载因子(LoadFactor=user/size),满足以下两种情况时会触发哈希表扩容

  1. 哈希表的 LoadFactor>=1 ,并且服务器没有执行 BGSAVE 或者 BGREWRITEAOF 等后台进程
  2. 哈希表的 LoadFactor>5

image-20230625165701915


3.2 Dict的收缩

Dict除了扩容以外,每次删除元素时, 也会对负载因子做检查,当 LoadFactor<0.1 时,会做哈希表收缩:

image-20230625165832961

image-20230625165840449image-20230625165845232


3.3 Dict的rehash

不管扩容还是收缩,必定会创建新的哈希表,导致哈希表的size和sizemask变化,而key的查询与sizemask有关。因此必须对哈希表中的每一个key重新计算索引,插入新的哈希表,这个过程称为 rehash

Dict的rehash并不是一次性完成的,如果Dict包含数百万的entry,要在一次rehash完成,极有可能导致主线程阻塞。因此Dict的rehash是分多次、渐进式的完成,因此称为 渐进式rehash。流程如下:

  1. 计算新hash表的size,值取决于当前要做的是扩容还是收缩:
    • 如果是扩容,则新size为第一个大于等于dict.ht[0].used + 1的2^n
    • 如果是收缩,则新size为第一个大于等于dict.ht[0].used的2^n(不得小于4)
  2. 按照新的size申请内存空间,创建dictht,并赋值给dict.ht[1]
  3. 设置dict.rehashindex=0,表示开始rehash
  4. 每次执行增删改查操作时,都检查一下dict.rehashindex是否大于-1,如果是,则将dict.ht[0].table[rehashindex]的entry链表hash到dict.ht[1],并且将rehashindex++,直至dict.ht[0]的所有数据都rehash到dict.ht[1]
  5. 将dict.ht[1]赋值给dict.ht[0],给dict.ht[1]初始化为空哈希表,释放原来的dict.ht[0]的内存
  6. 将rehashindex赋值为-1,代表rehash结束
  7. 在rehash过程中,新增操作,则直接写入ht[1],查询、修改、删除则会在dict.ht[0]和dict.ht[1]依次查找并执行。这样可以确保ht[0]的数据只减不增,随着rehash进行,ht[0]最终为空。

4. ZipList

ZipList是一种特殊的“双端链表”,由一系列特殊编码的连续内存块组成。可以在任意一端进行压入/弹出操作,并且该操作的时间复杂度为O(1)

image-20230625183939725

属性类型长度用途
zlbytesuint32_t4 字节记录整个压缩列表占用的内存字节数
zltailuint32_t4 字节记录压缩列表表尾节点距离压缩列表的起始地址有多少字节,通过这个偏移量,可以确定表尾节点的地址。
zllenuint16_t2 字节记录了压缩列表包含的节点数量。 最大值为UINT16_MAX (65534),如果超过这个值,此处会记录为65535,但节点的真实数量需要遍历整个压缩列表才能计算得出。
entry列表节点不定压缩列表包含的各个节点,节点的长度由节点保存的内容决定。
zlenduint8_t1 字节特殊值 0xFF (十进制 255 ),用于标记压缩列表的末端。

4.1 ZipList中的Entry

ZipList中的Entry并不像普通链表那样记录前后节点的指针,因为记录两个指针要占用16字节,浪费内存。所以采用下面这种结构:

image-20230625184307623

  • previous_entry_length:表示前一节点的长度,占1个或5个字节
    • 如果前一节点的长度小于254字节,则采用1个字节来保存这个长度值
    • 如果前一节点的长度大于254字节,则采用5个字节保存这个长度值,第一个字节为0xfe,后四个字节才是真实长度数据
  • encoding:编码属性,记录content的数据类型(字符串还是整数)以及长度,占用1、2或5个字节
  • content:负责保存节点的数据,可以是字符串或整数

注意:ZipList中所有存储长度的数值均采用小端字节序,即低位字节在前,高位字节在后。例如:数值0x1234,采用小端字节序后实际存储值为:0x3412


4.1.1 Encoding编码

ZipListEntry中的encoding编码分为字符串和整数两种:

  • 字符串:如果encoding是以“00”、“01”、或者“10”开头,则证明content是字符串
编码编码长度字符串大小
|00pppppp|1 bytes<= 63 bytes
|01pppppp|qqqqqqqq|2 bytes<= 16383 bytes
|10000000|qqqqqqqq|rrrrrrrr|ssssssss|tttttttt|5 bytes<= 4294967295 bytes

假设保存字符串“ab”,使用16进制表示:

image-20230625192321159

保存字符串:”ab”和“bc”,完整表示如下:

image-20230625192400122


  • 整数:如果encoding是以“11”开始,则证明content是整数,且encoding固定只占用1个字节
编码编码长度整数类型
110000001int16_t(2 bytes)
110100001int32_t(4 bytes)
111000001int64_t(8 bytes)
11110000124位有符整数(3 bytes)
1111111018位有符整数(1 bytes)
1111xxxx1直接在xxxx位置保存数值,范围从0001~1101,减1后结果为实际值

例如:一个ZipList中包含两个整数值:“2” 和 “5”

image-20230625194435016


4.2 ZipList的连锁更新问题

ZipList的每个Entry都包含previous_entry_length来记录上一个节点的大小,长度是1个或5个字节:

  • 如果前一节点的长度小于254字节,则采用1个字节来保存这个长度值
  • 如果前一节点的长度大于等于254字节,则采用5个字节来保存这个长度值,且第一个字节为0xfe,后四个字节才是真实长度数据

现在,假设我们有N个连续的、长度为250~253字节之间的entry,因此entry的previous_entry_length属性用一个字节即可表示,如下图所示:

image-20230626002225615

突然要插入一个长度为254字节的entry,采用头插法插入,那么原来第一个entry的previous_entry_length就变成了5,那么整体的entry长度就由原来的250变成了254字节,那么又导致原来的第二个entry的previous_entry_length变成了5…不断重复这个操作,直到遇到第一个长度<250的entry才结束。

image-20230626002529923

ZipList这种特殊情况下产生的连续多次空间扩展操作称之为连锁操作(Cascade Update)。新增、删除都可能导致连锁更新的发生。


4.3 特性

ZipList的特性:

  1. 压缩列表可以看作一种连续内存空间的“双向链表”
  2. 列表的节点之间不是通过指针连接,而是记录上一节点和本节点长度来寻址,内存占用较低
  3. 如果列表数据过多,导致链表过长,可能影响查询性能
  4. 增或删较大数据有可能发生连续更新问题

5. QuickList

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【软考网络管理员】2023年软考网管初级常见知识考点(7)-生成树协议

涉及知识点 STP的原理&#xff0c;端口的状态&#xff0c;RSTP协议&#xff0c;MSTP协议&#xff0c;软考网络管理员常考知识点&#xff0c;软考网络管理员网络安全&#xff0c;网络管理员考点汇总。 原创于&#xff1a;CSDN博主-《拄杖盲学轻声码》&#xff0c;更多考点汇总可…

模拟电路系列分享-运放的关键参数2

目录 概要 整体架构流程 技术名词解释 1.输入偏置电流&#xff1a; 2.输入失调电流 技术细节 总结; 概要 提示&#xff1a;这里可以添加技术概要 实际运放与理想运放具有很多差别。理想运放就像一个十全十美的人&#xff0c;他学习100 分&#xff0c;寿命无限长&#xff0c;长…

Modal对话框(antd-design组件库)展示所有配置选项和onChange的作用

1.Modal对话框 模态对话框。 2.何时使用 需要用户处理事务&#xff0c;又不希望跳转页面以致打断工作流程时&#xff0c;可以使用 Modal 在当前页面正中打开一个浮层&#xff0c;承载相应的操作。 另外当需要一个简洁的确认框询问用户时&#xff0c;可以使用 App.useApp 封装的…

创建微信小程序的几种方式

创建微信小程序的几种方式 1. 使用原生方式 在官网上下载微信开发者工具&#xff0c;之后使用微信开发者工具新建项目即可。 微信这边提供了多个模板&#xff0c;可以直接下载模板快速搭建上线&#xff0c;也可以使用空白模板根据需求自行编写。 空白模板项目结构&#xff1…

C语言:打印0-100000中的自幂数(水仙花数是其中一种)

题目&#xff1a; 求出 0&#xff5e;100000 之间的所有 自幂数 并输出。 自幂数是指一个n位数&#xff0c;其各位数字的n次方之和恰好等于该数本身&#xff0c; 如:153&#xff1d;1^3&#xff0b;5^3&#xff0b;3^3&#xff0c; 则153是一个自幂数。 思路&#xff1a; 总体…

【数据网格架构】分布式数据网格作为集中式数据单体的解决方案

企业数据架构师不应构建大型集中式数据平台&#xff0c;而应创建分布式数据网格。 ThoughtWorks 的首席技术顾问 Zhamak Dehghani 在旧金山 QCon 的演讲和相关文章中表示&#xff0c;这种方法的改变需要范式转变。随着数据变得越来越普遍&#xff0c;传统的数据仓库和数据湖架构…

Linux基础+命令操作+mysql、tomcat、nginx、RabbitMQ、Redis,ElasticSearch

配置代理 一、永久设置 //编辑配置文件 vi /etc/profile //在该配置文件的最后添加代理配置 export http_proxyhttp://f1336515:password10.137.255.169:3128 //代理服务器ip地址和端口号 export https_proxyhttp://f1336515:password10.137.255.169:3128 //代理服务器ip…

【软考网络管理员】2023年软考网管初级常见知识考点(11)-TCP和UDP详解

涉及知识点 传输控制协议TCP是什么&#xff0c;三次握手的概念理解&#xff0c;用户数据报协议UDP是什么&#xff0c;软考网络管理员常考知识点&#xff0c;软考网络管理员网络安全&#xff0c;网络管理员考点汇总。 原创于&#xff1a;CSDN博主-《拄杖盲学轻声码》&#xff0…

AntV G6新版源码浅析

前言 AntV是蚂蚁金服全新一代数据可视化解决方案&#xff0c;其中G6主要用于解决图可视领域相关的前端可视化问题&#xff0c;其是一个简单、易用、完备的图可视化引擎。本文旨在通过简要分析G6 5.x版本源码来对图可视领域的一些底层引擎进行一个大致了解&#xff0c;同时也为…

【玩转Linux操作】详细讲解expr,read,echo,printf,test,[]等命令

&#x1f38a;专栏【玩转Linux操作】 &#x1f354;喜欢的诗句&#xff1a;更喜岷山千里雪 三军过后尽开颜。 &#x1f386;音乐分享【free loop】 大一同学小吉&#xff0c;欢迎并且感谢大家指出我的问题&#x1f970; 文章目录 &#x1f354;expr命令⭐表达式说明 &#x1f3…

JAVA:Springboot动态装配Druid多数据源

1、简介 最近打算搭建一个鉴权中心服务&#xff0c;采用springbootFastMybatis装配Druid&#xff0c;考虑后续拓展采用Druid多数据源配置&#xff0c;以一个数据源为主&#xff0c;多个动态数据源为辅的结构。除了数据库&#xff0c;后续会结合shiro安全框架来搭建。 2、引用…

【Leetcode60天带刷】day33回溯算法——1005.K次取反后最大化的数组和 134. 加油站 135. 分发糖果

​ 题目&#xff1a; 1005. K 次取反后最大化的数组和 给你一个整数数组 nums 和一个整数 k &#xff0c;按以下方法修改该数组&#xff1a; 选择某个下标 i 并将 nums[i] 替换为 -nums[i] 。 重复这个过程恰好 k 次。可以多次选择同一个下标 i 。 以这种方式修改数组后&am…

将视频转为幻灯片图像:利用OpenCV实现视频资料转换的指南

视频成为了传播知识和信息的重要媒介之一。然而&#xff0c;有时我们需要以静态的形式保存视频内容&#xff0c;例如将视频讲座转换为幻灯片或图像&#xff0c;以便于分享、存档或打印。幸运的是&#xff0c;OpenCV这一功能强大的计算机视觉库提供了各种技术和工具&#xff0c;…

机器学习之线性回归算法

目录 线性回归算法 求导法推导 梯度下降法推导 线性回归实现人脸识别 导入数据 构建标签矩阵 经典线性回归求导法实现 经典线性回归梯度下降法实现 岭回归实现 套索回归实现 局部加权线性回归实现 可视化 人脸识别 线性回归算法 求导法推导 梯度下降法推导 线性回…

chatgpt赋能python:Title:Python编程中的空格怎么用?详细教程!

Title: Python编程中的空格怎么用&#xff1f;详细教程&#xff01; Introduction: Python编程的空格使用一直是令人困惑的话题之一&#xff0c;但它却是Python语言中非常重要的一部分。空格在Python程序中用来表示代码块的开始和结束&#xff0c;因此不同的空格使用方式可能…

【夜深人静学数据结构与算法 | 第十篇】动态规划

目录 前言&#xff1a; 动态规划&#xff1a; 常见应用&#xff1a; 解题步骤&#xff1a; 动态规划的简化步骤&#xff1a; 案例&#xff1a; 509. 斐波那契数 - 力扣&#xff08;LeetCode&#xff09; 70. 爬楼梯 - 力扣&#xff08;LeetCode&#xff09; 62. 不同路…

【软考网络管理员】2023年软考网管初级常见知识考点(10)- 网际协议IP及IPV6,IPV4详解

涉及知识点 分类的IP地址&#xff0c;子网划分&#xff0c;CIDR和路由汇聚&#xff0c;IPV4数据报格式&#xff0c;IPV6协议&#xff0c;软考网络管理员常考知识点&#xff0c;软考网络管理员网络安全&#xff0c;网络管理员考点汇总。 原创于&#xff1a;CSDN博主-《拄杖盲学…

Java的理论知识部分

文章目录 前言 一、Java的发展 1.1、Java的出现 1.2、Java官方网址 1.3、Java的平台 1.4、Java各版本新加的内容 1.5、java特点 1.6、Java的三种运行机制 1.7、Java的编译与运行 1.8、补充内容——华为鲲鹏jdk以及鲲鹏计算 二、面向对象程序编程 2.1、对象与类 2.2、Ja…

第一次安装cocoapods经历

先是执行&#xff1a;sudo gem install cocoapods 报错&#xff1a; ERROR: Error installing cocoapods: The last version of activesupport (> 5.0, < 8) to support your Ruby & RubyGems was 6.1.7.3. Try installing it with gem install activesupport -v…

无需麻烦,快速下载MySQL JDBC驱动程序!

如何提升你的MySQL数据库操作速度呢&#xff1f; 不必再费时寻找&#xff0c;我讲为你带来最简便、快速的MySQL JDBC驱动程序下载方法&#xff01; 无需繁琐步骤&#xff0c;轻松获取所需&#xff0c;让你的数据库操作更加流畅&#xff0c;事半功倍&#xff01;立即点击下载即…