Redis源码学习:Redis对象和5种数据类型的工作原理
Redis 提供 5 种基本数据类型:String(字符串)、List(列表)、Set(集合)、Hash(哈希)、Zset(有序集合),这些数据类型可以供用户直接使用。
RedisObject
RedisObject
是 Redis 中的一个数据结构,表示 Redis 中的所有数据类型(字符串、列表、集合、有序集合和哈希)的统一抽象。定义如下:
typedef struct redisObject {// 表示对象的类型,比如字符串、列表、集合等unsigned type:4;// 表示对象的编码方式,不同的编码方式在内存中的存储形式不同unsigned encoding:4;// 用于 LRU 或 LFU 过期策略unsigned lru:LRU_BITS;// 引用计数,用于内存管理int refcount;// 指向实际数据的指针void *ptr;
} robj;
5种数据类型及实现
String
字符串是最常用的 Redis 数据类型,可以存储普通文本或二进制数据。字符串的实现主要有三种编码方式:
RAW
:使用普通的动态字符串(SDS,Simple Dynamic String),存储上限时 512 mb(但是,你不能这么干)。EMBSTR
:优化的 SDS 存储方式,用于小于等于 44 字节的字符串,此时 object head 与 SDS 是一段连续的空间。申请内存时只需要一次内存分片,效率更高。INT
:用于表示可以用整数存储的字符串,且大小在 LONG_MAX 范围内,直接将数据保存在 RedisObject 的 ptr 指针位置(8字节),不再需要 SDS 了。
List
列表是一种有序的字符串链表,现在只使用 quicklist 实现 List。
在 object.c
文件中可以找到创建列表对象的代码:
robj *createQuicklistObject(void) {quicklist *l = quicklistCreate();robj *o = createObject(OBJ_LIST,l);o->encoding = OBJ_ENCODING_QUICKLIST;return o;
}
在插入元素时,使用的也是 quicklist:
void listTypePush(robj *subject, robj *value, int where) {if (subject->encoding == OBJ_ENCODING_QUICKLIST) {int pos = (where == LIST_HEAD) ? QUICKLIST_HEAD : QUICKLIST_TAIL;if (value->encoding == OBJ_ENCODING_INT) {char buf[32];ll2string(buf, 32, (long)value->ptr);quicklistPush(subject->ptr, buf, strlen(buf), pos);} else {quicklistPush(subject->ptr, value->ptr, sdslen(value->ptr), pos);}} else {serverPanic("Unknown list encoding");}
}
Set
集合是一组无序的字符串集合,集合中的元素是唯一的,查询效率高。集合的实现有两种主要的编码方式:
INTSET
:整数集合,用于存储数据都是整数,数量不超过 set-max-intset-entriesHT
:哈希表,用于存储字符串的大集合,dic 中的 key 存储元素,value 都是 null。
创建集合
在创建集合对象时,通过对 value 的类型判断,来选择不同的编码:
robj *setTypeCreate(sds value) {// 判断 value 是否是数值类型Long Long,如果是数值类型,采用 IntSet 编码// 否则,采用 HT 编码if (isSdsRepresentableAsLongLong(value,NULL) == C_OK)return createIntsetObject();return createSetObject();
}
选择编码方式的逻辑
int setTypeAdd(robj *subject, sds value) {long long llval;if (subject->encoding == OBJ_ENCODING_HT) {// 已经是 HT 编码,直接添加元素dict *ht = subject->ptr;dictEntry *de = dictAddRaw(ht,value,NULL);if (de) {dictSetKey(ht,de,sdsdup(value));dictSetVal(ht,de,NULL);return 1;}} else if (subject->encoding == OBJ_ENCODING_INTSET) {if (isSdsRepresentableAsLongLong(value,&llval) == C_OK) {// 目前是 IntSet// 判断 value 是否是整数uint8_t success = 0;// 是整数,直接添加元素到 setsubject->ptr = intsetAdd(subject->ptr,llval,&success);if (success) {// 当intset 元素数量超过 set_max_intset_entries,则转为 HT/* Convert to regular set when the intset contains* too many entries. */size_t max_entries = server.set_max_intset_entries;/* limit to 1G entries due to intset internals. */if (max_entries >= 1<<30) max_entries = 1<<30;if (intsetLen(subject->ptr) > max_entries)setTypeConvert(subject,OBJ_ENCODING_HT);return 1;}} else {// 不是整数,直接转为 HT/* Failed to get integer from object, convert to regular set. */setTypeConvert(subject,OBJ_ENCODING_HT);/* The set *was* an intset and this value is not integer* encodable, so dictAdd should always work. */serverAssert(dictAdd(subject->ptr,sdsdup(value),NULL) == DICT_OK);return 1;}} else {serverPanic("Unknown set encoding");}return 0;
}
Zset
有序集合(ZSet)的实现主要有两种编码方式:压缩列表(ziplist)和跳表(skiplist)。
定义
在 server.h
文件中,zset
结构体的定义如下:
typedef struct zset {dict *dict;zskiplist *zsl;
} zset;
创建集合
在 t_zset.c
文件中,可以找到创建有序集合对象的代码:
robj *createZsetObject(void) {zset *zs = zmalloc(sizeof(*zs));robj *o;zs->dict = dictCreate(&zsetDictType, NULL);zs->zsl = zslCreate();o = createObject(OBJ_ZSET, zs);o->encoding = OBJ_ENCODING_SKIPLIST;return o;
}
当元素数量不多时,HT 和 Skiplist 的优势不明显,而且更耗内存。因此,在满足以下条件的情况下,Zset 会采用 Ziplist 来节省内存。
- 元素数量小于 zset-max-ziplist-entries,默认值 128
- 每个元素都小于 zset-max-ziplist-value 字节,默认值 64
// zadd 添加元素时,先根据 key 找到 zset,不存在则创建新的 zset
zobj = lookupKeyWrite(c->db,key);
if (checkType(c,zobj,OBJ_ZSET)) goto cleanup;
if (zobj == NULL) {if (xx) goto reply_to_client; /* No key + XX option: nothing to do. */// zset_max_ziplist_entries 设置为0就是禁用了ziplist// 或者 value 大小超过了 zset_max_ziplist_value,采用 HT + Skiplistif (server.zset_max_ziplist_entries == 0 ||server.zset_max_ziplist_value < sdslen(c->argv[scoreidx+1]->ptr)){zobj = createZsetObject();} else {// 否则,采用 ziplistzobj = createZsetZiplistObject();}dbAdd(c->db,key,zobj);
}
// 采用 ziplist 编码
robj *createZsetZiplistObject(void) {unsigned char *zl = ziplistNew();robj *o = createObject(OBJ_ZSET, zl);o->encoding = OBJ_ENCODING_ZIPLIST;return o;
}
选择编码方式的逻辑
在有序集合的插入操作中,Redis 会根据特定条件选择编码方式。在插入操作中,Redis 会检查 zobj
的编码类型,如果是 ZIPLIST
编码,会进行 ziplist
相关的操作;如果是 SKIPLIST
编码,会进行 skiplist
相关的操作。源码如下:
int zsetAdd(robj *zobj, double score, sds ele, int *incr, double *newscore) {if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {// 使用 ziplist 进行插入操作的代码// 如果 ziplist 超过一定大小,会转换为 skiplist} else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {// 使用 skiplist 进行插入操作的代码}return 1;
}
判断是否需要转换
在 t_zset.c
文件中的 zsetAdd
函数中,有一个逻辑会判断是否需要将 ziplist
转换为 skiplist
:
if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {// 如果 ziplist 的元素数量超过一定阈值,或者单个元素的长度超过一定阈值if (ziplistLen(zobj->ptr) > server.zset_max_ziplist_entries ||ziplistBlobLen(zobj->ptr) > server.zset_max_ziplist_value){zsetConvert(zobj, OBJ_ENCODING_SKIPLIST);}
}
ziplist本身没有排序功能,而且没有键值对的概念,因此需要有 zset 通过编码实现:
- ziplist是连续内存,因此 score 和 element 是紧挨在一起的两个 entry,element 在前, score 在后
- score 越小越接近队首,score 越大越接近队尾,按照 score 值升序排列
Hash
Hash 底层采用的编码与 Zset 基本一致,只需要把排序相关的 Skiplist 去掉即可。
创建哈希
在 t_hash.c
文件中,可以找到创建哈希对象的代码:
robj *createHashObject(void) {unsigned char *zl = ziplistNew();robj *o = createObject(OBJ_HASH, zl);o->encoding = OBJ_ENCODING_ZIPLIST;return o;
}
Hash 结构默认采用 ziplist 编码,用来节省内存。 ziplist 中相邻的两个 entry 分别保存 field 和 value。
选择编码方式的逻辑
在 t_hash.c
文件中的 hashTypeSet
函数中,会判断是否需要将 ziplist
转换为 hashtable
void hashTypeTryConversion(robj *o, robj **argv, int start, int end) {int i;size_t sum = 0;// 已经是 ziplist 编码了,就什么都不做if (o->encoding != OBJ_ENCODING_ZIPLIST) return;// 依次遍历命令种的 field、valuefor (i = start; i <= end; i++) {if (!sdsEncodedObject(argv[i]))continue;size_t len = sdslen(argv[i]->ptr);// 如果 field 或 value 超过了hash_max_ziplist_value,则转为 hashtableif (len > server.hash_max_ziplist_value) {hashTypeConvert(o, OBJ_ENCODING_HT);return;}sum += len;}// ziplist 大小超过 1g,也要转if (!ziplistSafeToAdd(o->ptr, sum))hashTypeConvert(o, OBJ_ENCODING_HT);
}
当满足4个条件中的任意一个时,需要转换:
ziplist
的元素数量是否超过server.hash_max_ziplist_entries
(默认 512 )ziplist
的大小超过 1G- field 或 value 的长度是否超过
server.hash_max_ziplist_value
(默认 64 字节)
总结
本文讲解了什么Redis对象,已经面向用户的5种常用数据类型的底层逻辑,希望对你有帮助。
相关文章:

Redis源码学习:Redis对象和5种数据类型的工作原理
Redis 提供 5 种基本数据类型:String(字符串)、List(列表)、Set(集合)、Hash(哈希)、Zset(有序集合),这些数据类型可以供用户直接使用…...

从理论到实践掌握UML
统一建模语言(UML)是软件工程师用来设计软件系统的一种工具,就像是一套图形化的说明书。它让开发团队能够以图形化的方式来理解、设计和开发软件系统,比起用文字来描述,更加直观易懂。本文通过UML实例化的理论和实践相…...

LabVIEW Windows与RT系统的比较与选择
LabVIEW是一种系统设计和开发环境,广泛应用于各类工程和科学应用中。LabVIEW Windows和LabVIEW RT(Real-Time)是LabVIEW的两个主要版本,分别适用于不同的应用场景。以下从多个角度详细分析两者的区别,并提供选择建议。…...

docker搭建mongo副本集
1、mongo集群分类 MongoDB集群有4种类型,分别是主从复制、副本集、分片集群和混合集群。 MongoDB的主从复制是指在一个MongoDB集群中,一个节点(主节点)将数据写入并同步到其他节点(从节点)。主从复制提供…...

关于Pytorch转换为MindSpore的一点建议
一、事先准备 必须要对Mindspore有一些了解,因为这个框架确实有些和其它流程不一样的地方,比如算子计算、训练过程中的自动微分,所以这两个课程要好好过一遍,官网介绍文档最好也要过一遍 1、零基础Mindspore:https://…...

JetBrains IDEA 新旧UI切换
JetBrains IDE 新旧UI切换 IntelliJ IDEA 的老 UI 以其经典的布局和稳定的性能,成为了许多开发者的首选。而新 UI 则在此基础上进行了全面的改进,带来了更加现代化、响应式和高效的用户体验。无论是新用户还是老用户,都可以通过了解和适应这…...

iOS KeychainAccess的了解与使用
KeychainAccess 是一个用于 iOS、macOS、tvOS 和 watchOS 上的 Swift 密钥链访问库。它提供了一个简单且安全的 API,用于在设备的密钥链中存储和检索数据。 KeychainAccess 的一些主要特点包括: 简单易用的 API:该库提供了一个直观的 API,可以轻松地将数据存储和检…...

STM32 Customer BootLoader 刷新项目 (二) 方案介绍
STM32 Customer BootLoader 刷新项目 (二) 方案介绍 文章目录 STM32 Customer BootLoader 刷新项目 (二) 方案介绍1. 需求分析2. STM32 Memery介绍3. BootLoader方案介绍4. 支持指令 1. 需求分析 首先在开始编程之前,我们先详细设计一下BootLoder的方案。 本项目做…...

2-14 基于matlab的GA优化算法优化车间调度问题
基于matlab的GA优化算法优化车间调度问题。n个工作在m个台机器上加工。已知每个工作中工序加工顺序、各工序的加工时间以及每个工件所包含的工序,在满足约束条件的前提下,目的是确定机器上各工件顺序,以保证某项性能指标最优。程序功能说明&a…...

Program-of-Thoughts(PoT):结合Python工具和CoT提升大语言模型数学推理能力
Program of Thoughts Prompting:Disentangling Computation from Reasoning for Numerical Reasoning Tasks github:https://github.com/wenhuchen/Program-of-Thoughts 一、动机 数学运算和金融方面都涉及算术推理。先前方法采用监督训练的形式,但这…...

ansible setup模块
用于收集有关目标主机的系统和网络信息,并将这些信息存储为一个facts变量,可以在Playbook的后续任务中使用。setup模块可以用来获取主机的操作系统、软件包、IP地址、内存、磁盘和其他硬件信息。这些信息对编写Playbook和进行条件判断非常有用。当你在Pl…...

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] LYA的测试用例执行计划(100分) - 三语言AC题解(Python/Java/Cpp)
🍭 大家好这里是清隆学长 ,一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 💻 ACM银牌🥈| 多次AK大厂笔试 | 编程一对一辅导 👏 感谢大家的订阅➕ 和 喜欢💗 📎在线评测链接 https://app5938.acapp.acwing.com.cn/contest/2/problem/OD…...

NSIS 入门教程 (一)
介绍 大多数应用程序都附带一个安装程序,它将所需的文件复制到正确的文件夹中,创建注册表项,并提供卸载例程以(希望)从计算机中彻底删除应用程序. 有多种解决方案可以为自主开发的应用程序配备安装程序。除了Install …...

cve-2015-3306-proftpd-vulfocus
1.原理 proftp是用于搭建基于ftp协议的应用软件 ProFTPD是ProFTPD团队的一套开源的FTP服务器软件。该软件具有可配置性强、安全、稳定等特点。 ProFTPD 1.3.5中的mod_copy模块允许远程攻击者通过站点cpfr和site cpto命令读取和写入任意文件。任何未经身份验证的客户端都可以…...

超详细!想进华为od的请疯狂看我!
三分钟带你全面了解华为OD 【合同及管理】签约方为科锐国际/外企德科(人力服务公司),劳动合同期为4年,试用期6个月。员工关系合同管理、五险一金、考勤发薪由科锐国际/外企德科负责;定级定薪、员工培训、工作安排、绩…...

MQTT协议与TCP/IP协议在性能上的区别
MQTT协议与TCP/IP协议在性能上的区别主要体现在以下几个方面: 1.协议开销与传输效率: ① MQTT:MQTT协议针对消息传递进行了优化,使用了小型的控制包和变长的包头设计,极大程度地减少了数据传输过程中的冗余和带宽消耗…...

LeetCode 每日一题 2024/6/17-2024/6/23
记录了初步解题思路 以及本地实现代码;并不一定为最优 也希望大家能一起探讨 一起进步 目录 6/17 522. 最长特殊序列 II6/18 2288. 价格减免6/19 2713. 矩阵中严格递增的单元格数6/20 2748. 美丽下标对的数目6/21 LCP 61. 气温变化趋势6/22 2663. 字典序最小的美丽字…...

FlinkCDC pipeline模式 mysql-to-paimon.yaml
flinkcdc 需要引入: source端: flink-cdc-pipeline-connector-mysql-xxx.jar、mysql-connector-java-xxx.jar、 sink端: flink-cdc-pipeline-connector-paimon-xxx.jar flinkcdc官方提供connect包下载地址,pipeline模式提交作业和…...

mysql数据库入门手册
数据库 常见的数据库查看当前用户及其权限创建用户授权用户访问数据库撤销用户权限修改用户密码删除用户增创建一个数据库创建表表中插入数据表中添加字段(三种方式) 删删除表记录删除表字段删除表(三种方式)删除数据库 改修改表名…...

增强大型语言模型(LLM)可访问性:深入探究在单块AMD GPU上通过QLoRA微调Llama 2的过程
Enhancing LLM Accessibility: A Deep Dive into QLoRA Through Fine-tuning Llama 2 on a single AMD GPU — ROCm Blogs 基于之前的博客《使用LoRA微调Llama 2》的内容,我们深入研究了一种称为量化低秩调整(QLoRA)的参数高效微调࿰…...

空间复杂度 线性表,顺序表尾插。
各位少年,大家好,我是那一脸阳光,本次分享的主题是时间复杂度和空间复杂度 还有顺序表文章讲解和分享,如有不对可以评论区指导。 时间复杂度例题 // 计算斐波那契递归Fib的时间复杂度? long long Fib(size_t N){if(N…...

linux创建用户、切换用户、删除用户
创建用户 # 创建新用户 sudo useradd newuser# 设置新用户的密码 sudo passwd newuser切换用户 # 切换到新用户 su newuser# 验证用户切换 whoami 删除用户 # 删除用户 sudo userdel -r username# 验证用户是否已被删除 grep username /etc/passwd 如果删除用户时提示&…...

BC64 牛牛的快递(c++)
牛牛的快递 题目描述输入描述输出描述示例代码 解题思路例如 题目描述 牛牛正在寄快递,他了解到快递在 1kg 以内的按起步价 20 元计算,超出部分按每 kg 1元计算,不足 1kg 部分按 1kg计算。如果加急的话要额外付五元,请问牛牛总共要…...

离线linux通过USB连接并使用手机网络
离线linux通过USB连接并使用手机网络 引场景 引 离线环境要安装一些软件特别麻烦,要自己去官网下载对应的包,然后上传到服务器上,再解压,编译,执行,配置变量等等,错一步都可能安装失败。有网络…...

I2C总线8位IO扩展器PCF8574
PCF8574用于I2C总线的远程8位I/O扩展器 PCF8574国产有多个厂家有替代产品,图示为其中一款HT8574 1 产品特点 低待机电流消耗:10 uA(最大值) I2C 转并行端口扩展器 漏极开路中断输出 与大多数微控制器兼容 具有大电流驱动能力的闭…...

webClient + fastJSON2 获取json格式的数据,同时解析至java class 并 下划线转驼峰
webClient中 .accept(MediaType.APPLICATION_JSON) 决定返回值是什么格式一般情况可以不写,但这里要获取JSON格式的 .bodyToMono(String.class)指定返回类型 fastJSON2中 Student student JSON.parseObject(result, Student.class, JSONReader.Feature.SupportSm…...

4、SpringMVC 实战小项目【加法计算器、用户登录、留言板、图书管理系统】
SpringMVC 实战小项目 3.1 加法计算器3.1.1 准备⼯作前端 3.1.2 约定前后端交互接⼝需求分析接⼝定义请求参数:响应数据: 3.1.3 服务器代码 3.2 ⽤⼾登录3.2.1 准备⼯作3.2.2 约定前后端交互接⼝3.2.3 实现服务器端代码 3.3 留⾔板实现服务器端代码 3.4 图书管理系统准备后端 3…...

OpenCV--形态学
形态学 形态学图像全局二值化自适应阈值腐蚀操作膨胀开运算闭运算形态学梯度顶帽操作黑帽操作 形态学 从图像中提取对表达和描绘区域形状有意义的图像分量 图像全局二值化 import cv2 import numpy as np """ 图像全局二值化--0与255 二值化的主要目的是通过…...

【LinuxC语言】IP地址相关的函数
文章目录 前言inet_addr()inet_aton()inet_ntoa()示例代码总结前言 在Linux C语言编程中,处理网络通信是一个核心主题,其中涉及到的IP地址相关函数扮演着至关重要的角色。这些函数允许我们在不同的网络层次上操作和管理IP地址,从而实现有效的数据传输和通信控制。本文将介绍…...

QT事件处理系统之五:自定义事件的发送案例 sendEvent和postEvent接口
1、案例 双击窗口,会发送 自定义事件,然后在事件过滤中心进行拦截处理自定义事件。 2、核心代码 /*解释:双击窗口时,将产生双击事件,然后该事件被包裹成一个对象,随后将会被发往event事件中心,然后进行事件的处理(Widget对象);因为m_lineEdit开启了事件过滤机制,所…...