Redis的几大数据结构之一的ZSet实现的就是Ordered Set有序集合,通常在实际业务开发中ZSet也是较为高频使用的数据结构,可以用来实现排行榜、有序队列等应用。ZSet本身根据以下2个变量控制底层数据结构的选用,底层有ziplist和dict+skiplist的实现方式。
为了尽量节省内存,zset在以下2个条件都成立时会使用ziplist数据结构实现
zset-max-ziplist-entries 128 // 当zset中存的元素数量 <= 128时
zset-max-ziplist-value 64 // 当每个元素的字节大小 <= 64时
在一开始调用zadd命令时如果val的大小<=64字节,肯定先用ziplist创建。在以后每次zadd命令都会检查这2个条件以便进行ziplist到skiplist的转化。那么skiplist会不会退化成ziplist? 也会的,在进行zinter和zunion命令的时候,因为对2个集合的元素做了合并或求交集,可能对集合的元素数量改变了就需要进行转化。
从zadd命令作为切入点看看zset是如何选择实例化数据结构的
/* This generic command implements both ZADD and ZINCRBY. */
void zaddGenericCommand(client *c, int flags) {
// 省略了解析命令,提取输入参数,校验命令语法的代码..
/* redis有16个库,每个库本身是一个dict的实现,这里根据输入key找redis obj对象 */
zobj = lookupKeyWrite(c->db,key);
if (zobj == NULL) {
if (xx) goto reply_to_client; /* 无此key且操作参数为xx模式,直接响应 */
// 如果redis.conf配置的max_ziplist_entries是0或者本次添加的元素大小超过了max_ziplist_value(默认64字节),就用skiplist+dict实现zset
if (server.zset_max_ziplist_entries == 0 ||
server.zset_max_ziplist_value < sdslen(c->argv[scoreidx+1]->ptr))
{
// 创建skiplist+dict,每个redis obj对象有encoding字段标识它是什么数据类型
zobj = createZsetObject();
} else {
// 创建ziplist,一般第一次zadd都会走这里,除非元素比较大
zobj = createZsetZiplistObject();
}
// 加到redis库中
dbAdd(c->db,key,zobj);
} else {
if (zobj->type != OBJ_ZSET) {
addReply(c,shared.wrongtypeerr);
goto cleanup;
}
}
// 遍历元素,加入zset,elements在前面解析客户端输入参数时计算出
for (j = 0; j < elements; j++) {
double newscore;
// ele对应的分数
score = scores[j];
int retflags = flags;
// ele元素
ele = c->argv[scoreidx+1+j*2]->ptr;
// 执行zadd命令逻辑
int retval = zsetAdd(zobj, score, ele, &retflags, &newscore);
// 出错了直接响应
if (retval == 0) {
addReplyError(c,nanerr);
goto cleanup;
}
// 统计加入、更新的元素
if (retflags & ZADD_ADDED) added++;
if (retflags & ZADD_UPDATED) updated++;
if (!(retflags & ZADD_NOP)) processed++;
// score用来回应incrby命令,告诉用户此元素最新的分数
score = newscore;
}
// dirty统计一段时间内redis改变的key数量,用来rdb save做判断
server.dirty += (added+updated);
// 省略失败清理资源代码和响应客户端代码
}
在上面可以看到实例化zset底层数据结构的逻辑,但是就添加元素来说是在zsetAdd函数实现。
int zsetAdd(
// zset对象,可能是ziplist或skiplist
robj *zobj,
// 分数
double score,
// 元素值
sds ele,
// 命令参数
int *flags,
// newscore指针,计算新分数并响应
double *newscore) {
/* 提取命令参数 */
int incr = (*flags & ZADD_INCR) != 0;
int nx = (*flags & ZADD_NX) != 0;
int xx = (*flags & ZADD_XX) != 0;
*flags = 0; /* We'll return our response flags. */
double curscore;
/* score有效性检查 */
if (isnan(score)) {
*flags = ZADD_NAN;
return 0;
}
/* 根据数据结构执行添加逻辑 */
// ziplist
if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
unsigned char *eptr;
// zzlFind,zl是ziplist缩写,ziplist查找ele
if ((eptr = zzlFind(zobj->ptr, ele,&curscore)) != NULL) {
// ziplist已存在ele值
/* nx模式,啥都不做,统计个数据返回 */
if (nx) {
*flags |= ZADD_NOP;
return 1;
}
/* 如果是zincrby命令,就算下加分 */
if (incr) {
score += curscore;
if (isnan(score)) {
*flags |= ZADD_NAN;
r

本文深入探讨了Redis中的有序集合ZSet数据结构,包括ziplist和skiplist两种底层实现方式。详细介绍了这两种数据结构的选择标准、内存布局及增删查改等核心操作的实现原理。
最低0.47元/天 解锁文章
867

被折叠的 条评论
为什么被折叠?



