redis核心数据结构源码分析

dictEntry和redisObject

在 Redis 的实现中,当一个键值对被创建并存储时,键通常是一个字符串,而值则是一个 redisObject。因此,在 dictEntry 结构中,key 成员指向的是一个字符串,而 v.val 成员则指向一个 redisObject。这意味着,当你在 Redis 中存储一个值时,你实际上是在字典中插入一个 dictEntry,其中 dictEntry 的值部分指向一个包含实际数据和元数据的 redisObject

dictEntry

struct dictEntry {void *key;union {void *val;uint64_t u64;int64_t s64;double d;} v;struct dictEntry *next;     /* Next entry in the same hash bucket. */
};
/*位置:dict.c*/

一个dictEntry就是哈希表中的一个结点。

1.void *val:这是指向键的指针。在 Redis 中,键通常是字符串,但这里使用void * 是为了增加灵活性,允许键的类型在底层实现中变化。

2.union { ... } v;

这是一个联合体(union),就是哈希表结点中的value。

void *val;:一个通用指针,通常用于指向更复杂的数据结构,如链表、压缩列表或字符串。

uint64_t u64;:一个无符号 64 位整数,用于存储较小的数值类型。

int64_t s64;:一个带符号 64 位整数,用于存储较小的数值类型。

double d;:一个双精度浮点数,用于存储实数。

在任何给定时刻,联合体中只有一个成员会被使用,具体取决于值的实际类型。这种设计节省了内存,因为不需要为每种类型分配独立的空间。

3.struct dictEntry *next;  

链地址法解决哈希冲突

redisObject

struct redisObject {unsigned type:4;unsigned encoding:4;unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or* LFU data (least significant 8 bits frequency* and most significant 16 bits access time). */int refcount;void *ptr;
};
/*位置:server.h*/

1.unsigned type:4;

4位的type表示具体的数据类型。包括OBJ_STRING,OBJ_LIST,OBJ_HASH,OBJ_SET,OBJ_ZSET

可通过type命令查看

2.unsigned encoding:4;

4位的encoding表示该类型的物理编码方式(如下),同一种数据类型可能有不同的编码方式。(比如String就提供了3种:int embstr raw)

#define OBJ_ENCODING_RAW 0     /* Raw representation */
#define OBJ_ENCODING_INT 1     /* Encoded as integer */
#define OBJ_ENCODING_HT 2      /* Encoded as hash table */
#define OBJ_ENCODING_ZIPMAP 3  /* No longer used: old hash encoding. */
#define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */
#define OBJ_ENCODING_ZIPLIST 5 /* No longer used: old list/hash/zset encoding. */
#define OBJ_ENCODING_INTSET 6  /* Encoded as intset */
#define OBJ_ENCODING_SKIPLIST 7  /* Encoded as skiplist */
#define OBJ_ENCODING_EMBSTR 8  /* Embedded sds string encoding */
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of listpacks */
#define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */
#define OBJ_ENCODING_LISTPACK 11 /* Encoded as a listpack */
#define OBJ_ENCODING_LISTPACK_EX 12 /* Encoded as listpack, extended with metadata */
/*位置:server.h*/

可通过object encoding命令查看 

3.unsigned lru:LRU_BITS;

对象最后一次被访问的时间戳,与内存回收有关。

4.int refcount

refcount表示对象的引用计数(可类比JVM中的引用计数法)

5.void *ptr

ptr指针指向实际对象

五大基本数据类型和底层数据类型对应关系

redis6

redis7

五大基本数据类型源码分析

 String

三大物理编码方式

1.int

保存long型的64位(8个字节)有符号整数

只有整数才会使用int,如果是浮点数,Redis内部其实先将浮点数转化为字符串值,然后再保存。

2.embstr

embedded string,表示嵌入式的String。代表embstr格式的SDS(simple dynamic string,简单动态字符串),保存长度小于44字节的字符串。

嵌入式怎么理解?

robj *createEmbeddedStringObject(const char *ptr, size_t len) {robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr8)+len+1);struct sdshdr8 *sh = (void*)(o+1);o->type = OBJ_STRING;o->encoding = OBJ_ENCODING_EMBSTR;o->ptr = sh+1;//sh 是 sdshdr8 结构体的指针,它紧跟在 redisObject 结构体之后在内存中分配。o->refcount = 1;o->lru = 0;sh->len = len;sh->alloc = len;sh->flags = SDS_TYPE_8;if (ptr == SDS_NOINIT)sh->buf[len] = '\0';else if (ptr) {memcpy(sh->buf,ptr,len);sh->buf[len] = '\0';} else {memset(sh->buf,0,len+1);}return o;
}
/*位置:object.c*/

字符串 sds结构体与其对应的 redisObject 对象分配在同一块连续的内存空间,字符串sds嵌入在redisObject对象之中一样。 

3.raw

保存长度大于44字节的字符串

源码证明:

#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
robj *createStringObject(const char *ptr, size_t len) {if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT) //长度小于44return createEmbeddedStringObject(ptr,len);//emb编码elsereturn createRawStringObject(ptr,len);//raw编码
}
/*位置:object.c*/

SDS 

typedef char *sds;/* Note: sdshdr5 is never used, we just access the flags byte directly.* However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {unsigned char flags; /* 3 lsb of type, and 5 msb of string length */char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {uint8_t len; /* used */uint8_t alloc; /* excluding the header and null terminator */unsigned char flags; /* 3 lsb of type, 5 unused bits */char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {uint16_t len; /* used */uint16_t alloc; /* excluding the header and null terminator */unsigned char flags; /* 3 lsb of type, 5 unused bits */char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {uint32_t len; /* used */uint32_t alloc; /* excluding the header and null terminator */unsigned char flags; /* 3 lsb of type, 5 unused bits */char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {uint64_t len; /* used */uint64_t alloc; /* excluding the header and null terminator */unsigned char flags; /* 3 lsb of type, 5 unused bits */char buf[];
};
/*位置:sds.h*/
  •  len:字符串的实际长度(不包括末尾的空字符),对sdshdr8的uint8_t 来说,可存储0-255的长度。
  • alloc:为字符串分配的总空间大小(同样不包括结构体头部和空终止符)。
  • flags:标志位。低 3 位被用于表示 SDS 的类型,而剩下的 5 位目前是未使用的。
  • buf:实际存储字符串的字符数组,SDS本质上也是一个字符数组。

为什么要重新设计一个简单动态字符串(SDS),而不直接用c语言的char[]?

  • 字符串长度处理:c的cha[]要遍历得到长度,时间复杂度为O(n),SDS与len,直接读取即可,时间复杂度为O(1)。
  • 内存分配:预先分配更多的空间,减少频繁的内存重新分配;且SDS缩短时并不会回收多余的空间,而是用free将多余的空间记录下来,如果后序需要再分配,直接使用free标记的字段,而不用重新申请。
  • 因为c的char[]将\0作为字符串结束的标志,如果字符串内容中含有\0,会错把该字符当做结束标志。而SDS根据len判断字符串结束,不会有该问题。

Hash

redis6

redis中Hash结构由zipList和Hashtable构成。当hashkey满足1.哈希对象保存的键值对数量小于512个; 2.所有的键值对的健和值的字符串长度都小于等于64byte(一个英文字母一个字节)时用ziplist,反之用hashtable。ziplist升级到hashtable可以,反过来降级不可以。

ziplist:

ziplist 是一个经过特殊编码的双向链表,它的设计目标是节约内存。它可以存储字符串或者整数。其中整数是按二进制进行编码的,而不是字符串序列。它能以 O(1) 的时间复杂度在列表的两端进行 push 和 pop 操作。但是由于每个操作都需要对 ziplist 所使用的内存进行重新分配,所以实际操作的复杂度与 ziplist 占用内存大小有关。(官方注释)

因为 ziplist 的设计目标是为了 节约内存,而链表的各项之间需要使用指针连接起来,这种方式会带来大量的内存碎片,而且地址指针也会占用额外的内存,(压缩的地方)这与 ziplist 的设计初衷不符。而且后面我们看了 ziplist 的数据结构就会发现,ziplist 实际上是一块连续的内存。

因此我们可以这么理解:ziplist 是一个特殊的双向链表,特殊之处在于:没有维护双向指针,prev、next,而是存储了上一个 entry 的长度和当前 entry 的长度,通过长度推算下一个元素。

也就是:

  • 压缩列表本质上就是一个字节数组
  • 是 Redis 为了节约内存而设计的一种线性结构
  • 可以包含多个元素,每个元素可以是一个字节数组或一个整数

zipList结构:

 

每个entry结构:

 previous_entry_length 字段表示前一个元素的字节长度

encoding 字段表示当前元素的编码,记录了节点的 content 字段所保存数据的类型以及长度

content 字段存储节点的值,节点值可以是一个字节数组或者整数,值的类型和长度由节点的 encoding 属性决定。

ziplist的连锁更新问题:

previous_entry_length 属性都记录了前一个节点的长度:

  • 如果前一节点的长度小于 254 字节,那么 previous_entry_length 属性需要用 1 字节长的空间来保存这个长度值。
  • 如果前一节点的长度大于等于 254 字节,那么 previous_entry_length 属性需要用 5 字节长的空间来保存这个长度值。

现在假设一个压缩列表中有多个连续的、长度在 250~253 之间的节点,如下图:

因为这些节点长度值小于 254 字节,所以 prevlen 属性需要用 1 字节的空间来保存这个长度值,一切OK,O(∩_∩)O哈哈~

这时,如果将一个长度大于等于 254 字节的新节点加入到压缩列表的表头节点,即新节点将成为entry1的前置节点,如下图:

因为entry1节点的prevlen属性只有1个字节大小,无法保存新节点的长度,此时就需要对压缩列表的空间重分配操作并将entry1节点的prevlen 属性从原来的 1 字节大小扩展为 5 字节大小。

连续更新问题出现

entry1节点原本的长度在250~253之间,因为刚才的扩展空间,此时entry1节点的长度就大于等于254,因此原本entry2节点保存entry1节点的 prevlen属性也必须从1字节扩展至5字节大小。entry1节点影响entry2节点,entry2节点影响entry3节点......一直持续到结尾。这种在特殊情况下产生的连续多次空间扩展操作就叫做「连锁更新」 

为了解决连锁更新,redis7中出现了listpack(紧凑列表)

redis7

listpack(1.哈希对家保存的键值对数量小于512个;2.所有的键值对的健和值的字符串长度都小于等于64byte(一个英文字母一个字节)时用listpack,反之用nashtable listpack升级到hashtable可以,反过来降级不可以)

和ziplist列表项类似,listpack列表项也包含了元数据信息和数据本身。不过,为了避免ziplist引起的连锁更新问题,listpack中的每个列表项不再像ziplist列表项那样保存其前一个列表项的长度。

List

redis6

list用quicklist来存储,quicklist存储了一个双向链表,每个节点都是一个ziplist。

quicklist源码:

quicklistNode:

  

redis7

 Iist用quicklist来存储,quicklist存储了一个双向链表,每个节点都是一个listpack

Set

intset或hashtable

集合元素都是long类型并且元素个数<=set-max-intset-entries,编码就是intset,反之就是hashtable

zset

redis6是ziplist+skiplist。redis7是listpack+skiplist

当ZSet的元素数量比较少时,Redis会采用ZipList(ListPack)来存储ZSet的数据。ZipList(ListPack)是一种紧凑的列表结构,它通过连续存储元素来节约内存空间。当ZSet的元素数量增多时,Redis会自动将ZipList(ListPack)转换为SkipList,以保持元素的有序性和支持范围查询操作。

skiplist

时间复杂度:O(n)。空间复杂度:O(n) 

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

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

相关文章

45.5【C语言】typedef

目录&#xff1a; *全称 *格式 一般指针 数组指针 函数指针 *细节 *全称 type define 类型&#xff08;重新&#xff09;定义&#xff08;或命名&#xff09;&#xff0c;可简化输入 *格式 1.非指针类型: typedef 类型 简化名称 typedef signed long long k; signed long …

搭建自己的金融数据源和量化分析平台(七):定时更新上市公司所属行业门类及大类

0x00 前言 由于此前从深交所下载的股票信息中只有行业门类信息&#xff0c;没有行业大类信息&#xff0c;导致后续解析三大报表和量化选股的时候无法进行&#xff1a; 可以看到深交所的股票是没有大类信息的。 再看看上交所的保险股&#xff1a; 因此需要将深交所股票的所属…

WIFI驱动开发

Linux 4.9 内核驱动移植 Linux 4.9 BSP 内核驱动 下载驱动后获得驱动的 tar.gz 压缩包 解压后找到如下驱动与文件夹 进入内核&#xff0c;找到 linux-4.9/drivers/net/wireless 文件夹中&#xff0c;新建文件夹aic8800 并且把上面的驱动与文件夹放入刚刚创建好的 aic8800 中。…

【MySQL】 黑马 MySQL进阶 笔记

文章目录 存储引擎MySQL的体系结构存储引擎概念存储引擎特点InnoDBMyISAMMemory 存储引擎选择 索引概述结构B Tree(多路平衡查找树)B TreeHash为什么InnoDB存储引擎选择使用Btree索引结构? 分类思考题 语法SQL性能分析&#xff08;索引相关&#xff09;SQL执行频率慢查询日志p…

SSRF和CSRF实战复现

文章目录 SSRFWeb-Hacking-Lab-master1、Centos未授权访问2、Ubuntu未授权访问3、Ubuntu传入公钥访问4、ssrf_redis_lab_pickle_redis_lab CSRF:windphp SSRF SSRF(Server-Side Request Forgery:服务器端请求伪造) 是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。 f…

Unity 波函数坍缩算法随机地图生成

Unity 波函数坍缩算法随机地图生成 波函数波函数基本概念位置空间波函数动量空间波函数两种波函数之间的关系波函数的本征值和本征态波函数坍缩 熵是什么熵作为状态函数时间之箭 实现原理举个例子&#xff1a;2D迷宫地图生成 Unity 如何实现前期准备单元格代码瓦片地图代码波函…

通过建模走出人工智能寒冬

很多人对 GenAI 是否会产生商业影响持怀疑态度&#xff0c;但我认为他们不仅错了&#xff0c;而且犯了 2001 年人们在互联网上犯下的错误。他们认为硅谷的炒作是无稽之谈&#xff0c;因此其背后的想法也是无稽之谈。 这是很危险的&#xff0c;我认为&#xff0c;这比大多数零售…

nacos 使用 docker 单机部署连接 MySQL 数据库并开启鉴权

文章目录 本地部署的配置启用鉴权(未验证) docker部署的配置修改docker 镜像源启用鉴权&#xff0c;必须添加如下环境变量如何生成鉴权的密钥 完整环境变量docker启动命令 本地部署的配置 文件结构 application.properties #配置文件 mysql-schema.sql …

WPS关闭后,进程依然在后台运行的解决办法

问题 wps启动后 在启动wps后&#xff0c;什么都不做&#xff0c;打开进程管理器&#xff0c;发现居然运行了3个wps进程&#xff1a; win10只会显示wps进程&#xff1a; win11显示比较准确&#xff1a; 关闭后 在关闭wps&#xff0c;再去任务管理器查看&#xff0c;发现在…

Python计算机视觉 第3章-图像到图像的映射

Python计算机视觉 第3章-图像到图像的映射 3.1 单应性变换 单应性变换&#xff08;Homography&#xff09;是计算机视觉中非常重要的一种几何变换&#xff0c;它用于将一个平面内的点映射到另一个平面内。具体来说&#xff0c;单应性变换可以描述一个图像在摄像机视角变化、…

vue3+vite+axios+mock从接口获取模拟数据实战

文章目录 一、安装相关组件二、在vite.config.js中配置vite-plugin-mock插件三、实现mock服务四、调用api接口请求mock数据方法一、直接使用axios 请求mock 数据方法二、对axios进行封装统一请求mock数据 五、实际运行效果 在用Vue.js开发前端应用时通常要与后端服务进行交互&a…

白酒与家庭聚会:温馨与和谐的时光

在繁忙的都市生活中&#xff0c;家庭聚会是每个人心中那份较温暖的港湾。每当夜幕降临&#xff0c;灯火通明&#xff0c;家人们围坐在一起&#xff0c;谈笑风生&#xff0c;那份温馨与和谐仿佛能够驱散一切疲惫。而在这个温馨的时刻&#xff0c;白酒——豪迈白酒&#xff08;HO…

Python 爬虫入门(十二):正则表达式「详细介绍」

Python 爬虫入门&#xff08;十二&#xff09;&#xff1a;正则表达式 前言一、正则表达式的用途二、正则表达式的基本组成元素2.1 特殊字符2.2 量词2.3 位置锚点2.4 断言2.5 字符集2.6 字符类2.6.1 基本字符类2.6.2 常见字符类简写2.6.3 POSIX字符类2.6.4 组合使用 三、 正则表…

如何使用ssm实现亿互游在线平台设计与开发+vue

TOC ssm118亿互游在线平台设计与开发vue 绪论 1.1研究背景 时代的发展&#xff0c;我们迎来了数字化信息时代&#xff0c;它正在渐渐的改变着人们的工作、学习以及娱乐方式。计算机网络&#xff0c;Internet扮演着越来越重要的角色&#xff0c;人们已经离不开网络了&#x…

2024世界机器人大会盛大开幕,卓翼飞思携无人智能领域产品集中亮相 !

开放创新 聚享未来&#xff01;万众瞩目的2024世界机器人大会暨博览会于8月21日在北京亦创国际会展中心盛大开幕。大会聚焦机器人技术与产业前沿趋势&#xff0c;展示机器人创新应用赋能千行百业的多元场景&#xff0c;全球顶尖的机器人科学家、行业领袖、创新精英汇聚一堂&…

使用HAL库实现按键控制LED和蜂鸣器

下载STM32CubeMX实现项目的初始配置&#xff08;寄存器操作)&#xff0c;下载keil对程序进行编译烧写 在STM32CubeMX中将PB0/PB1设置为输入引脚作为按键&#xff0c;PA6/PA4设置为输出引脚作为led和Beep&#xff0c;将按键引脚设置为上拉输入&#xff1a; 创建项目完成后在kei…

C语言 | Leetcode C语言题解之第365题水壶问题

题目&#xff1a; 题解&#xff1a; bool canMeasureWater(int jug1Capacity, int jug2Capacity, int targetCapacity) {int j1 jug1Capacity < jug2Capacity ? jug1Capacity : jug2Capacity, j2 jug1Capacity > jug2Capacity ? jug1Capacity : jug2Capacity;if (ta…

Umi-OCR 文字识别工具

免费开源的离线orc识别功能 git地址 感谢大佬的贡献 Umi-OCR 文字识别工具 使用说明 • 下载地址 • 更新日志 • 提交Bug 免费&#xff0c;开源&#xff0c;可批量的离线OCR软件 适用于 Windows7 x64 、Linux x64 免费&#xff1a;本项目所有代码开源&#x…

Verilog刷题笔记59

题目: Exams/m2014 q6c 解题&#xff1a; module top_module (input [6:1] y,input w,output Y2,output Y4);assign Y2y[1]&w0;assign Y4(y[2]&w1)|(y[3]&w1)|(y[5]&w1)|(y[6]&w1);endmodule结果正确: 注意点: 起初&#xff0c;我的代码有错误,代码如下…

9 正则表达式:Java爬虫和正则表达式、String中的正则表达式方法(基本语法7)

文章目录 前言一、正则表达式1 [ ] 语法(1)[ABC] 和 [^ABC](2)[A-Z]和[a-zA-Z]小总结2 特殊字符语法(\w 这些)3 数量符4 \ 、()、 |5 锚点 ^ 和 $,\b,\B6 (?i) : 忽略其后面的大小写 ---- 这个Java是可以的,其他语言我不知道(正则表达式虽然大多通用,但也有部分是…