Redis数据结构和类型

news2024/11/27 12:50:59

Redis 包含五种数据类型,分别为String、List、Hash、Set、ZSet

底层实现的数据结构包SDS、双向链表、压缩列表、哈希表、整数集合、跳表

  • redis结构图

  • 数据类型和数据结构的关系

Redis六种数据结构

一、动态字符串(SDS)

Redis 是用 C 语言实现的,但是它没有直接使用 C 语言的 char* 字符数组来实现字符串,而是自己封装了一个名为简单动态字符串(simple dynamic string,SDS) 的数据结构来表示字符串,也就是 Redis 的 String 数据类型的底层数据结构是 SDS

总的来说,Redis 的 SDS 结构在原本字符数组之上,增加了三个元数据:len、alloc、flags,用来解决 C 语言字符串的缺陷,之所以 SDS 设计不同类型的结构体,是为了能灵活保存不同大小的字符串,从而有效节省内存空间。比如,在保存小字符串时,结构头占用空间也比较少

优点:

  • 获取字符串长度复杂度:C 语言的字符串长度获取 strlen 函数,复杂度是O(n),而 Redis 的 SDS 结构因为加入了 len 成员变量,所以是O(1)
  • 二进制安全:因为 SDS 不需要用 “\0” 字符来标识字符串结尾了
  • 不会发生缓冲区溢出:C 语言的字符串标准库提供的字符串操作函数,大多数(比如 strcat 追加字符串函数)都是不安全的,Redis 的 SDS 结构里引入了 alloc 和 leb 成员变量,这样 SDS API 通过 alloc - len 计算,可以算出剩余可用的空间大小,这样在对字符串做修改操作的时候,就可以由程序内部判断缓冲区大小是否足够用
  • 节省内存空间:SDS 结构中有个 flags 成员变量,表示的是 SDS 类型,之所以 SDS 设计不同类型的结构体,是为了能灵活保存不同大小的字符串,从而有效节省内存空间。比如,在保存小字符串时,结构头占用空间也比较少

二、链表(linkedlist)

list 结构为链表提供了链表头指针 head、链表尾节点 tail、链表节点数量 len、以及可以自定义实现的 dup、free、match 函数

三、压缩列表(ziplist)

压缩列表是 Redis 为了节约内存而开发的,它是由连续内存块组成的顺序型数据结构,有点类似于数组

当我们往压缩列表中插入数据时,压缩列表 就会根据数据是字符串还是整数,以及它们的大小会在 prevlen 和 encoding 这两个元素里保存不同的信息,这种根据数据大小进行对应信息保存的设计思想,正是 Redis 为了节省内存而采用的

压缩列表除了查找复杂度高的问题,压缩列表在插入元素时,如果内存空间不够了,压缩列表还需要重新分配一块连续的内存空间,而这可能会引发连锁更新的问题

压缩列表里的每个节点中的  prevlen 属性都记录了「前一个节点的长度」,而且 prevlen 属性的空间大小跟前一个节点长度值有关,比如:

  • 如果前一个

节点的长度小于 254 字节,那么 prevlen 属性需要用 1 字节的空间来保存这个长度值;

  • 如果前一个

节点的长度大于等于 254 字节,那么 prevlen 属性需要用 5 字节的空间来保存这个长度值

这种在特殊情况下产生的连续多次空间扩展操作就叫做「连锁更新」,连锁更新一旦发生,就会导致压缩列表 占用的内存空间要多次重新分配,这就会直接影响到压缩列表的访问性能

四、哈希表(hash)

Hash 表优点在于,它能以 O(1) 的复杂度快速查询数据。主要是通过 Hash 函数的计算,就能定位数据在表中的位置,紧接着可以对数据进行操作,这就使得数据操作非常快

但是存在的风险也是有,在哈希表大小固定的情况下,随着数据不断增多,那么哈希冲突的可能性也会越高,Redis 采用了链式哈希来解决哈希冲突,以及rehash

为了避免 rehash 在数据迁移过程中,因拷贝数据的耗时,影响 Redis 性能的情况,所以 Redis 采用了渐进式 rehash,也就是将数据的迁移的工作不再是一次性迁移完成,而是分多次迁移

触发 rehash 操作的条件,主要有两个:

  • 当负载因子大于等于 1 ,并且 Redis 没有在执行 bgsave 命令或者 bgrewiteaof 命令,也就是没有执行 RDB 快照或没有进行 AOF 重写的时候,就会进行 rehash 操作。
  • 当负载因子大于等于 5 时,此时说明哈希冲突非常严重了,不管有没有有在执行 RDB 快照或 AOF 重写,都会强制进行 rehash 操作

五、跳表(skiplist)

有序列表 zset 的数据结构,它类似于 Java 中的 SortedSet 和 HashMap 的结合体,一方面它是一个 set 保证了内部 value 的唯一性,另一方面又可以给每个 value 赋予一个排序的权重值 score,来达到 排序 的目的

因为 zset 要支持随机的插入和删除,所以它 不宜使用数组来实现,关于排序问题,我们也很容易就想到 红黑树/ 平衡树 这样的树形结构,为什么 Redis 不使用这样一些结构呢

  1. 性能考虑:在高并发的情况下,树形结构需要执行一些类似于 rebalance 这样的可能涉及整棵树的操作,相对来说跳跃表的变化只涉及局部
  2. 实现考虑:在复杂度与红黑树相同的情况下,跳跃表实现起来更简单,看起来也更加直观;

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

这种方法在插入数据的时候有很大的问题。新插入一个节点之后,就会打乱上下相邻两层链表上节点个数严格的 2:1 的对应关系。如果要维持这种对应关系,就必须把新插入的节点后面的所有节点 (也包括新插入的节点) 重新进行调整,这会让时间复杂度重新蜕化成 O(n)。删除数据也有同样的问题

skiplist 为了避免这一问题,它不要求上下相邻两层链表之间的节点个数有严格的对应关系,而是 为每个节点随机出一个层数(level)。从上面的创建和插入的过程中可以看出,每一个节点的层数(level)是随机出来的,而且新插入一个节点并不会影响到其他节点的层数,因此,插入操作只需要修改节点前后的指针,而不需要对多个节点都进行调整,这就降低了插入操作的复杂度。

六、整数集合(intset)

intset实质就是一个有序数组,存储元素紧密,空间利用率高,并通过二分法降低查找元素的时间复杂度,而且不容易因频繁地插入删除而产生内存碎片

支持整型编码,intset中所有数据元素的存储类型是一致的。新插入数据时,如果数据的类型大于当前intset的数据类型,为了防止溢出,会对其进行升级操作,然后才能将新元素添加到整数集合里

Intset 只支持升级,不支持降级

升级会引起整个 intset 进行内存重分配,并移动集合中的所有元素,这个操作的复杂度为O(n)

升级整数集合:

1)根据新元素的类型,拓展整数集合底层数组的空间大小,并且为新元素分配空间。

2)将底层数组现有的元素都转成新原属相同的类型,并且将转换后的元素放置到正确的位上,而且放置元素的过程中,需要继续位置数组的有序性质不变。

3)将新元素加入到底层数组里面

Redis五种基本数据类型

一、String

字符串对象的编码可以是int(整数 可以用long类型)、raw(超过39字节)、embstr(小于等于39字节)

raw编码会调用两次内存分配函数来创建redisObject结构和sdshdr结构,而embstr编码则通过调用一次内存分配函数来分配一块连续的内存,空间依次包含redisObject和sdshdr结构

  • 应用场景
    • 计数器:incr操作用来计数
    • 缓存:缓存普通信息
  • 常用API
set   [key]  [value]   给指定key设置值
get  [key]   获取指定key 的值
setex    [key]  [time]  [value]  等价于 set + expire 命令组合
expire [key]  [time]    给指定key 设置过期时间  单位秒
exists  [key]  判断是否存在指定key
mset  [key1]  [value1]  [key2]  [value2] ...... 批量存键值对
mget  [key1]  [key2] ......   批量取key
incr   [key]           如果value为整数 可用 incr命令每次自增1
incrby  [key] [number]  使用incrby命令对整数值 进行增加 number

二、Hash

Redis 散列可以存储多个键值对之间的映射。和字符串一样,散列存储的值既可以是字符串又可以是数值,并且用户同样可以对散列存储的数字值执行自增或自减操作。这个和 Java 的 HashMap 很像,每个 HashMap 有自己的名字,同时可以存储多个 k/v 对

  • 使用场景
    • Hash更适合存储结构化的数据:存储对象的每个属性
    • 购物车场景:hset [key] [field] [value] 存储购物车的三个要素
    • 用户已读:key:uid field:mid value:时间戳
  • 常用API
hset  [key]  [field] [value]    新建字段信息
hget  [key]  [field]    获取字段信息
hgetall  [key]  获取指定key 字典里的所有字段和值
hmset  [key]  [field1] [value1] [field2] [value2] ......   批量创建

三、List

有序可重复列表,编码可以是ziplist、linkedlist,列表对象保存的所有字符串元素的长度都小于 64 字节并且保存的元素数量小于 512 个,使用 ziplist 编码;否则使用 linkedlist

  • 使用场景
    • 消息队列:rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试
    • 可以用来实现粉丝/点赞列表,通过rpush插入数据,然后使用lrange命令读取最新的元素列表
  • 常用API
rpush  [key] [value1] [value2] ......    链表右侧插入
rpop    [key]  移除右侧列表头元素,并返回该元素
lpop   [key]    移除左侧列表头元素,并返回该元素
llen  [key]     返回该列表的元素个数
lrange [key]  [start_index] [end_index]   获取list 区间内的所有元素 (时间复杂度为 O(n))

四、Set

Redis 的set和list都可以存储多个字符串,他们之间的不同之处在于,list是有序可重复,而set是无序不可重复

  • 使用场景
    • 业务场景用户白名单:点赞、投稿
    • 业务失败兜底留存:用户打赏失败后记录信息
  • 常用API
sadd  [key]  [value]  向指定key的set中添加元素
smembers [key]    获取指定key 集合中的所有元素
scard [key]    获取集合的长度
srem [key] [value]  删除指定元素

五、SortSet

zset也叫SortedSet一方面它是个 set ,保证了内部 value 的唯一性,另方面它可以给每个 value 赋予一个score,代表这个value的排序权重。它的内部实现用的是一种叫作“跳跃列表”的数据结构

  • 使用场景
    • 排行榜:score为热度值或者点赞数等 飙升榜
    • 带权重的消息队列:重要的消息 score 大一些,普通消息 score 小一些,可以实现优先级高的任务先执行
    • 投稿审核优先级队列:score为mediaId uuid 发号器,越大说明最新
  • 常用API
zadd [key] [score] [value] 向指定key的集合中增加元素
zrem [key] [value]  删除元素
zrange [key] [start_index] [end_index] 获取下标范围内的元素列表,按score 排序输出
zrevrange [key] [start_index] [end_index]  获取范围内的元素列表 ,按score排序 逆序输出
zrangebyscore [key] [score1] [score2]  输出score范围内的元素列表
zcard [key]  获取集合列表的元素个数
zscore [key] [value] 获取元素的score

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

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

相关文章

Kotlin高仿微信-第12篇-单聊-图片

Kotlin高仿微信-项目实践58篇详细讲解了各个功能点,包括:注册、登录、主页、单聊(文本、表情、语音、图片、小视频、视频通话、语音通话、红包、转账)、群聊、个人信息、朋友圈、支付服务、扫一扫、搜索好友、添加好友、开通VIP等众多功能。 Kotlin高仿…

STC 51单片机45——51单片机对脉冲计数 汇编 16位除法

部分代码: ORG 0000H LJMP INIT ORG 0003H //外部中断0 LJMP INT0SUB ORG 0013H //外部中断1 LJMP INT1SUB ORG 0100H INIT: CLR P1.0 //控制端复位 …

D-019 EEROM硬件电路设计

EEROM硬件电路设计1 简介1.1 存储器的分类1.2EEPROM的特性2 接口介绍2.1 IIC接口2.2 SPI接口2.3 MicroWire 接口3 EEPROM 和 FLASH4 电路设计实战5 电路设计要点1 简介 1.1 存储器的分类 按照掉电数据是否丢失的特性,存储器可划分为: 易失性存储器&…

快排图文详解:快速排序算法的实现 - 【双边循环法与单边循环法 递归与非递归(栈的方式)的实现】

1.基本介绍 同冒泡排序一样,快速排序(Quicksort)也属于交换排序,通过元素之间的比较和交换位置来达到排序的目的。但快速排序是对冒泡排序的一种改进。 2.基本思想 关于基本思想,我们在这里先不考虑是如何具体实现的…

nuxtjs生命周期、项目创建、声明式导航与编程式导航、动态路由、嵌套路由、配置式路由、定制默认应用模板、扩展默认布局

文章目录1. 介绍2. 生命周期3. 项目创建4. 声明式导航和编程式导航5. 动态路由参数和验证6. 嵌套路由7. 404页面8. 配置式路由9. 定制默认应用模板10. 扩展默认布局10.1 默认布局10.2 自定义布局10.3 显示错误的布局1. 介绍 Nuxt.js 是一个基于 Vue.js 的通用应用框架。通过对…

离线解锁 CodeCombat 全关卡教程 使用docker安装实现

背景 暂时还没收入,想玩顺便,但官方的有点贵(是真的贵,扛不住) 前期准备 下载安装docker desktop https://www.123pan.com/s/fmvUVv-HqApH, 这个安装不会的随便搜一个教程,挺多的。我随便找了一…

创建实例化新表格及新行

这期讲一下如何创建创建实例化新表格及行进行添加数据, 在上图可以看到先实例化DataTable表格,用于接收数据, Columns 获取此表的列的集合, Add 创建并添加DataColumn对象, Typeof 数据类型 创建并添加表头。 接下来…

Java定时器选择

java计时器和死循环哪个好?哪个建议使用? 计时器性能更好,但是写起来稍微复杂一点。如果是非常短暂的延迟,用死循环也未尝不可。一般来说能不用死循环的尽量不用死循环!如果你使用的是JDK1.5以上的,可以使…

PyQt5可视化编程-控件

控件就像是应用这座房子的一块块砖。PyQt5有很多的控件,比如按钮,单选框,滑动条,复选框等等。我们将介绍一些很有用的控件: QCheckBox,ToggleButton,QSlider,QProgressBar, QCalendarWidget,QPixmap,QLineEdit,QSplitt…

实验四+ R型指令设计实验【计算机组成原理】

实验四+ R型指令设计实验【计算机组成原理】 前言推荐实验四+ R型指令设计实验结果附录defineInstMemIDEX最后前言 编写于 2022/11/22 VIP发布于 2022/11/22 实验于 2022/11/24 发布于 2022/11/24 以下内容源自计算机组成原理实验 仅供学习交流使用 推荐 实验四+ R型指令…

论文指标评价体系及权重计算

一 、评价指标体系 评价指标体系构建在实际研究中使用较为广泛,比如企业绩效评价指标体系构建、政府财政支出绩效评价、医院绩效评价研究等等。 ‍1、相关背景 在中国知网搜索 “ 评价指标 ”、“ 指标体系权重 ” 等相关关键词,可以发现,…

学习ASP.NET Core Blazor编程系列十三——路由(完)

九、NavigationManager 有的时候我们可能需要在代码里进行导航,如果是JavaScript我们会用window.location来切换页面,Blazor为我们提供了相应的封装:NavigationManager。使用NavigationManager可以通过代码直接进行页面间的跳转。我们在BookI…

算法训练Day34 贪心算法专题 | LeetCode1005.K次取反后最大化的数组和 ;134.加油站;135.分发糖果(不要两头兼顾,一边一边处理)

前言: 算法训练系列是做《代码随想录》一刷,个人的学习笔记和详细的解题思路,总共会有60篇博客来记录,计划用60天的时间刷完。 内容包括了面试常见的10类题目,分别是:数组,链表,哈…

基于人工势场法的移动机器人路径规划研究(Matlab代码实现)

目录 💥1 概述 📚2 运行结果 🎉3 参考文献 👨‍💻4 Matlab代码 💥1 概述 路径规划是移动机器人领域的热点研究方向,人工势场法已在工业机器人路径规划中得到广泛应用,近年来正逐…

RKMEDIA--VI的使用

在上一篇文章RKMEDIA使用简介中简单的介绍了rkmedia的组成部分,本章主要聊聊VI模块。 rkmedia中的VI模块主要可以从两个方式获取流:直接打开video节点的方式、使用rk平台的rkaiq。 1、直接打开video节点的方式 顾名思义只需要在vi初始化中配置VI_CHN_AT…

Redeis缓存查询基于元注解与AOP结合使用——不过时的优雅

Redeis缓存查询基于元注解与AOP结合使用 根据优化需要,数据查询的时候无法避免的使用Redis基于缓存查询,进而减少对于数据库的查询压力,对于过多的方法基于缓存存储,为提高代码的复用性,采用一种不过时的写法。 整体的…

spring JPA整合hibernate,IDEA社区版,Java

spring JPA整合hibernate,IDEA社区版,Java 本文基于IDEA社区版,不是IDEA企业版。 (1)首先用IDEA新建一个spring web项目。参考文章: IDEA社区版(Community Edition)创建Springboot-Web项目,J…

第十二章 使用 Monorepo 方式管理组件生态

组件库一般都会配有周边产品,比如 Admin 、Template、CLI 工具等等。周边产品相当于有关联的多个项目,更准确的说法是多个软件包。这个时候就应该使用 Monorepo 方式组织代码,方便频繁在多个项目间同时交替开发,同时发布&#xff…

图解LeetCode——895. 最大频率栈(难度:困难)

一、题目 设计一个类似堆栈的数据结构,将元素推入堆栈,并从堆栈中弹出 出现频率 最高的元素。 实现 FreqStack 类: FreqStack() 构造一个空的堆栈。void push(int val) 将一个整数 val 压入栈顶。int pop() 删除并返回堆栈中出现频率最高的元素。如果出…

圣杯与双飞翼布局,clip-path,列表与生成元素,计数器

❤️ Author: 老九 ☕️ 个人博客:老九的CSDN博客 🙏 个人名言:不可控之事 乐观面对 😍 系列专栏: 文章目录圣杯与双飞翼布局clip属性clip-path属性例子(不同区域使用不同颜色的导航&#xff09…