Redis复习-五种数据类型

news/2025/2/27 10:44:01/文章来源:https://www.cnblogs.com/Helix6/p/18740450

String

String是Redis中最常见的数据存储类型:
1.其基本编码方式是RAW,基于简单动态字符串(SDS)实现,存储上限为512mb。
2.如果存储的SDS长度小于44字节,则会采用EMBSTR编码,此时object head与SDS是一段连续空间。申请内存时只需要调用一次内存分配函数,效率更高。
3.如果存储的字符串是整数值,并且大小在LONG_MAX范围内,则会采用INT编码:直接将数据保存在RedisObject的ptr指针位置(刚好8字节),不再需要SDS了。

三种结构:

List

Redis的List结构类似一个双端链表,可以从首、尾操作列表中的元素:
在3.2版本之前,Redis采用ZipList和LinkedList来实现List,当元素数量小于512并且元素大小小于64字节时采用ZipList编码,超过则采用LinkedList编码。
在3.2版本之后,Redis统一采用QuickList来实现List:
源码部分

void pushGenericCommand(client *c, int where, int xx) {int j;// 尝试找到KEY对应的listrobj *lobj = lookupKeyWrite(c->db, c->argv[1]);// 检查类型是否正确if (checkType(c,lobj,OBJ_LIST)) return;// 检查是否为空if (!lobj) {
​    if (xx) {
​      addReply(c, shared.czero);
​      return;
​    }
​    // 为空,则创建新的QuickList
​    lobj = createQuicklistObject();
​    quicklistSetOptions(lobj->ptr, server.list_max_ziplist_size,
​              server.list_compress_depth);
​    dbAdd(c->db,c->argv[1],lobj);}// 略 ...
}

创建quickList

robj *createQuicklistObject(void) {// 申请内存并初始化QuickListquicklist *l = quicklistCreate();// 创建RedisObject,type为OBJ_LIST// ptr指向 QuickListrobj *o = createObject(OBJ_LIST,l);// 设置编码为 QuickListo->encoding = OBJ_ENCODING_QUICKLIST;return o;
}

整体结构:

Set

Set是Redis中的单列集合,满足下列特点:
1.不保证有序性
2.保证元素唯一
3.求交集、并集、差集

Set是Redis中的集合,不一定确保元素有序,可以满足元素唯一、查询效率要求极高。
1.为了查询效率和唯一性,set采用HT编码(Dict)。Dict中的key用来存储元素,value统一为null。
2.当存储的所有数据都是整数,并且元素数量不超过set-max-intset-entries时,Set会采用IntSet编码,以节省内存。

源码结构:

每次添加时判断是否需要改变编码

int setTypeAdd(robj *subject, sds value) { //已经是HT编码,直接添加元素long long llval;if (subject->encoding == OBJ_ENCODING_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) { //目前是INTSET//判断value是否是整数if (isSdsRepresentableAsLongLong(value,&llval) == C_OK) {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也就是SortedSet,其中每一个元素都需要指定一个score值和member值:
1.可以根据score值排序后
2.member必须唯一
3.可以根据member查询分数

因此,zset底层数据结构必须满足键值存储、键必须唯一、可排序这几个需求。之前学习的哪种编码结构可以满足?
SkipList:可以排序,并且可以同时存储score和ele值(member)
HT(Dict):可以键值存储,并且可以根据key找value

zset源码中使用了两种数据结构

// zset结构
typedef struct zset {// Dict指针dict *dict;// SkipList指针zskiplist *zsl;
} zset;

使用两种数据结构的整体结构

当元素数量不多时,HT和SkipList的优势不明显,而且更耗内存。因此zset还会采用ZipList结构来节省内存,不过需要同时满足两个条件:
1.元素数量小于zset_max_ziplist_entries,默认值128
2.每个元素都小于zset_max_ziplist_value字节,默认值64

每次添加元素时判断是否需要改变ziplist编码为ht和skiplist

int zsetAdd(robj *zobj, double score, sds ele, int in_flags, int *out_flags, double *newscore) {/* 判断编码方式*/if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {// 是ZipList编码
​    unsigned char *eptr;
​    // 判断当前元素是否已经存在,已经存在则更新score即可
​     if ((eptr = zzlFind(zobj->ptr,ele,&curscore)) != NULL) {
​      //...略
​      return 1;
​    } else if (!xx) {
​      // 元素不存在,需要新增,则判断ziplist长度有没有超、元素的大小有没有超
​      if (zzlLength(zobj->ptr)+1 > server.zset_max_ziplist_entries|| sdslen(ele) > server.zset_max_ziplist_value|| !ziplistSafeToAdd(zobj->ptr, sdslen(ele)))
​      { // 如果超出,则需要转为SkipList编码
​        zsetConvert(zobj,OBJ_ENCODING_SKIPLIST);
​      } else {
​        zobj->ptr = zzlInsert(zobj->ptr,ele,score);
​        if (newscore) *newscore = score;
​        *out_flags |= ZADD_OUT_ADDED;
​        return 1;
​      }
​    } else {
​      *out_flags |= ZADD_OUT_NOP;
​      return 1;
​    }}// 本身就是SKIPLIST编码,无需转换if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {
​    // ...略} else {
​    serverPanic("Unknown sorted set encoding");}return 0; /* Never reached. */
}

ziplist本身没有排序功能,而且没有键值对的概念,因此需要有zset通过编码实现:
1.ZipList是连续内存,因此score和element是紧挨在一起的两个entry, element在前,score在后
2.score越小越接近队首,score越大越接近队尾,按照score值升序排列

Hash

Hash结构与Redis中的Zset非常类似:
1.都是键值存储
2.都需求根据键获取值
3.键必须唯一

区别如下:
1.zset的键是member,值是score;hash的键和值都是任意值
2.zset要根据score排序;hash则无需排序

因此,Hash底层采用的编码与Zset也基本一致,只需要把排序有关的SkipList去掉即可:
Hash结构默认采用ZipList编码,用以节省内存。 ZipList中相邻的两个entry 分别保存field和value
当数据量较大时,Hash结构会转为HT编码,也就是Dict,触发条件有两个:
1.ZipList中的元素数量超过了hash-max-ziplist-entries(默认512)
2.ZipList中的任意entry大小超过了hash-max-ziplist-value(默认64字节)

ZipList结构

Dict结构

源码部分:
hset指令源码

void hsetCommand(client *c) {// hset user1 name Jack age 21int i, created = 0;robj *o; // 略 ...// 判断hash的key是否存在,不存在则创建一个新的,默认采用ZipList编码if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;// 判断是否需要把ZipList转为DicthashTypeTryConversion(o,c->argv,2,c->argc-1);// 循环遍历每一对field和value,并执行hset命令for (i = 2; i < c->argc; i += 2)
​    created += !hashTypeSet(o,c->argv[i]->ptr,c->argv[i+1]->ptr,HASH_SET_COPY);// 略 ...
}

判断Key类型,创建hash结构

robj *hashTypeLookupWriteOrCreate(client *c, robj *key) {// 查找keyrobj *o = lookupKeyWrite(c->db,key);if (checkType(c,o,OBJ_HASH)) return NULL;// 不存在,则创建新的if (o == NULL) {
​    o = createHashObject();
​    dbAdd(c->db,key,o);}return o;
}

创建hash的RedisObject

robj *createHashObject(void) {// 默认采用ZipList编码,申请ZipList内存空间unsigned char *zl = ziplistNew();robj *o = createObject(OBJ_HASH, zl);// 设置编码o->encoding = OBJ_ENCODING_ZIPLIST;return o;
}

把ZipList转为Dict

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、value参数for (i = start; i <= end; i++) {
​    if (!sdsEncodedObject(argv[i]))
​      continue;
​    size_t len = sdslen(argv[i]->ptr);
​    // 如果field或value超过hash_max_ziplist_value,则转为HT
​    if (len > server.hash_max_ziplist_value) {
​      hashTypeConvert(o, OBJ_ENCODING_HT);
​      return;
​    }
​    sum += len;}// ziplist大小超过1G,也转为HTif (!ziplistSafeToAdd(o->ptr, sum))
​    hashTypeConvert(o, OBJ_ENCODING_HT);
}

每次ziplist插入完毕后,都会判断是否需要转码成dict

int hashTypeSet(robj *o, sds field, sds value, int flags) {int update = 0;// 判断是否为ZipList编码if (o->encoding == OBJ_ENCODING_ZIPLIST) {
​    unsigned char *zl, *fptr, *vptr;
​    zl = o->ptr;
​    // 查询head指针
​    fptr = ziplistIndex(zl, ZIPLIST_HEAD);
​    if (fptr != NULL) { // head不为空,说明ZipList不为空,开始查找key
​      fptr = ziplistFind(zl, fptr, (unsigned char*)field, sdslen(field), 1);
​      if (fptr != NULL) {// 判断是否存在,如果已经存在则更新
​        update = 1;
​        zl = ziplistReplace(zl, vptr, (unsigned char*)value,
​            sdslen(value));
​      }
​    }
​    // 不存在,则直接push
​    if (!update) { // 依次push新的field和value到ZipList的尾部
​      zl = ziplistPush(zl, (unsigned char*)field, sdslen(field),
​          ZIPLIST_TAIL);
​      zl = ziplistPush(zl, (unsigned char*)value, sdslen(value),
​          ZIPLIST_TAIL);
​    }
​    o->ptr = zl;
​    /* 插入了新元素,检查list长度是否超出,超出则转为HT */
​    if (hashTypeLength(o) > server.hash_max_ziplist_entries)
​      hashTypeConvert(o, OBJ_ENCODING_HT);} else if (o->encoding == OBJ_ENCODING_HT) {
​    // HT编码,直接插入或覆盖} else {
​    serverPanic("Unknown hash encoding");}return update;
}

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

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

相关文章

cfWGBS揭示与年龄和肌萎缩侧索硬化相关cfDNA甲基化变化及组织/细胞溯源

大家好,这里是专注表观组学十余年,领跑多组学科研服务的易基因。 游离细胞DNA (Cell-free DNA,cfDNA)是血浆中游离的DNA片段,通常来源于正常细胞更新或病理状态下的细胞死亡。cfDNA已被广泛应用于癌症早期检测、胎儿遗传病诊断、器官移植评估等领域。然而,cfDNA在神经退行…

低代码加速智能制造,兰之天的选择是 NocoBase

兰之天借力 NocoBase,破解中小制造企业数字化转型困局,实现智能制造系统开发周期从数月压缩至数周。智能制造的挑战:数字化转型的必然趋势 在全球制造业加速迈向数字化、智能化的背景下,智能制造已成为提升企业竞争力的关键战略。根据财富商业洞察(Fortune Business Insig…

纷享销客CRM全面评测:纷享销客比销售易差异化对比

企业数字化转型热潮中,CRM是众多企业迈向数字化管理的里程碑。近年来,国产CRM在政策推动下成为大中型企业的首选,也有很多企业选择国产CRM替代国外供应商。国产CRM第一梯队中,纷享销客以其卓越的表现脱颖而出,稳坐头把交椅。IDC发布了最新数据报告《IDC China Semiannual …

ABB机器人平衡缸维修

在现代工业生产中,工业机器人扮演着至关重要的角色。其中,ABB机器人以其高精度、高可靠性而被广泛应用。然而,如同所有机械设备一样,ABB机器人也会出现故障,这就需要专业的维修。一、ABB机器人故障与平衡缸维修的重要性ABB机器人故障的出现会严重影响生产进程。机器人平衡…

python打包工具-Nuitka

nuitka将python源码转成C++(这里得到的是二进制的pyd文件,防止了反编译),然后再编译成可执行文件。提高安全性和运行速度。 github:https://github.com/2267770481/cython_test 安装 pip install nuitka pip install ordered-set # 加速编译 pip install zstandard # onef…

Javaweb中Vue指令的详细解析与应用

在现代Web开发中,Vue.js已经成为了一个非常流行且强大的前端框架,尤其是在JavaWeb项目中,它通过简化DOM操作,提高响应式交互的能力,大大加快了开发速度和提高了用户体验。Vue的核心之一是其指令系统,通过一系列预定义或自定义的指令,开发者可以更加方便地控制页面渲染和…

Svelte 最新中文文档教程(21)—— 自定义元素

前言 Svelte,一个语法简洁、入门容易,面向未来的前端框架。 从 Svelte 诞生之初,就备受开发者的喜爱,根据统计,从 2019 年到 2024 年,连续 6 年一直是开发者最感兴趣的前端框架 No.1:Svelte 以其独特的编译时优化机制著称,具有轻量级、高性能、易上手等特性,非常适合构…

AXI总线学习

AXI 总线概述 AXI协议是一种高性能、高带宽、低延迟的片内总线,具有如下特点: 1、总线的地址/控制和数据通道是分离的; 2、支持不对齐的数据传输; 3、支持突发传输,突发传输过程中只需要首地址; 4、具有分离的读/写数据通道; 5、支持显著传输访问和乱序访问; 6、更加容…

windows用任务计划定时执行powershell脚本

环境介绍】 操作系统:Windows Server 2019 Standard,64位操作系统 PowerShell版本:PowerShell 1.0 脚本位置:"F:\Scripts\BackupScript.ps1" 启动目录:"C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe" 【步骤图】1.打开Windows任务计划…

城通网盘就是骗子,根本无法提现,大家不要再被坑了!

​今天很气愤,必须给大家曝光下被城通网盘坑的经历,大家以后都卸载这个垃圾网盘吧,根本无法提现,就是骗你免费给他打工,妥妥的无良企业! 我个人博客站(潘子夜个人博客)会分享一些免费的资源,一般都会上传到百度网盘、城通网盘和夸克网盘,毕竟城通网盘每天看上去还是有…

window的apt-get : Chocolatey工具的命令choco

Chocolatey是一款专为Windows系统开发的、基于NuGet的包管理器工具,类似于Node.js的npm,MacOS的brew,Ubuntu的apt-get,它简称为choco。Chocolatey的设计目标是成为一个去中心化的框架,便于开发者按需快速安装应用程序和工具。 Chocolatey用于完成Windows中软件的搜索、…

揭秘!软件测试开发质量衡量标准全攻略!

在软件开发过程中,软件质量是衡量产品成功与否的重要标准,直接关系到企业的生存和发展。而如何衡量软件质量一直是业界关注的焦点,一直是困扰开发测试团队的问题。随着技术的发展和项目管理方法的演进,质量衡量标准也从最初的简单BUG统计,发展到了更为复杂和全面的服务级别…