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

Redis zset 共享对象

前言

本文介绍 Redis 中 skiplist 编码的 zset 对象是如何共享对象的。

skiplist 编码的 zset 对象为了同时支持高效的点查询和范围查询,内部使用了跳表和哈希表。倘若将每个插入的元素都拷贝两份,分别插入跳表和哈希表,将浪费大量的内存,那 Redis 是怎么让二者共享对象的呢?

介绍的源码基于 Redis 5.0.8 版本,不同版本采用的方式可能不同,同时会删除一些不影响理解的部分。

前置知识

对象的表示

Redis 中的每个对象都由一个 redisObject 结构表示,该结构中和保存数据有关的三个属性分别是 type、encoding 和 ptr。

// redisObject 用来保存各种对象
typedef struct redisObject {unsigned type:4;        // 数据类型unsigned encoding:4;    // 编码类型unsigned lru:LRU_BITS; int refcount;           // 引用计数void *ptr;              // 指向值的指针
} robj;

字符串对象

// sdshdr64 len 和 alloc 用的是 uint64_t
struct sdshdr64 {uint64_t len;uint64_t alloc;char buf[];
};

Redis 中的字符串对象由 SDS 表示,它的主要属性有以下三个:

  • len 属性记录已使用长度
  • alloc 属性记录分配空间长度
  • buf 属性是一个 char 类型的数组,记录实际的数据

zset 对象

// zset 有序集合,里面包含哈希表和跳表
typedef struct zset {dict *dict;zskiplist *zsl;
} zset;

client

client 中记录了我们输入的命令的参数。

typedef struct client {int argc;               // 当前命令的参数数量robj **argv;            // 当前命令的参数
} client;

问题探索

下面就顺着源码一步步寻找 zset 是怎么共享对象的。

当我们在 Redis 客户端输入如下命令时:

127.0.0.1:6379> ZADD study 1 mem1 2 mem2 3 mem3

会调用 zaddGenericCommand 函数。该函数会有如下一段向 zset 插入数据的代码:

for (j = 0; j < elements; j++) {double newscore;score = scores[j];int retflags = flags;ele = c->argv[scoreidx+1+j*2]->ptr;int retval = zsetAdd(zobj, score, ele, &retflags, &newscore);
}

以上面的命令为例:

  • scores 为 double 类型的数组,记录所有的 score,即 1 2 3
  • scoreidx 为 2,指示实际的 score score-element pair 的开始位置
  • elements 为 3,即 score 和 score-element pair 的数量
  • argv 为 robj * 类型的数组,记录了所有参数对应的字符串对象

关键是 ele 是什么,ele 的类型是 sds,sds 类型就是 char * 类型的指针。当 j 为 0 时,scoreidx+1+j*2 为 3,c->argv[3] 为从 mem1 构建的 SDS 所属的 redisObject 对象,c->argv[3]->ptr 是指向 SDS 底层数组的指针,所以 ele 就是指向 SDS 底层数组的指针。

在 zsetAdd 函数中,会用如下代码将数据插入跳表和哈希表:

ele = sdsdup(ele);
znode = zslInsert(zs->zsl,score,ele);
serverAssert(dictAdd(zs->dict,ele,&znode->score) == DICT_OK);

首先复制了一份新的 SDS 对象,然后将它插入到跳表和哈希表中。ele 是一个 char * 类型的指针,因此插入跳表和哈希表中的 key 是一个指针,这个指针指向同一份数据,这两种结构通过指针来共享相同的成员。同理,score 在跳表中是一个 double 类型的浮点数,在哈希表中是保存这个浮点数的指针。

这里为什么需要复制一份,不太理解,以下是个人猜测。

假如不复制,直接使用 c->argv 里面保存的 redisObject 内的 SDS 对象。redisObject 中还有一些我们并不需要的变量,若是字符串比较短,redisObject 和 SDS 内存是一起申请的,不能归还其中一个,就白白浪费一些空间来保存并不需要的数据;若是字符串比较长,redisObject 和 SDS 内存是分开申请的,这样可以做到只归还 redisObject 的内存,SDS 的内存交给 zset 归还,但这样的话处理上比较繁琐,需要加一些额外的逻辑来判断,同样耗费时间。

参考资料

  • Redis 5.0.8
  • 《Redis 设计与实现》
  • 极客时间:Redis 源码剖析与实战

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • OpenSNN推文:百度沈抖:深度拥抱人工智能+,加速发展新质生产力,共创智能时代新未来
  • 故障诊断 | 基于Transformer故障诊断分类预测(Matlab)
  • Godot入门 03世界构建1.0版
  • 【.NET 6 实战--孢子记账--从单体到微服务】--开发环境设置
  • 日拱一卒 | JVM
  • 哪个邮箱最安全最好用啊
  • Webpack 从入门到精通
  • PCB设计需要注意哪些事项?
  • LeetCode 2766.重新放置石块:哈希表
  • 【学习笔记】子集DP
  • nginx代理服务配置,基于http协议-Linux(CentOS)
  • JavaEE - Spring Boot 简介
  • MATLAB-bode图编程
  • 本地连接远程阿里云K8S
  • OpenCV车牌识别技术详解
  • 77. Combinations
  • axios请求、和返回数据拦截,统一请求报错提示_012
  • CSS3 变换
  • css布局,左右固定中间自适应实现
  • Docker 笔记(2):Dockerfile
  • Java面向对象及其三大特征
  • Joomla 2.x, 3.x useful code cheatsheet
  • QQ浏览器x5内核的兼容性问题
  • ReactNative开发常用的三方模块
  • SQLServer之创建数据库快照
  • Yeoman_Bower_Grunt
  • 编写高质量JavaScript代码之并发
  • 大型网站性能监测、分析与优化常见问题QA
  • 等保2.0 | 几维安全发布等保检测、等保加固专版 加速企业等保合规
  • 工作手记之html2canvas使用概述
  • 机器学习 vs. 深度学习
  • 基于MaxCompute打造轻盈的人人车移动端数据平台
  • 如何合理的规划jvm性能调优
  • 入口文件开始,分析Vue源码实现
  • 使用parted解决大于2T的磁盘分区
  • 网络应用优化——时延与带宽
  • 微信小程序实战练习(仿五洲到家微信版)
  • 译米田引理
  • MiKTeX could not find the script engine ‘perl.exe‘ which is required to execute ‘latexmk‘.
  • ​Python 3 新特性:类型注解
  • ## 临床数据 两两比较 加显著性boxplot加显著性
  • (4) PIVOT 和 UPIVOT 的使用
  • (动态规划)5. 最长回文子串 java解决
  • (二)丶RabbitMQ的六大核心
  • (翻译)Quartz官方教程——第一课:Quartz入门
  • (附源码)spring boot校园健康监测管理系统 毕业设计 151047
  • (六) ES6 新特性 —— 迭代器(iterator)
  • (转)Android学习笔记 --- android任务栈和启动模式
  • (轉貼) UML中文FAQ (OO) (UML)
  • .NET Core工程编译事件$(TargetDir)变量为空引发的思考
  • .NET 通过系统影子账户实现权限维持
  • .net6 webapi log4net完整配置使用流程
  • .netcore如何运行环境安装到Linux服务器
  • @angular/cli项目构建--http(2)
  • @RequestBody与@RequestParam