[从零开始学习Redis | 第九篇] 深入了解Redis数据类型

前言:

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

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

在前面的文章中,我们介绍了Redis的底层数据结构,而这篇文章,我们来介绍一下Redis中能够被我们直接使用的数据结构

底层数据结构:

【从零开始学习Redis | 第七篇】认识Redis底层数据结构(上)-CSDN博客文章浏览阅读1k次,点赞14次,收藏13次。在现代软件开发中,数据存储和处理是至关重要的一环。为了高效地管理数据,并实现快速的读写操作,各种数据库技术应运而生。其中,Redis作为一种高性能的内存数据库,广泛应用于缓存、会话存储、消息队列等场景。要深入了解Redis的工作原理,就必须先了解其底层数据结构。Redis之所以能够在性能上表现出色,部分原因在于其精心设计的数据结构。这些数据结构不仅简单高效,而且能够满足各种复杂的数据处理需求。本文将深入探讨Redis底层数据结构的设计原理,包括字符串哈希列表集合有序集合。https://liyuanxin.blog.csdn.net/article/details/136991225

目录

前言:

String:

List: 

Set:

ZSet:

Hash:

总结:

 


String:

String 是Redis中最常见的部分,他的基本编码方式是RAW,基于简单动态字符串(SDS)实现,存储上限为512mb

我们用图片来表示一下数据结构(RAW):

如果存储的SDS长度小于44字节,则会采用EMBSTR编码,此时object head 和SDS是一段连续空间,申请内存的时候只需要调用一次内存分配函数,效率更高

我们来看一下EMBSTR作为编码方式时的数据结构:

也就是说:当我们采用EMBSTR来作为编码方式的时候,能够减少内存申请的次数,而内存的申请需要操作系统从用户态转变为内核态,因此减少了内存申请的次数,就变相提升了效率。

当我们存储的字符串是整数的时候,而且大小在LONG_MAX(2,147,483,647)范围内,则会采用INT编码,直接把数据保存在ptr指针位置,不再需要SDS。

EMBSTR为什么最大存储44个字节? 

Redis的底层采用的是  jemalloc 这种内存回收算法,而这个算法在分配内存的时候,会以2^{^{n}}去做内存分配,而64字节是Redis的分片大小,也就是说:如果我们采用一整个分片的话,就不会产生内存碎片。

那么我们来看看:RedisObject头部的字节数为16。我们采用最节省空间的SDS结构也会占据三个个字节大小(len,alloc,flags),加上字符串结束标识符“/0”,我们能够存储字符串的最大字节数就是:64 - 16 - 4 = 44字节。

这也就是为什么EMBSTR编码方式能够存储的最大字节数为44字节的原因。

我们可以用Redis中的这个命令来查看此时的编码方式:

object encoding name

1.存入范围小于LONG_MAX的整数:

2.存入字节数小于44的字符串:

3.存入字节数大于44的字符串:

结果:

List: 

在Redis3.2版本之前,Redis采用的是ZipList和LinkedList来实现List,当元素数量小于512且元素大小小于64字节的时候采用ZipList,超过则采用LinkedList编码。

在3.2之后,Redis同一采用QuicList来实现List。

在当前的最新版本中,redis引入了一个新的数据结构:ListPack。来作为List的底层数据结构。

我们来看一看整个发展流程:

因为可以节省内存空间,创造了ZipList结构体---->因为为了降低连锁更新的影响面,创造了QuickList---->为了解决连锁更新问题,创建了ListPack

创建新的数据结构调用的是这个方法,我们点进去看一看:

在这里我们创建了一个类型叫做Listpack的变量。因此我们来看一看这个数据结构。

通过对listpackEntry源码的查看,我们可以发现:

为了规避掉zipList的连续更新的风险,listPack不再记录上一个结点的长度,而是改为记录本节点自身的长度。

 在listpack.c文件中,官方用了大量的宏定义来指定编码类型:


#define LP_ENCODING_7BIT_UINT 0
#define LP_ENCODING_7BIT_UINT_MASK 0x80
#define LP_ENCODING_IS_7BIT_UINT(byte) (((byte)&LP_ENCODING_7BIT_UINT_MASK)==LP_ENCODING_7BIT_UINT)
#define LP_ENCODING_7BIT_UINT_ENTRY_SIZE 2#define LP_ENCODING_6BIT_STR 0x80
#define LP_ENCODING_6BIT_STR_MASK 0xC0
#define LP_ENCODING_IS_6BIT_STR(byte) (((byte)&LP_ENCODING_6BIT_STR_MASK)==LP_ENCODING_6BIT_STR)#define LP_ENCODING_13BIT_INT 0xC0
#define LP_ENCODING_13BIT_INT_MASK 0xE0
#define LP_ENCODING_IS_13BIT_INT(byte) (((byte)&LP_ENCODING_13BIT_INT_MASK)==LP_ENCODING_13BIT_INT)
#define LP_ENCODING_13BIT_INT_ENTRY_SIZE 3#define LP_ENCODING_12BIT_STR 0xE0
#define LP_ENCODING_12BIT_STR_MASK 0xF0
#define LP_ENCODING_IS_12BIT_STR(byte) (((byte)&LP_ENCODING_12BIT_STR_MASK)==LP_ENCODING_12BIT_STR)#define LP_ENCODING_16BIT_INT 0xF1
#define LP_ENCODING_16BIT_INT_MASK 0xFF
#define LP_ENCODING_IS_16BIT_INT(byte) (((byte)&LP_ENCODING_16BIT_INT_MASK)==LP_ENCODING_16BIT_INT)
#define LP_ENCODING_16BIT_INT_ENTRY_SIZE 4#define LP_ENCODING_24BIT_INT 0xF2
#define LP_ENCODING_24BIT_INT_MASK 0xFF
#define LP_ENCODING_IS_24BIT_INT(byte) (((byte)&LP_ENCODING_24BIT_INT_MASK)==LP_ENCODING_24BIT_INT)
#define LP_ENCODING_24BIT_INT_ENTRY_SIZE 5#define LP_ENCODING_32BIT_INT 0xF3
#define LP_ENCODING_32BIT_INT_MASK 0xFF
#define LP_ENCODING_IS_32BIT_INT(byte) (((byte)&LP_ENCODING_32BIT_INT_MASK)==LP_ENCODING_32BIT_INT)
#define LP_ENCODING_32BIT_INT_ENTRY_SIZE 6#define LP_ENCODING_64BIT_INT 0xF4
#define LP_ENCODING_64BIT_INT_MASK 0xFF
#define LP_ENCODING_IS_64BIT_INT(byte) (((byte)&LP_ENCODING_64BIT_INT_MASK)==LP_ENCODING_64BIT_INT)
#define LP_ENCODING_64BIT_INT_ENTRY_SIZE 10#define LP_ENCODING_32BIT_STR 0xF0
#define LP_ENCODING_32BIT_STR_MASK 0xFF
#define LP_ENCODING_IS_32BIT_STR(byte) (((byte)&LP_ENCODING_32BIT_STR_MASK)==LP_ENCODING_32BIT_STR)

在这之中,int类型的编码方式一共有六种:

  •  LP_ENCODING_7BIT_UIN
  •  LP_ENCODING_13BIT_UIN
  •  LP_ENCODING_16BIT_UIN
  •  LP_ENCODING_24BIT_UIN
  •  LP_ENCODING_32BIT_UIN
  •  LP_ENCODING_64BIT_UIN

字符串编码方式一共有三种:

  • LP_ENCODING_6BIT_STR
  • LP_ENCODING_12BIT_STR
  • LP_ENCODING_32BIT_STR

我们用图来表示一下listPack的样式:

Set:

set是Redis的单列集合,它满足以下特点:

  • 不保证有序性
  • 保证元素唯一
  • 可以求交集,并集和差集

Set底层为了查询效率和唯一性,set采用HT编码(Dict)。Dict的key用来存储元素,Value统一为null。

我们之前的文章中介绍过Dict这种底层数据结构,感兴趣的可以看一看。

【从零开始学习Redis | 第七篇】认识Redis底层数据结构(上)-CSDN博客文章浏览阅读1k次,点赞14次,收藏13次。在现代软件开发中,数据存储和处理是至关重要的一环。为了高效地管理数据,并实现快速的读写操作,各种数据库技术应运而生。其中,Redis作为一种高性能的内存数据库,广泛应用于缓存、会话存储、消息队列等场景。要深入了解Redis的工作原理,就必须先了解其底层数据结构。Redis之所以能够在性能上表现出色,部分原因在于其精心设计的数据结构。这些数据结构不仅简单高效,而且能够满足各种复杂的数据处理需求。本文将深入探讨Redis底层数据结构的设计原理,包括字符串哈希列表集合有序集合。https://liyuanxin.blog.csdn.net/article/details/136991225当所有存储的数据都是整数的时候,并且元素个数不超过set-max-intset-entries时,Set会采用IntSet编码,以此来节省内存。

如果我们使用IntSet存储编码的时候,存储的元素个数超过了set-max-intset-entries的时候,就会转为Dict来进行存储。

set-max-intset-entries的最大值是512

ZSet:

 Zset实际上就是SortedSet,其中每一个元素都需要指定一个socre值和member值,他满足以下特点:

  • 可以根据socre值排序
  • member必须唯一
  • 可以根据member查询分数

也就是说:Zset必须满足键值对存储键必须唯一可排序这几个需求 

SkipList:可以排序,并且可以同时存储socre值和ele值。

HT(Dict):可以键值对存储,并且可以根据key找Value。

那么ZSet结合了这两种结构体:

typedef struct zset {dict *dict;zskiplist *zsl;
} zset;

我们来看一看创建Zset方法:

robj *createZsetObject(void) {zset *zs = zmalloc(sizeof(*zs));robj *o;//创建dictzs->dict = dictCreate(&zsetDictType);//创建ziplistzs->zsl = zslCreate();o = createObject(OBJ_ZSET,zs);o->encoding = OBJ_ENCODING_SKIPLIST;return o;
}

 它使用dict实现键值对的存储和唯一性,使用Skiplist来实现排序性。Zset的编码声明的是SKPIList。

而这种Zset结构实在是太浪费空间了,所以官方也给出了自己的优化方案:

)Zset在满足以下条件的时候,会采用ZipList结构来节省内存:

元素数量小于zset_max_ziplist_entries,默认值为128。

每个元素都小于zset_max_ziplist_value,默认值为64。

)Zset在满足以下条件的时候,会采用listpackt结构来节省内存:

元素数量小于zset_max_listpack_entries。

每个元素都小于zset_max_listpack_value。

robj *zsetTypeCreate(size_t size_hint, size_t val_len_hint) {if (size_hint <= server.zset_max_listpack_entries &&val_len_hint <= server.zset_max_listpack_value){return createZsetListpackObject();}robj *zobj = createZsetObject();zset *zs = zobj->ptr;dictExpand(zs->dict, size_hint);return zobj;
}

Hash:

Hash结构与Zset的结构非常类似:

  • 都是键值对存储。
  • 都需要根据键获取值
  • 键必须唯一

最关键的是Hash不需要进行排序。Hash的底层默认采用的是ListPack的编码方式。老的版本中是zipList。新版本为了规避ziplist连锁更新的问题,所以大量的替换了原有的ziplist为listpack

robj *createHashObject(void) {unsigned char *zl = lpNew(0);robj *o = createObject(OBJ_HASH, zl);o->encoding = OBJ_ENCODING_LISTPACK;return o;
}

在一些情况下,我们会把插入的对象转为dict类型的,在源码中可以看到:

我们点进这个方法:

这段代码是 Redis 中用于尝试将哈希对象转换为合适类型的函数 hashTypeTryConversion。让我解释一下它的主要作用:

  1. if (o->encoding != OBJ_ENCODING_LISTPACK) return;: 首先,它检查哈希对象的编码方式是否为列表包装编码。如果不是,则不执行转换,直接返回。

  2. 计算需要添加到哈希对象的新字段数量,并根据一定的条件决定是否将哈希对象转换为哈希表编码。

  3. 如果新字段数量超过了预设的阈值 server.hash_max_listpack_entries,则将哈希对象转换为哈希表编码,并根据新字段数量扩展哈希表的空间。

  4. 遍历输入参数列表中的键值对,计算它们的总长度,并检查每个值的长度是否超过了最大允许长度 server.hash_max_listpack_value如果有任何一个值的长度超过了限制,则将哈希对象转换为哈希表编码

  5. 最后,如果列表包装编码不适合将新字段添加到哈希对象中,或者任何值的长度超过了限制,那么就将哈希对象转换为哈希表编码。

这段代码的作用是在执行 HSETHMSET 命令时,根据参数列表中键值对的特征和限制条件,决定是否将哈希对象转换为哈希表编码,以确保能够有效地存储和操作数据。

总结:

通过本文的介绍,我们深入探讨了 Redis 中常用的数据结构及其应用。Redis 提供了丰富的数据类型,包括字符串、哈希、列表、集合和有序集合,每种数据结构都有其独特的特点和适用场景。

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

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

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

相关文章

栈的ADT实现——有空间限制的栈

1、研究有空间限制的栈的原因 当我们使用很多软件时都有类似“undo”功能,比如Web浏览器的回退功能、文本编辑器的撤销编辑功能。这些功能都可以使用Stack实现,但是在现实中浏览器的回退功能也好,编辑器的撤销功能也好,都有一定的数量限制。因此我们需要的不是一个普通的Sta…

齐护机器人方位传感器指南针罗盘陀螺仪

一、方位传感器原理及功能说明 齐护方位传感器是一款集成了三轴磁传感器芯片的方位传感器模块。适用于无人机、机器人、移动和个人手持设备中的罗盘&#xff08;指南针&#xff09;、导航和游戏等高精度应用。模块可以感应XYZ平面角度外&#xff0c;还可实现1至2的水平面角度罗…

【精品教程】护网HVV实战教程资料合集(持续更新,共20节)

以下是资料目录&#xff0c;如需下载&#xff0c;请前往星球获取&#xff1a; 01-HW介绍.zip 02-HTTP&Burp课程资料.zip 03-信息收集_3.zip 04-SQL注入漏洞_2.zip 05-命令执行漏洞.zip 06-XSS漏洞.zip 07-CSRF.zip 08-中间件漏洞.zip 09-SSRF.zip 10-XXE.zip 11-Java反序列…

用栈实现队列-使用两个栈来实现队列,则使我们插入的元素依照先入先出原则即可

一、用栈实现队列 https://leetcode.cn/problems/implement-queue-using-stacks/ &#xff08;一&#xff09;分析题目 &#xff08;二&#xff09;编写代码 typedef char STDataType; typedef struct Stack {STDataType* a; //int top; //相当于数组下标&#xff0c;注意…

idea 中 大于等于,不等于、小于等于等等这些符号发生了改变问题解决方法

1.问题描述 idea 中&#xff01;变为 ≠、 >变成了≥、<变成了 ≤ 等问题的解决办法 展示效果如下截图 解决方法

Spark 部署与应用程序交互简单使用说明

文章目录 前言步骤一&#xff1a;下载安装包Spark的目录和文件 步骤二&#xff1a;使用Scala或PySpark Shell本地 shell 运行 步骤3:理解Spark应用中的概念Spark Application and SparkSessionSpark JobsSpark StagesSpark Tasks 转换、立即执行操作和延迟求值窄变换和宽变换 S…

设计模式总结-外观模式(门面模式)

外观模式 模式动机模式定义模式结构外观模式实例与解析实例一&#xff1a;电源总开关实例二&#xff1a;文件加密 模式动机 引入外观角色之后&#xff0c;用户只需要直接与外观角色交互&#xff0c;用户与子系统之间的复杂关系由外观角色来实现&#xff0c;从而降低了系统的耦…

leetcode.面试题 02.07. 链表相交

题目 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 思路 假a在链表A上移动,b在链表B上移动&#xff0c;a移动完在B上开始&…

mysql中主键索引和联合索引的原理解析

mysql中主键索引和联合索引的原理解析 一、主键索引二、什么是联合索引? 对应的B树是如何生成的?1、建立索引方式2、什么是最左前缀原则?3、回表4、为什么要遵守最左前缀原则才能利用到索引?5、什么是覆盖索引?6、索引扫描底层原理7、order by为什么会导致索引消失&#x…

OA系统:把复杂流程和操作简单化,十分考验设计对业务的理解。

面向企业端的管理系统功能是越来越臃肿&#xff0c;每个厂商都想把功能做的大而全&#xff0c;如果不这么做就会给你留下市场空挡给竞争对手&#xff0c;在这种复杂化不可逆转的情形下&#xff0c;如何用操作简单化呢&#xff0c;本文给出几点建议。 1. 简化流程&#xff1a; …

opencv使用问题记录一二

opencv介绍 opencv是一个计算机视觉处理软件库&#xff0c;拥有强大的功能和高效的性能。 但是由于早期版本的原因&#xff0c;存在一些与目前主流使用不兼容的问题 问题与解决 RGB通道顺序 一般图片处理类库的通道顺序就是RGB&#xff0c;但是opencv的是反过来的&#xf…

RabbitMQ小记

参考书籍&#xff1a;朱忠华的《RabbitMQ实战指南》 一、基础概念 1.Exchange 1.1 创建方法的参数&#xff0c;exchangeDeclare() exchange&#xff1a;交换器的名称type&#xff1a;交换器的类型durable&#xff1a;是否持久化&#xff0c;true代表持久化。&#xff08;持…

【APUE】网络socket编程温度采集智能存储与上报项目技术------多路复用

作者简介&#xff1a; 一个平凡而乐于分享的小比特&#xff0c;中南民族大学通信工程专业研究生在读&#xff0c;研究方向无线联邦学习 擅长领域&#xff1a;驱动开发&#xff0c;嵌入式软件开发&#xff0c;BSP开发 作者主页&#xff1a;一个平凡而乐于分享的小比特的个人主页…

Git命令(1)[删除,恢复与移动]

文章目录 1.删除文件1.1命令----rm <filename>1.2命令----git rm <filename>1.1命令----git rm <filename> -f 2.恢复文件2.1命令----git restore <filename>2.1命令----git restore --staged <filename> 3.重命名文件3.1命令----mv 旧文件 新文…

八股面试速成—Java语法部分

暑期实习面试在即&#xff0c;这几天八股和算法轮扁我>_ 八股部分打算先找学习视屏跟着画下思维导图&#xff0c;然后看详细的面试知识点&#xff0c;最后刷题 其中导图包含的是常考的题&#xff0c;按照思维导图形式整理&#xff0c;会在复盘后更新 细节研究侧重补全&a…

租用阿里云的服务器多少钱?30元、61元、99元、165元、199元

租个阿里云的服务器多少钱&#xff1f;很便宜&#xff0c;云服务器2核2G3M固定带宽99元一年、2核4G服务器30元3个月、199元一年&#xff0c;轻量应用服务器2核2G3M配置61元一年、2核4G4M带宽165元一年&#xff0c;可以在阿里云CLUB中心查看 aliyun.club 当前最新的优惠券和活动…

GitOps - 为 OpenShift GitOps 配置邮件通知

《OpenShift 4.x HOL教程汇总》 说明&#xff1a;本文已经 在OpenShift 4.15 OpenShift GitOps 1.11.2 环境中验证 文章目录 ArgoCD 的 Notification 功能简介启动 OpenShift GitOps 的 Notification 功能配置邮件通知验证参考 说明&#xff1a;先根据《OpenShift 4 之 GitOp…

学习 Git 基础知识 - 日常开发任务手册

欢迎来到我关于 Git 的综合指南&#xff0c;Git 是一种分布式版本控制系统&#xff0c;已经在软件开发中彻底改变了协作和代码管理方式。 无论你是经验丰富的开发者还是刚开始编程之旅的新手&#xff0c;理解 Git 对于正确掌控代码、高效管理项目和与他人合作至关重要。 在本…

App应用的服务器如何增加高并发能力

大家好&#xff01;我是你们的好朋友咕噜铁蛋&#xff01;近年来&#xff0c;随着移动互联网的蓬勃发展&#xff0c;各类App应用如雨后春笋般涌现&#xff0c;用户量呈现爆发式增长。然而&#xff0c;随之而来的高并发访问问题也开始频繁出现&#xff0c;给服务器带来了极大的挑…

UV胶水与聚氯乙烯PVC材料的塑料粘接,效果如何?

UV胶水可以与聚氯乙烯PVC很好地粘接。 PVC是一种常见的塑料材料&#xff0c;UV胶水通常对PVC具有良好的粘接性能。UV胶水可以在紫外线照射下迅速固化&#xff0c;形成坚固的粘接&#xff0c;因此通常被用于PVC制品的粘接和修复。 UV胶水与PVC粘接的优点&#xff1a; 1. 快速固…