[从零开始学习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.rhkb.cn/news/298807.html

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

相关文章

重读Java设计模式: 桥接模式详解

引言 在软件开发中&#xff0c;经常会遇到需要在抽象与实现之间建立连接的情况。当系统需要支持多个维度的变化时&#xff0c;使用传统的继承方式往往会导致类爆炸和耦合度增加的问题。为了解决这一问题&#xff0c;我们可以使用桥接模式。桥接模式是一种结构型设计模式&#…

ARM架构学习笔记2-汇编

RISC是精简指令集计算机&#xff08;RISC:Reduced Instruction Set Computing&#xff09; ARM汇编概述 一开始&#xff0c;ARM公司发布两类指令集&#xff1a; ① ARM指令集&#xff0c;这是32位的&#xff0c;每条指令占据32位&#xff0c;高效&#xff0c;但是太占空间 2…

物联网实战--入门篇之(十)安卓QT--后端开发

目录 一、项目配置 二、MQTT连接 三、数据解析 四、数据更新 五、数据发送 六、指令下发 一、项目配置 按常规新建一个Quick空项目后&#xff0c;我们需要对项目内容稍微改造、规划下。 首先根据我们的需要在.pro文件内添加必要的模块&#xff0c;其中quick就是qml了&…

燃气管网安全运行监测系统功能介绍

燃气管网&#xff0c;作为城市基础设施的重要组成部分&#xff0c;其安全运行直接关系到居民的生命财产安全和城市的稳定发展。然而&#xff0c;随着城市规模的不断扩大和燃气使用量的增加&#xff0c;燃气管网的安全运行面临着越来越大的挑战。为了应对这些挑战&#xff0c;燃…

虚幻UE5智慧城市全流程开发教学

一、背景 这几年&#xff0c;智慧城市/智慧交通/智慧水利等飞速发展&#xff0c;骑士特意为大家做了一个这块的学习路线。 二、这是学习大纲 1.给虚幻UE5初学者准备的智慧城市/数字孪生蓝图开发教程 https://www.bilibili.com/video/BV1894y1u78G 2.UE5数字孪生蓝图开发教学…

蓝桥集训之斐波那契数列

蓝桥集训之斐波那契数列 核心思想&#xff1a;矩阵乘法 将原本O(n)的递推算法优化为O(log2n) 构造1x2矩阵f和2x2矩阵a 发现f(n1) f(n) * a 则f(n1) f(1) * an可以用快速幂优化 #include <iostream>#include <cstring>#include <algorithm>using na…

算法刷题应用知识补充--基础算法、数据结构篇

这里写目录标题 位运算&#xff08;均是拷贝运算&#xff0c;不会影响原数据&#xff0c;这点要注意&#xff09;&、|、^位运算特性细节知识补充对于n-1的理解异或来实现数字交换找到只出现一次的数据&#xff0c;其余数据出现偶数次 >> 、<<二进制中相邻的位的…

第12届蓝桥杯省赛 ---- C/C++ C组

文章目录 1. ASC2. 空间3. 卡片4. 相乘5. 路径6.时间显示7.最少砝码8. 杨辉三角形9. 左孩子右兄弟 第12届蓝桥杯省赛&#xff0c;C/C C组真题&#xff0c;第10题不是很清楚&#xff0c;题解不敢乱放&#x1f601;&#x1f601;&#x1f601; 1. ASC 额。。。。 #include <i…

【WEEK6】 【DAY1】DQL查询数据-第一部分【中文版】

2024.4.1 Monday 目录 4.DQL查询数据&#xff08;重点&#xff01;&#xff09;4.1.Data Query Language查询数据语言4.2.SELECT4.2.1.语法4.2.2.实践4.2.2.1.查询字段 SELECT 字段/* FROM 表查询全部的某某查询指定字段 4.2.2.2.给查询结果或者查询的这个表起别名&#xff08…

Spark-Scala语言实战(13)

在之前的文章中&#xff0c;我们学习了如何在spark中使用键值对中的keys和values,reduceByKey,groupByKey三种方法。想了解的朋友可以查看这篇文章。同时&#xff0c;希望我的文章能帮助到你&#xff0c;如果觉得我的文章写的不错&#xff0c;请留下你宝贵的点赞&#xff0c;谢…

【瑞萨RA6M3】1. 基于 vscode 搭建开发环境

基于 vscode 搭建开发环境 1. 准备2. 安装2.1. 安装瑞萨软件包2.2. 安装编译器2.3. 安装 cmake2.4. 安装 openocd2.5. 安装 ninja2.6. 安装 make 3. 生成初始代码4. 修改 cmake 脚本5. 调试准备6. 仿真 1. 准备 需要瑞萨仓库中的两个软件&#xff1a; MDK_Device_Packs.zipse…

浅谈物联网高速公路智慧配电室系统构建方案

关键词&#xff1a;高速公路&#xff1b;智慧供配电&#xff1b;电力监控&#xff1b;配电室智能运维托管&#xff1b;安全隐患 0、引言 随着高速公路事业的不断发展和路网的不断延伸&#xff0c;传统的管理方式已难以满足日益增长的需求&#xff0c;动态管理和安全隐患预警成…

ubuntu16如何使用高版本cmake

1.引言 最近在尝试ubuntu16.04下编译开源项目vsome&#xff0c;发现使用apt命令默认安装cmake的的版本太低。如下 最终得知&#xff0c;ubuntu16默认安装确实只能到3.5.1。解决办法只能是源码安装更高版本。 2.源码下载3.20 //定位到opt目录 cd /opt 下载 wget https://cmak…

ADB 命令之 模拟按键/输入

ADB 命令之 模拟按键/输入 模拟按键/输入 在 ​​adb shell​​​ 里有个很实用的命令叫 ​​input​​&#xff0c;通过它可以做一些有趣的事情。 ​​input​​ 命令的完整 help 信息如下&#xff1a; Usage: input [<source>] <command> [<arg>...] Th…

leetcode.面试题 02.07. 链表相交

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

javaweb学习(day11-监听器Listener过滤器Filter)

一、监听器Listener 1 Listener介绍 Listener 监听器它是 JavaWeb 的三大组件之一。JavaWeb 的三大组件分别是&#xff1a;Servlet 程 序、Listener 监听器、Filter 过滤器 Listener 是 JavaEE 的规范&#xff0c;就是接口 监听器的作用是&#xff0c;监听某种变化(一般就是对…

XRDP登录ubuntu桌面闪退问题

修改 /etc/xrdp/startwm.sh unset DBUS_SESSION_BUS_ADDRESS unset XDG_RUNTIME_DIR . $HOME/.profile

​如何使用ArcGIS Pro进行洪水淹没分析

洪水淹没分析是一种常见的水文地理信息系统应用&#xff0c;用于模拟和预测洪水事件中可能受到淹没影响的地区&#xff0c;这里为大家介绍一下ArcGIS Pro进行洪水淹没分析的方法&#xff0c;希望能对你有所帮助。 数据来源 教程所使用的数据是从水经微图中下载的DEM数据&…

数据结构(六)——图的应用

6.4 图的应用 6.4.1 最小生成树 对于⼀个带权连通⽆向图G (V, E)&#xff0c;⽣成树不同&#xff0c;每棵树的权&#xff08;即树中所有边上的权值之和&#xff09;也可能不同。设R为G的所有⽣成树的集合&#xff0c;若T为R中边的权值之和最小的生成树&#xff0c;则T称为G的…

怎样把学浪上的视频保存到电脑

怎样把学浪上的视频保存到电脑,这里给大家一个工具,专门用来下载学浪上的视频 这个工具叫做小浪助手.exe 我已经打包好了,有需要的自己取一下 链接&#xff1a;https://pan.baidu.com/s/1y7vcqILToULrYApxfEzj_Q?pwdkqvj 提取码&#xff1a;kqvj --来自百度网盘超级会员V…