当前位置: 首页 > news >正文

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-entries
  • HT:哈希表,用于存储字符串的大集合,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 种基本数据类型&#xff1a;String&#xff08;字符串&#xff09;、List&#xff08;列表&#xff09;、Set&#xff08;集合&#xff09;、Hash&#xff08;哈希&#xff09;、Zset&#xff08;有序集合&#xff09;&#xff0c;这些数据类型可以供用户直接使用…...

从理论到实践掌握UML

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

LabVIEW Windows与RT系统的比较与选择

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

docker搭建mongo副本集

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

关于Pytorch转换为MindSpore的一点建议

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

JetBrains IDEA 新旧UI切换

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

iOS KeychainAccess的了解与使用

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

STM32 Customer BootLoader 刷新项目 (二) 方案介绍

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

2-14 基于matlab的GA优化算法优化车间调度问题

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

Program-of-Thoughts(PoT):结合Python工具和CoT提升大语言模型数学推理能力

Program of Thoughts Prompting:Disentangling Computation from Reasoning for Numerical Reasoning Tasks github&#xff1a;https://github.com/wenhuchen/Program-of-Thoughts 一、动机 数学运算和金融方面都涉及算术推理。先前方法采用监督训练的形式&#xff0c;但这…...

ansible setup模块

用于收集有关目标主机的系统和网络信息&#xff0c;并将这些信息存储为一个facts变量&#xff0c;可以在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 入门教程 (一)

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

cve-2015-3306-proftpd-vulfocus

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

超详细!想进华为od的请疯狂看我!

三分钟带你全面了解华为OD 【合同及管理】签约方为科锐国际/外企德科&#xff08;人力服务公司&#xff09;&#xff0c;劳动合同期为4年&#xff0c;试用期6个月。员工关系合同管理、五险一金、考勤发薪由科锐国际/外企德科负责&#xff1b;定级定薪、员工培训、工作安排、绩…...

MQTT协议与TCP/IP协议在性能上的区别

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

LeetCode 每日一题 2024/6/17-2024/6/23

记录了初步解题思路 以及本地实现代码&#xff1b;并不一定为最优 也希望大家能一起探讨 一起进步 目录 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 需要引入&#xff1a; source端&#xff1a; flink-cdc-pipeline-connector-mysql-xxx.jar、mysql-connector-java-xxx.jar、 sink端&#xff1a; flink-cdc-pipeline-connector-paimon-xxx.jar flinkcdc官方提供connect包下载地址&#xff0c;pipeline模式提交作业和…...

mysql数据库入门手册

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

增强大型语言模型(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》的内容&#xff0c;我们深入研究了一种称为量化低秩调整&#xff08;QLoRA&#xff09;的参数高效微调&#xff0…...

FastAPI 教程:从入门到实践

FastAPI 是一个现代、快速&#xff08;高性能&#xff09;的 Web 框架&#xff0c;用于构建 API&#xff0c;支持 Python 3.6。它基于标准 Python 类型提示&#xff0c;易于学习且功能强大。以下是一个完整的 FastAPI 入门教程&#xff0c;涵盖从环境搭建到创建并运行一个简单的…...

《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》

在注意力分散、内容高度同质化的时代&#xff0c;情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现&#xff0c;消费者对内容的“有感”程度&#xff0c;正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中&#xff0…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序

一、开发环境准备 ​​工具安装​​&#xff1a; 下载安装DevEco Studio 4.0&#xff08;支持HarmonyOS 5&#xff09;配置HarmonyOS SDK 5.0确保Node.js版本≥14 ​​项目初始化​​&#xff1a; ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...

算法笔记2

1.字符串拼接最好用StringBuilder&#xff0c;不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...

Docker 本地安装 mysql 数据库

Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker &#xff1b;并安装。 基础操作不再赘述。 打开 macOS 终端&#xff0c;开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...

JS手写代码篇----使用Promise封装AJAX请求

15、使用Promise封装AJAX请求 promise就有reject和resolve了&#xff0c;就不必写成功和失败的回调函数了 const BASEURL ./手写ajax/test.jsonfunction promiseAjax() {return new Promise((resolve, reject) > {const xhr new XMLHttpRequest();xhr.open("get&quo…...

【C++进阶篇】智能指针

C内存管理终极指南&#xff1a;智能指针从入门到源码剖析 一. 智能指针1.1 auto_ptr1.2 unique_ptr1.3 shared_ptr1.4 make_shared 二. 原理三. shared_ptr循环引用问题三. 线程安全问题四. 内存泄漏4.1 什么是内存泄漏4.2 危害4.3 避免内存泄漏 五. 最后 一. 智能指针 智能指…...

Python+ZeroMQ实战:智能车辆状态监控与模拟模式自动切换

目录 关键点 技术实现1 技术实现2 摘要&#xff1a; 本文将介绍如何利用Python和ZeroMQ消息队列构建一个智能车辆状态监控系统。系统能够根据时间策略自动切换驾驶模式&#xff08;自动驾驶、人工驾驶、远程驾驶、主动安全&#xff09;&#xff0c;并通过实时消息推送更新车…...

Web中间件--tomcat学习

Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机&#xff0c;它可以执行Java字节码。Java虚拟机是Java平台的一部分&#xff0c;Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...

【Linux手册】探秘系统世界:从用户交互到硬件底层的全链路工作之旅

目录 前言 操作系统与驱动程序 是什么&#xff0c;为什么 怎么做 system call 用户操作接口 总结 前言 日常生活中&#xff0c;我们在使用电子设备时&#xff0c;我们所输入执行的每一条指令最终大多都会作用到硬件上&#xff0c;比如下载一款软件最终会下载到硬盘上&am…...