您的位置:首页 > 汽车 > 新车 > Redis zset 共享对象

Redis zset 共享对象

2024/12/27 5:24:41 来源:https://blog.csdn.net/qq_40080842/article/details/140698293  浏览:    关键词: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 源码剖析与实战

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com