Redis数据类型及底层大剖析(redis5种数据类型对应底层结构)
mhr18 2024-10-31 13:24 19 浏览 0 评论
Redis有丰富的数据类型,但最基本也是最常见的有下面五种:String、List、Set、Hset、Zset
数据类型整体来说会比较杂且枯燥一点,但是其在Redis中作用非常之大,为什么Redis能那么快呢,一方面来说因为其是内存型数据库,另外一方面也是因为其提供丰富的数据结构类型来加速性能。所以可以说这是学习Redis的第一关。
一、整体结构的了解
首先,我们先来对数据类型有个整体轮廓的了解。Redis是一种key-value的存储结构,在Redis中,key和value都被抽象为了对象,key只能为string对象,而value则有多个,包括上文提到的五种基础数据类型以及Redis后续追加的四种数据类型。
在Redis这种对象的结构体如下:
- type:表示哪种对象
- encoding:表示底层的编码
- lru:记录了对象的访问信息。用于内存淘汰的,内存淘汰有8种策略,一种是lru,以及后面推出的lfu等等
- refcount:引用计数,描述多少个指针指向该对象
- ptr:内容指针,指向实际的内容
二、五种基本数据类型
String
String对应的value可以是字符串,也可以是整形或者浮点型的数字,甚至是图片、视频、音频等二进制数据。value能容纳最大的数据长度为512M。 而同时我们也要去了解这些基本类型的底层实现以及编码方式。
String类型底层实现:int和sds(simple dynamic string)
String类型编码方式:int、embstr和raw
联系为:
int很好理解,因为上文说了String可以存数字,所以存在这种编码方式与实现。
sds的引入,主要是c语言的字符串过于简单导致有些方面存在缺陷,而Redis又是C语言实现的。所以Redis又在此基础之上封装出了一个结构sds,正如那句计算机名言:没有什么问题不可以通过添加一层抽象来解决。
具体来说,C中的字符串
- 每次计算字符串长度的复杂度需要O(n)
- 同时对字符串进行追加,需要重新分配内存
- 非二进制安全
在Redis内部,字符串的追加以及返回长度是很常见的,而追求高性能的Redis肯定是不容许这些事情发生的,所以进行了结构的封装。sds又分为sds8,sds16,sds32,sds64,字段属性没有区别。结构如下:
首先,记录了len字段,可以O(1)返回字符串的长度,同时alloc表示分配的内存,alloc-len就是预留空间,预留的空间为min(len,1M)。flags代表sds的分类,上面说了sds分为了4种。同时不再以‘\0’作为结束标识,但内联数组最后会是'\0'结尾,二进制安全,拼接字符串前也会检查空间是否满足要求,不会导致缓冲区溢出的问题。
而对于三种编码方式,当存储的字符串是整数值,且大小在long范围,则采用int编码方式实现int数据结构,而当字符串长度比较小,采用embstr编码,当超过一定的长度embstr会变为raw编码。这个阈值在3.2版本之前是39字节,3.2版本版本之后是44字节。对于embstr,它的RedisObject和sds是连续的,而raw下面这两个在内存是分开的。同时需要注意的是embstr被设置为了只读,任何写操作embstr都会变为raw,理念就是修改过的字符串通常认为是易变的,计算机的局部性原理。而对于embstr来说,它的RedisObject是连续的,耦合性更强,当需要重新分配embstr的时候,这两个部分需要重新分配,而对于raw来说,它的redisObject不需要重新分配。
关于阈值的数值,有兴趣的可以继续往下看: 首先,Redis使用了jemalloc作为内存分配器,jemalloc以64字节为阈值区分大小字符串,所以embstr的阈值其实是受64字节的影响。超过64字节就超过一个内存单元,会用raw编码,反之认为是一个小字符串,用embstr编码。接着分析:我们也知道这两种编码方式由RedisObject和sds两部分组成,分别分析即可。
- 对于RedisObject,其大小没有变过,大小为4+4+24+32+64=128bits=16B
- 而对于sds,内存大小为1B+1B+1B+内联数组大小,同时内联大小最后有一个'\0'占一个字节。所以还剩下能用的大小就为:64-16-3-1=44
而对于之前为什么是39呢?少了5个字节,首先,下图是之前的sds结构图:
当时是通用的sds结构,没有细分为8,16,32,64。3.2之后的版本的embstr用的是sds8,总容量len少了3个字节,容量也减少了3个字节,总共减少了6个字节,但是多了个flag字段占用1个字节,所以相比于没有拆分sds结构来说,embstr的阈值会多5个字节。
list
List 列表是简单的字符串列表,按照插入顺序排序,可以从头部或尾部向 List 列表添加元素。列表能存的元素就非常之多了,每个列表支持超过40亿个元素。
同样的看看其底层实现与编码方式:
其底层实现是通过压缩列表或双向链表实现的, 当为小列表的时候采用压缩列表,判断小的判断有两个维度,一个是列表数量小(512个),一个是列表元素占用字节少(64字节)。当不满足ziplist编码的条件会转为linkedlist编码(linkedlist底层是通过双向链表进行实现的),Redis作为一种内存型数据库,很多时候我感觉是在有限的内存以及性能的追求寻找一个平衡,提供多种数据结构。
Redis在3.2版本引进了一种新的数据结构quicklist,为什么呢,ziplist是为了在数据较少时节约内存,当数据稍多时插入数据会导致很多内存复制,而当linkedlist链表节点很多的话,又会占用很多内存,并不能很好的解决问题。quicklist是这两者的结合体。单个quicklist节点存的是ziplist,即多个数据,如下图:
当数据较少的时候,quicklist的节点只有一个,此时相当于就是一个ziplist
当数据很多的时候,则同时利用了ziplist和linkedlist的优势。
ziplist本身存在一个连锁更新的问题,在Redis7.0之后,使用listpack的编码方式取代了ziplist,但本质都是一种压缩的列表,可以统称为压缩列表,主要是用来解决连锁更新的问题。
连锁更新的问题大家可以去了解一下ziplist中entry它的结构,这个结构里面有个prevlen属性导致了连锁更新的问题,它用来表示上一个节点的数据长度,通过它可以定位到上一个数据,也因此压缩列表才可以从后向前遍历,如果前一个节点的长度小于254字节(255是特殊字符,被zlend占用,表示ziplist的结束),这个属性占用一个字节,否则使用5个字节去保存长度值。连锁更新指的是后移操作不止发生了一次,而是发生了多次。
比如说当插入一个节点,导致后面的数据进行了一次后移操作,同时,因为它的插入,原本后面结点的prevlen属性本来只占用一个属性,而现在变成了五个字节,结点膨胀之后又要逐步迭代更新。实际业务中,很少碰到需要迭代更新超过2个结点的情况,但这种情况的存在会使性能不稳定。当然也是从结点的结构定义下手,有一个element-tot-len属性表示存储整个结点除它自身之外的长度。
规则如下:每个字节第一个bit标识是否结束,0是结束,省下7个bit存储数据大小。那么,我们如何从当前结点找到上一个结点的首位置呢,该结点首部即上个结点的element-tot-len属性(该属性在结点结构尾部),假如为(00000010 10000101)占用大小的字节为 0000010 0000101 —--十进制—> 261字节
set
Set是一个无序且不重复的字符串集合
同样的看看其底层实现与编码方式:
其底层实现是通过哈希表或整数集合实现的,出于性能与内存的综合考虑,set也支持两种编码方式,当元素都是整数且元素个数不超过512个,就用INTSET编码,否则用哈希表。哈希表能O(1)时间进行快速查询,而INTSET编码只能进行二分查询。
HSet/Hash
HSet是一个键值对集合,很适合拿来存储对象。比如任务信息对象,field可以设置为任务类型,value设置为任务配置参数。先来直观了解一下其结构,首先,它是一种key-value集合,但需要注意的是它的value形式是这样子的:value=[{field1,value1},...{fieldN,valueN}],比如说我们的购物车,永无id作为key,商品id作为field, 商品数量作为value
同样的HSet也有两种底层实现与编码方式:压缩列表和哈希表(压缩列表在较高的版本7.0已经完全是listpack了)
- 当哈希类型元素个数小于 512 个,field和value小于 64 字节,Redis 会使用压缩列表作为 Hash 类型的底层数据结构;
- 如果哈希类型元素不满足上面条件,Redis 会使用哈希表作为 Hash 类型的 底层数据结构。
ZSet/SortedSet
ZSet比Set多了一个排序属性score,同样的value有两个值组成,一个是排序值,一个是元素值。当我们使用ZADD为集合添加元素,也是排序值在前,元素值在后。
同样的ZSet也有两种底层实现与编码方式:压缩列表或跳表+哈希表
- 如果有序集合的元素个数小于 128 个,并且每个元素值小于 64 字节时,Redis 会使用压缩列表作为 Zset 类型的底层数据结构;(是不包括分数值占用的字节的,score入参的时候还是double,还不知道转换成字符串会占用多少字节)
- 如果有序集合的元素不满足上面的条件,Redis 会使用跳表+哈希表作为 ZSet 类型的底层数据结构;
三、关系总结图
下面是针对Redis对外提供的五种基本类型与底层实现的数据结构的关系,图来源于网络。最后ZSet对应的跳表最好再加个哈希表。使得可以O(1)时间复杂度查询到成员的分数值。
作者:本生来滑稽
链接:https://juejin.cn/post/7228551764835156023
来源:稀土掘金
相关推荐
- Java培训机构,你选对了吗?(java培训机构官网)
-
如今IT行业发展迅速,不仅是大学生,甚至有些在职的员工都想学习java开发,需求量的扩大,薪资必定增长,这也是更多人选择java开发的主要原因。不过对于没有基础的学员来说,java技术不是一两天就能...
- 产品经理MacBook软件清单-20个实用软件
-
三年前开始使用MacBookPro,从此再也不想用Windows电脑了,作为生产工具,MacBook可以说是非常胜任。作为产品经理,值得拥有一台MacBook。MacBook是工作平台,要发挥更大作...
- RAD Studio(Delphi) 本月隆重推出新的版本12.3
-
#在头条记录我的2025#自2024年9月,推出Delphi12.2版本后,本月隆重推出新的版本12.3,RADStudio12.3,包含了Delphi12.3和C++builder12.3最...
- 图解Java垃圾回收机制,写得非常好
-
什么是自动垃圾回收?自动垃圾回收是一种在堆内存中找出哪些对象在被使用,还有哪些对象没被使用,并且将后者删掉的机制。所谓使用中的对象(已引用对象),指的是程序中有指针指向的对象;而未使用中的对象(未引用...
- Centos7 初始化硬盘分区、挂载(针对2T以上)添加磁盘到卷
-
1、通过命令fdisk-l查看硬盘信息:#fdisk-l,发现硬盘为/dev/sdb大小4T。2、如果此硬盘以前有过分区,则先对磁盘格式化。命令:mkfs.文件系统格式-f/dev/sdb...
- 半虚拟化如何提高服务器性能(虚拟化 半虚拟化)
-
半虚拟化是一种重新编译客户机操作系统(OS)将其安装在虚拟机(VM)上的一种虚拟化类型,并在主机操作系统(OS)运行的管理程序上运行。与传统的完全虚拟化相比,半虚拟化可以减少开销,并提高系统性能。虚...
- HashMap底层实现原理以及线程安全实现
-
HashMap底层实现原理数据结构:HashMap的底层实现原理主要依赖于数组+链表+红黑树的结构。1、数组:HashMap最底层是一个数组,称为table,它存放着键值对。2、链...
- long和double类型操作的非原子性探究
-
前言“深入java虚拟机”中提到,int等不大于32位的基本类型的操作都是原子操作,但是某些jvm对long和double类型的操作并不是原子操作,这样就会造成错误数据的出现。其实这里的某些jvm是指...
- 数据库DELETE 语句,还保存原有的磁盘空间
-
MySQL和Oracle的DELETE语句与数据存储MySQL的DELETE操作当你在MySQL中执行DELETE语句时:逻辑删除:数据从表中标记为删除,不再可见于查询结果物理...
- 线程池—ThreadPoolExecutor详解(线程池实战)
-
一、ThreadPoolExecutor简介在juc-executors框架概述的章节中,我们已经简要介绍过ThreadPoolExecutor了,通过Executors工厂,用户可以创建自己需要的执...
- navicat如何使用orcale(详细步骤)
-
前言:看过我昨天文章的同鞋都知道最近接手另一个国企项目,数据库用的是orcale。实话实说,也有快三年没用过orcale数据库了。这期间问题不断,因为orcale日渐消沉,网上资料也是真真假假,难辨虚...
- 你的程序是不是慢吞吞?GraalVM来帮你飞起来性能提升秘籍大公开
-
各位IT圈内外的朋友们,大家好!我是你们的老朋友,头条上的IT技术博主。不知道你们有没有这样的经历:打开一个软件,半天没反应;点开一个网站,图片刷不出来;或者玩个游戏,卡顿得想砸电脑?是不是特别上火?...
- 大数据正当时,理解这几个术语很重要
-
目前,大数据的流行程度远超于我们的想象,无论是在云计算、物联网还是在人工智能领域都离不开大数据的支撑。那么大数据领域里有哪些基本概念或技术术语呢?今天我们就来聊聊那些避不开的大数据技术术语,梳理并...
- 秒懂列式数据库和行式数据库(列式数据库的特点)
-
行式数据库(Row-Based)数据按行存储,常见的行式数据库有Mysql,DB2,Oracle,Sql-server等;列数据库(Column-Based)数据存储方式按列存储,常见的列数据库有Hb...
- AMD发布ROCm 6.4更新:带来了多项底层改进,但仍不支持RDNA 4
-
AMD宣布,对ROCm软件栈进行了更新,推出了新的迭代版本ROCm6.4。这一新版本里,AMD带来了多项底层改进,包括更新改进了ROCm的用户空间库和AMDKFD内核驱动程序之间的兼容性,使其更容易...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracle位图索引 (74)
- oracle批量插入数据 (65)
- oracle事务隔离级别 (59)
- oracle 空为0 (51)
- oracle主从同步 (56)
- oracle 乐观锁 (53)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)