Redis zset数据结构以及时间复杂度总结(源码)

目录

在使用zset做一个临时消息列表需求时,有涉及在lua中操作zset的场景,有点担心zset操作会影响脚本性能,因此本文会对zset操作时间复杂度进行总结。

本文以5.1.x 版本为参考,从操作时间复杂度的角度进行梳理,总结关于zset一些常用操作使用时一些注意事项。

下载源码后,zset相关操作实现源码在t_zset.c 源码文件。

底层数据结构

底层实现结合了 压缩列表(ziplist)和 跳跃表 (skiplist)。

bash 复制代码
# 查看编码类型,可以看到使用了哪种集合。
OBJECT ENCODING myzset

(1)压缩列表(ziplist):在满足下述条件阈值时使用压缩列表。

  • 当元素数量 ≤ zset-max-ziplist-entries(默认128)且每个元素的长度 ≤ zset-max-ziplist-value(默认64字节)时使用。
  • 每个元素由 两个连续的压缩列表节点 存储:
    • 第一个节点保存成员(member),第二个节点保存分值(score),例如,插入 (member1, score1) 时,ziplist 的布局如: [member1][score1][member2][score2]...

(2)跳跃表 (skiplist)和 字典(hash): 超过上述阈值时,则会转为跳跃表和哈希表的组合结构。

  • hash 用来存储 value score的映射,因此可以在 O(1)时间复杂度内找到 value 对应的分数
  • skipList每个元素的值都是 [socre,value]**对,按从小到大的顺序进行存储;
  • skipList 可以保证增、删、查等操作时的时间复杂度为O(logN)
c 复制代码
// server.h 文件,zset的结构定义:
typedef struct zset {
    dict *dict; // 哈希表实现,维护 member->score 的映射(O(1) 查找)
    zskiplist *zsl; // 按 score 排序的跳表(支持范围查询)
} zset;

// server.h 文件,跳表zskiplist 的结构定义:
typedef struct zskiplist {
    // 指向跳表的头节点、尾节点
    struct zskiplistNode *header, *tail;
    // 记录跳表的长度
    PORT_ULONG length;
    // 记录当前跳表内,所有节点中层数最大的level
    int level;
} zskiplist;

// server.h 文件,跳表节点的结构定义:
typedef struct zskiplistNode {
    sds ele; // 元素
    double score; // 分数
    struct zskiplistNode *backward; // 指向当前节点的后退指针,用于从后往前遍历跳表
    struct zskiplistLevel {
        struct zskiplistNode *forward; // 指向同一层的下一个节点
        PORT_ULONG span; // 跨度, 可用来计算当前节点在跳表中的一个排名,为zset提供了查看排名的方法
    } level[]; // 数组存储每一层的向前指针和跨度信息,用于实现跳表的快速查找特性
} zskiplistNode;

t_zset.c 源码文件包含与有序集合(zset)相关的操作实现:

c 复制代码
// 根据给定的层数、分数和元素创建一个新的跳表节点
zskiplistNode *zslCreateNode(int level, double score, sds ele) {
    zskiplistNode *zn =
        zmalloc(sizeof(*zn)+level*sizeof(struct zskiplistLevel));
    // 设置节点的分数
    zn->score = score;
    // 设置节点的元素
    zn->ele = ele;
    return zn;
}


#define ZSKIPLIST_MAXLEVEL 64 /* Should be enough for 2^64 elements */
#define ZSKIPLIST_P 0.25      /* Skiplist P = 1/4 */
// 随机层数生成算法
int zslRandomLevel(void) {
    int level = 1;
    while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))
        level += 1;
    return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}

...

Ziplist -> Skiplist 转换阈值可通过zset-max-ziplist-entrieszset-max-ziplist-value配置。(下文zadd源码部分涉及这两个参数)

常用操作时间复杂度

Zscore

时间复杂度 O(1)

核心查找函数:

(1)压缩列表(ziplist)查找 :需要遍历压缩列表(默认 ≤ 128 个元素)查找成员member,复杂度是O(N)。但是此处 N 为常数且元素很少,因此也可以视为时间复杂度 O(1)

(2)跳跃表(skiplist)查找:使用字典dict作为成员member到分数score的映射,dictFind 时间复杂度 O(1)

c 复制代码
int zsetScore(robj *zobj, sds member, double *score) {
    if (!zobj || !member) return C_ERR;

    if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
        // 压缩列表编码的情况
        if (zzlFind(zobj->ptr, member, score) == NULL) return C_ERR;
    } else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {
        // 跳跃表编码的情况
        zset *zs = zobj->ptr;
        dictEntry *de = dictFind(zs->dict, member);
        if (de == NULL) return C_ERR;
        *score = *(double*)dictGetVal(de);
    } else {
        serverPanic("Unknown sorted set encoding");
    }
    return C_OK;
}

Zadd

时间复杂度 O(log N)

主要处理流程:

(1)若集合不存在,先创建集合对象 压缩列表 ,若不满足压缩列表条件则创建 跳跃表;

(2)添加成员和分数(核心函数 zsetAdd)。

c 复制代码
void zaddCommand(client *c) {
    zaddGenericCommand(c,ZADD_NONE);
}
/* This generic command implements both ZADD and ZINCRBY. */
void zaddGenericCommand(client *c, int flags) {
    // 解析和处理参数...
    
    // 获取或创建有序集合对象
    zobj = lookupKeyWrite(c->db,key);
    if (zobj == NULL) {
        if (xx) goto reply_to_client; // +XX 选项但key不存在
        
        if (server.zset_max_ziplist_entries == 0 ||
            server.zset_max_ziplist_value < sdslen(c->argv[scoreidx+1]->ptr))
        {
            // 创建跳跃表编码的集合
            // (不满足 zset_max_ziplist_entries 和 zset_max_ziplist_value)
            zobj = createZsetObject();
        } else {
            // 创建压缩列表编码的集合
            zobj = createZsetZiplistObject();
        }
        dbAdd(c->db,key,zobj);
    } else {
        if (zobj->type != OBJ_ZSET) {
            addReply(c,shared.wrongtypeerr);
            goto cleanup;
        }
    }

    // // 处理每个元素
    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);
        
        // ...处理返回值
    }
    
    // ...返回响应
}

核心添加逻辑(zsetAdd):

(1)压缩列表处理:查找/插入/更新(先删除后插入)的时间复杂度都为O(N);

(2)跳跃表处理:查找元素时间复杂度O(1) (字典直接返回),插入/更新(先删除后插入)的时间复杂度都为O(log N),更新时若分数相同时间复杂度O(1);

c 复制代码
int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore) {
    // ...

    /* (1)压缩列表ziplist处理 */
    if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
        unsigned char *eptr;
        
        // (1.1) 元素存在,处理更新分数, zzlFind查找时间复杂度 O(N)
        if ((eptr = zzlFind(zobj->ptr,ele,&curscore)) != NULL) {
            /* NX 参数处理,已存在直接返回 */
            if (nx) {
                *flags |= ZADD_NOP;
                return 1;
            }

            if (incr) {
                // incr 分数处理
            }

            /* 更新分数 先删除,后插入 */
            if (score != curscore) {
                zobj->ptr = zzlDelete(zobj->ptr,eptr);
                zobj->ptr = zzlInsert(zobj->ptr,ele,score);
                // ...
            }
            return 1;
        } 
        // (1.2) 元素不存在,插入新元素
        else if (!xx) {
            // 检查是否超过压缩列表限制 
            // (不满足 zset_max_ziplist_entries 和 zset_max_ziplist_value)
            if (zzlLength(zobj->ptr)+1 > server.zset_max_ziplist_entries ||
                sdslen(ele) > server.zset_max_ziplist_value ||
                !ziplistSafeToAdd(zobj->ptr, sdslen(ele)))
            {
                // 转换为跳跃表
                zsetConvert(zobj,OBJ_ENCODING_SKIPLIST);
            } else {
                // 插入新元素到压缩列表(保持有序)
                zobj->ptr = zzlInsert(zobj->ptr,ele,score);
                // ...
            }
        } 
        // ...
    }
    
    /* (2) 跳跃表skiplist处理 */
    if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {
        // ...

        // 查找元素是否已存在(通过字典O(1)查找)
        de = dictFind(zs->dict,ele);
        // (2.1) 元素存在,处理更新分数
        if (de != NULL) {
            /* NX 参数,已存在直接返回 */
            if (nx) {
                // ...
                return 1;
            }
            curscore = *(double*)dictGetVal(de);

            if (incr) {
                // incr 分数处理
            }

            /* Remove and re-insert when score changes. */
            if (score != curscore) {
                // 先删除,再插入
                znode = zslUpdateScore(zs->zsl,curscore,ele,score);
                /* 更新字段分数score */
                dictGetVal(de) = &znode->score; /* Update score ptr. */
                // ...
            }
            
            // 分数相同,无需更新
            return 1;
        } 
        // (2.2)元素不存在,插入到跳跃表和字典
        else if (!xx) {
            ele = sdsdup(ele);
            // 插入跳跃表
            znode = zslInsert(zs->zsl,score,ele);
            // 插入字典
            serverAssert(dictAdd(zs->dict,ele,&znode->score) == DICT_OK);
            // ...
            return 1;
        } 
        // ...
    } else {
        serverPanic("Unknown sorted set encoding");
    }
    return 0; /* Never reached. */
}

zskiplistNode *zslUpdateScore(zskiplist *zsl, double curscore, sds ele, double newscore) {
    // ...  
    zslDeleteNode(zsl, x, update);
    zskiplistNode *newnode = zslInsert(zsl,newscore,x->ele);
    // ...
    return newnode;
}

Zrem

时间复杂度 O(log N)

主要处理流程:

(1)若集合不存在,直接返回;

(2)删除成员(核心删除逻辑 zsetDel);

(3)若集合为空,删除 key

c 复制代码
void zremCommand(client *c) {
    // ...

    // 查找有序集合对象
    if ((zobj = lookupKeyWriteOrReply(c,key,shared.czero)) == NULL ||
        checkType(c,zobj,OBJ_ZSET)) return;

    // 遍历所有要删除的元素
    for (j = 2; j < c->argc; j++) {
        // 移除元素
        if (zsetDel(zobj,c->argv[j]->ptr)) deleted++;
        // 元素已清空,把集合也移除
        if (zsetLength(zobj) == 0) {
            dbDelete(c->db,key);
            // ...
        }
    }
   // ...
}

核心删除逻辑(zsetDel):

时间复杂度主要 = 查找 + 删除,

(1)压缩列表删除:需要移动后续元素填补被删除元素的空间,时间复杂度 O(N) + O(N) = O(N)

(2)跳跃表删除:O(1) + O(log N) = O(log N)

c 复制代码
int zsetDel(robj *zobj, sds ele) {
    // 压缩列表处理,时间复杂度 查找 O(N) + 删除重建 O(N)
    if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
        unsigned char *eptr;

        if ((eptr = zzlFind(zobj->ptr,ele,NULL)) != NULL) {
            zobj->ptr = zzlDelete(zobj->ptr,eptr);
            return 1;
        }
    } 
    // 跳跃表处理,时间复杂度 字典元素移除 O(1) + 删除 (O(log N))
    else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {
        zset *zs = zobj->ptr;
        dictEntry *de;
        double score;
        
        de = dictUnlink(zs->dict,ele);
        if (de != NULL) {
            /* Get the score in order to delete from the skiplist later. */
            score = *(double*)dictGetVal(de);

            /* Delete from the hash table and later from the skiplist.
             * Note that the order is important: deleting from the skiplist
             * actually releases the SDS string representing the element,
             * which is shared between the skiplist and the hash table, so
             * we need to delete from the skiplist as the final step. */
            dictFreeUnlinkedEntry(zs->dict,de);

            // 从跳跃表删除 (O(log N))
            int retval = zslDelete(zs->zsl,score,ele,NULL);
            // ...
            return 1;
        }
    } else {
        serverPanic("Unknown sorted set encoding");
    }
    return 0; /* No such element found. */
}

Zrange / Zrevrange

时间复杂度 :返回 K个元素(K = end -start + 1)
(1) 压缩列表:O(start + K) / O(N)
(2) 跳跃表:O(log N + K)
(3) 获取头部/末尾节点元素:O(1)

命令:

c 复制代码
void zrangeCommand(client *c) {
    zrangeGenericCommand(c,0);
}

void zrevrangeCommand(client *c) {
    zrangeGenericCommand(c,1);
}

通用范围命令实现 (zrangeGenericCommand):

分为压缩列表和跳跃表处理,主要流程一致,

(1) 根据 start,end 参数,确定遍历范围;

(2) 根据 start 获取起始元素;

(3) 顺序访问遍历返回[start, end]范围内的所有元素;

c 复制代码
void zrangeGenericCommand(client *c, int reverse) {
    robj *key = c->argv[1];
    robj *zobj;
    int withscores = 0;
    PORT_LONG start;
    PORT_LONG end;
    PORT_LONG llen;
    PORT_LONG rangelen;

    // 1. 解析校验 start 、end
    if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != C_OK) ||
        (getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != C_OK)) return;
    
    // 2. 检查 WITHSCORES 选项
    if (c->argc == 5 && !strcasecmp(c->argv[4]->ptr,"withscores")) {
        withscores = 1;
    } 
    // ...

    // 3. 查找有序集合对象,不存在直接返回
    if ((zobj = lookupKeyReadOrReply(c,key,shared.emptymultibulk)) == NULL
         || checkType(c,zobj,OBJ_ZSET)) return;

    // 4. 处理负数索引 :从尾部开始
    llen = zsetLength(zobj);
    if (start < 0) start = llen+start;
    if (end < 0) end = llen+end;
    if (start < 0) start = 0;

    // 5. 校验范围 [start, end]
    if (start > end || start >= llen) {
        // ...
        return;
    }
    if (end >= llen) end = llen-1;
    // rangelen 为遍历范围长度,后续需要依次遍历
    rangelen = (int)(end-start)+1;                                              
    
    // ...

    // 6. 根据编码方式处理 ------ 压缩列表处理
    if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
        unsigned char *zl = zobj->ptr;
        unsigned char *eptr, *sptr;
        unsigned char *vstr;
        unsigned int vlen;
        PORT_LONGLONG vlong;

        // 定位起始元素,时间复杂度 O(start)
        if (reverse)
            eptr = ziplistIndex(zl,(int)(-2-(2*start)));     
            // ...                   
        else
            eptr = ziplistIndex(zl,(int)(2*start)); 
            // ...                             

        // 遍历返回[start, end]范围内的元素 ,时间复杂度 O(rangelen)
        serverAssertWithInfo(c,zobj,eptr != NULL);
        sptr = ziplistNext(zl,eptr);
        while (rangelen--) {
            serverAssertWithInfo(c,zobj,eptr != NULL && sptr != NULL);
            // 获取元素值
            serverAssertWithInfo(c,zobj,ziplistGet(eptr,&vstr,&vlen,&vlong));
            if (vstr == NULL)
                addReplyBulkLongLong(c,vlong);
            else
                addReplyBulkCBuffer(c,vstr,vlen);

            // zzlGetScore 获取分数 (O(1))
            if (withscores)
                addReplyDouble(c,zzlGetScore(sptr));

            // 移动到下一个元素, prev or next
            if (reverse)
                zzlPrev(zl,&eptr,&sptr);
            else
                zzlNext(zl,&eptr,&sptr);
        }

    } 
    // 6. 根据编码方式处理 ------ 跳跃表处理
    else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {
        zset *zs = zobj->ptr;
        zskiplist *zsl = zs->zsl;
        zskiplistNode *ln;
        sds ele;

        // 定位起始节点 , 时间复杂度 O(log N) ,若为头节点或尾节点则是 O(1)
        if (reverse) {
            // 获取尾节点
            ln = zsl->tail;
            // start > 0 表示非尾节点,需要继续查找
            if (start > 0)
                ln = zslGetElementByRank(zsl,llen-start);
        } else {
            // 获取尾头结点
            ln = zsl->header->level[0].forward;
            // start > 0 表示非头节点,需要继续查找
            if (start > 0)
                ln = zslGetElementByRank(zsl,start+1);
        }

        // 遍历返回[start, end]范围内的元素 ,时间复杂度 O(rangelen)
        while(rangelen--) {
            // ...
            ele = ln->ele;
            addReplyBulkCBuffer(c,ele,sdslen(ele));
            // 获取分数 (O(1))
            if (withscores)
                addReplyDouble(c,ln->score);
            // 移动到下一个元素, backward or forward    
            ln = reverse ? ln->backward : ln->level[0].forward;
        }
    } else {
        serverPanic("Unknown sorted set encoding");
    }
}

ZrangeByScore / ZrevrangeByScore

时间复杂度 :
(1) 压缩列表:O(N)
(2) 跳跃表:O(log N + K + M), K为遍历元素数 + LIMIT offset 跳过元素数
(3) 获取头部/末尾节点元素:O(1)

命令:

c 复制代码
void zrangebyscoreCommand(client *c) {
    genericZrangebyscoreCommand(c,0);
}

void zrevrangebyscoreCommand(client *c) {
    genericZrangebyscoreCommand(c,1);
}

通用实现 (genericZrangebyscoreCommand):

c 复制代码
/* This command implements ZRANGEBYSCORE, ZREVRANGEBYSCORE. */
void genericZrangebyscoreCommand(client *c, int reverse) {
    zrangespec range;
    robj *key = c->argv[1];
    robj *zobj;
    PORT_LONG offset = 0, limit = -1;
    int withscores = 0;
    PORT_ULONG rangelen = 0;
    void *replylen = NULL;
    int minidx, maxidx;

    /* Parse the range arguments. */
    if (reverse) {
        /* Range is given as [max,min] */
        maxidx = 2; minidx = 3;
    } else {
        /* Range is given as [min,max] */
        minidx = 2; maxidx = 3;
    }

    // 1. 解析分数范围 (O(1))
    if (zslParseRange(c->argv[minidx],c->argv[maxidx],&range) != C_OK) {
        addReplyError(c,"min or max is not a float");
        return;
    }

    // 2. 解析可选参数 [WITHSCORES] [LIMIT offset count] 
    /* Parse optional extra arguments. Note that ZCOUNT will exactly have
     * 4 arguments, so we'll never enter the following code path. */
    if (c->argc > 4) {
        int remaining = c->argc - 4;
        int pos = 4;

        while (remaining) {
            // 解析可选参数 [WITHSCORES]
            if (remaining >= 1 && !strcasecmp(c->argv[pos]->ptr,"withscores")) {
                pos++; remaining--;
                withscores = 1;
            } 
            // 2. 解析可选参数 [LIMIT offset count] 
            else if (remaining >= 3 && !strcasecmp(c->argv[pos]->ptr,"limit")) {
                // offset count
                if ((getLongFromObjectOrReply(c, c->argv[pos+1], &offset, NULL)
                        != C_OK) ||
                    (getLongFromObjectOrReply(c, c->argv[pos+2], &limit, NULL)
                        != C_OK))
                {
                    return;
                }
                pos += 3; remaining -= 3;
            } 
            // ...
        }
    }

    // 3. 查找有序集合对象
    if ((zobj = lookupKeyReadOrReply(c,key,shared.emptymultibulk)) == NULL ||
        checkType(c,zobj,OBJ_ZSET)) return;

    // 4. 根据编码处理 - 压缩列表处理
    if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
        unsigned char *zl = zobj->ptr;
        unsigned char *eptr, *sptr;
        unsigned char *vstr;
        unsigned int vlen;
        PORT_LONGLONG vlong;
        double score;

        // 根据分数范围,定位找到第一个符合条件的元素,时间复杂度 O(N)
        if (reverse) {
            eptr = zzlLastInRange(zl,&range);
        } else {
            eptr = zzlFirstInRange(zl,&range);
        }

        if (eptr == NULL) {
            // ...
            return;
        }

        /* Get score pointer for the first element. */
        serverAssertWithInfo(c,zobj,eptr != NULL);
        // 继续顺序遍历范围内的元素,时间复杂度 O(K)
        sptr = ziplistNext(zl,eptr);
        
        // 处理LIMIT - offset 逻辑, 跳过到下一个元素 prev or next
        while (eptr && offset--) {
            if (reverse) {
                zzlPrev(zl,&eptr,&sptr);
            } else {
                zzlNext(zl,&eptr,&sptr);
            }
        }

        // 处理LIMIT - count 逻辑, 返回limit个元素
        while (eptr && limit--) {
            // 分数
            score = zzlGetScore(sptr);

            /* 遍历到分数不在范围内,结束遍历 */
            if (reverse) {
                if (!zslValueGteMin(score,&range)) break;
            } else {
                if (!zslValueLteMax(score,&range)) break;
            }

            /* We know the element exists, so ziplistGet should always succeed */
            serverAssertWithInfo(c,zobj,ziplistGet(eptr,&vstr,&vlen,&vlong));

            rangelen++;
            if (vstr == NULL) {
                addReplyBulkLongLong(c,vlong);
            } else {
                addReplyBulkCBuffer(c,vstr,vlen);
            }

            // 处理 withscores
            if (withscores) {
                addReplyDouble(c,score);
            }

            // 移动到下一个节点
            if (reverse) {
                zzlPrev(zl,&eptr,&sptr);
            } else {
                zzlNext(zl,&eptr,&sptr);
            }
        }
    } 
    // 4. 根据编码处理 - 跳跃表处理
    else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {
        zset *zs = zobj->ptr;
        zskiplist *zsl = zs->zsl;
        zskiplistNode *ln;

         // 1. 定位分数范围内的第一个节点,时间复杂度 O(log N)
        if (reverse) {
            ln = zslLastInRange(zsl,&range);
        } else {
            ln = zslFirstInRange(zsl,&range);
        }

        /* No "first" element in the specified interval. */
        if (ln == NULL) {
            addReply(c, shared.emptymultibulk);
            return;
        }
        
        // ...

        // 处理LIMIT - offset 逻辑, 跳过到下一个元素 prev or next
        while (ln && offset--) {
            if (reverse) {
                ln = ln->backward;
            } else {
                ln = ln->level[0].forward;
            }
        }
        
        // 遍历 limit 个元素
        while (ln && limit--) {
            /* 遍历到分数不在范围内,结束遍历 */
            if (reverse) {
                if (!zslValueGteMin(ln->score,&range)) break;
            } else {
                if (!zslValueLteMax(ln->score,&range)) break;
            }

            rangelen++;
            addReplyBulkCBuffer(c,ln->ele,sdslen(ln->ele));

            // 处理 withscores
            if (withscores) {
                addReplyDouble(c,ln->score);
            }

            /* 移动到下一个节点  backward or forward */
            if (reverse) {
                ln = ln->backward;
            } else {
                ln = ln->level[0].forward;
            }
        }
    } 
    // ...

    if (withscores) {
        rangelen *= 2;
    }

    setDeferredMultiBulkLength(c, replylen, rangelen);
}

Zcard

时间复杂度 O(1)

命令:

c 复制代码
void zcardCommand(client *c) {
    robj *key = c->argv[1];
    robj *zobj;

    if ((zobj = lookupKeyReadOrReply(c,key,shared.czero)) == NULL ||
        checkType(c,zobj,OBJ_ZSET)) return;

    addReplyLongLong(c,zsetLength(zobj));
}

PORT_ULONG zsetLength(const robj *zobj) {
    PORT_ULONG length = 0;
    if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
        // 压缩列表编码
        length = zzlLength(zobj->ptr);
    } else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {
        // 跳跃表编码
        length = (PORT_ULONG) ((const zset*)zobj->ptr)->zsl->length;                         WIN_PORT_FIX /* cast (PORT_ULONG) */
    } else {
        serverPanic("Unknown sorted set encoding");
    }
    return length;
}

(1) 压缩列表(ziplist):

  • ziplist在头部维护了节点数量的字段 zlbyteszltailzllen
  • zllen 直接记录了当前包含的节点数量(元素member + 分数score 属于一对,会连续占用2个节点)
  • 获取长度只需读取固定位置的内存值;
c 复制代码
unsigned int zzlLength(unsigned char *zl) {
    // 在有序集合的 ziplist 编码中,每个元素存储为两个连续的 ziplist 节点,因此实际元素个数要 总节点数量/2
    return ziplistLen(zl)/2;
}

// ziplist.h
/* Return the length of a ziplist, or UINT16_MAX if the length cannot be
 * determined without scanning the whole ziplist. */
#define ZIPLIST_LENGTH(zl)      (*((uint16_t*)((zl)+sizeof(uint32_t)*2)))

(2) 跳跃表(skiplist):从zskiplist 结构定义中看到,长度可以直接通过 zsl->length 获取;

c 复制代码
typedef struct zskiplist {
    
    struct zskiplistNode *header, *tail;
    
    // 维护当前节点总数
    PORT_ULONG length;
    
    int level;
} zskiplist;
相关推荐
Asthenia04129 分钟前
Pandas全面操作指南与电商销售数据分析
后端
松韬9 分钟前
Spring + Redisson:从 0 到 1 搭建高可用分布式缓存系统
java·redis·分布式·spring·缓存
姜威鱼17 分钟前
蓝桥杯python编程每日刷题 day 21
数据结构·算法·蓝桥杯
绝顶少年21 分钟前
Spring Boot 注解:深度解析与应用场景
java·spring boot·后端
天上掉下来个程小白24 分钟前
Redis-14.在Java中操作Redis-Spring Data Redis使用方式-操作列表类型的数据
java·redis·spring·springboot·苍穹外卖
孪生质数-35 分钟前
SQL server 2022和SSMS的使用案例1
网络·数据库·后端·科技·架构
uhakadotcom40 分钟前
AWS Lightsail 简介与实践
后端·面试·github
振鹏Dong42 分钟前
MySQL 事务底层和高可用原理
数据库·mysql
·云扬·44 分钟前
深度剖析 MySQL 与 Redis 缓存一致性:理论、方案与实战
redis·mysql·缓存