redis中的zskiplist

本文深入解析Redis中跳跃表(zskiplist)的数据结构与实现原理。介绍跳跃表节点的构成,包括robj对象、节点分数(score)等,并详细说明如何通过zslCreate创建新的跳跃表实例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

redis中通过zskiplist 来表示一个跳跃表
typedef struct zskiplist {

    // 跳跃表的表头和表尾
    struct zskiplistNode *header, *tail;

    // 表示有跳跃表中有几个节点
    unsigned long length;

    // 跳跃表最大的层数
    int level;

} zskiplist;

//跳跃表的单个节点的数据结构如下所示:
typedef struct zskiplistNode {

    // 具体结构在后面
    robj *obj;
    // 节点的分值
    double score;
    struct zskiplistNode *backward;
    struct zskiplistLevel {

        // 前向指针
        struct zskiplistNode *forward;

        // 跨度
        unsigned int span;
    } level[];

} zskiplistNode;
#robj 结构体采用了c语言中的位域,为了节省内存
typedef struct redisObject {
 
    unsigned type:4;
    unsigned encoding:4;
    unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
     int refcount;
    void *ptr;

} robj;

从跳跃表的数据结构可以看出跳跃表是几个跳跃节点组成的list,组成跳跃表的节点之前根据里跳跃表的远近采取分层的方式,也就是所谓的level
本质上说跳跃表也是一种链表,其操作也就是链表的那几种常用操作。
zslCreate 用于新建一个新的跳跃表
zskiplist *zslCreate(void) {
    int j;
    zskiplist *zsl;

    // 分配空间
    zsl = zmalloc(sizeof(*zsl));

    // 初始化levl和length
    zsl->level = 1;
    zsl->length = 0;

	// 申请表头节点
    zsl->header = zslCreateNode(ZSKIPLIST_MAXLEVEL,0,NULL);
    for (j = 0; j < ZSKIPLIST_MAXLEVEL; j++) {
	#表头节点赋值,这里ZSKIPLIST_MAXLEVEL 等于32 ,可见跳跃表最多有32个子表.这里一次性创建了32 个子表,然后分别最着32个子表初始化
        zsl->header->level[j].forward = NULL;
        zsl->header->level[j].span = 0;
    }
    zsl->header->backward = NULL;

    // 表尾赋值为null
    zsl->tail = NULL;

    return zsl;
}

zskiplistNode *zslCreateNode(int level, double score, robj *obj) {
    
    // 根据level 分配空间,可见跳跃表在创建的时候就已经申请了可以支持的最大内存空间
    zskiplistNode *zn = zmalloc(sizeof(*zn)+level*sizeof(struct zskiplistLevel));

    // 这里的obj 为null,score 为0
    zn->score = score;
    zn->obj = obj;

    return zn;
}

### Redis 中 ZSet 的实现原理 #### 1. ZSet 的定义 ZSet 是 Redis 提供的一种有序集合数据结构,它允许存储键值对形式的元素,并通过分数(score)来维护这些元素的顺序。如果两个元素具有相同的分数,则会根据其成员名称进行字典序排序[^2]。 --- #### 2. 底层数据结构 Redis 使用两种不同的底层编码方式来表示 ZSet 数据结构:`ziplist` 和 `skiplist+dict`。这两种编码的选择取决于 ZSet 的大小以及配置参数: - **当 ZSet 较小时**(默认情况下,元素数量小于等于 128 并且每个成员长度不超过 64 字节),Redis 会选择更紧凑的 `ziplist` 编码。 - **当 ZSet 较大时** 或者超出上述条件限制时,Redis 则切换到更为高效的 `skiplist+dict` 结构[^3]。 --- #### 3. Skiplist+Dict 的具体实现 在 `skiplist+dict` 模式下,ZSet 主要由两部分组成: - **Hash Table (Dictionary)** Hash 表用于快速查找某个成员是否存在及其对应的分数。它的作用类似于索引机制,能够以 O(1) 时间复杂度完成查询操作。 - **Skip List (跳跃表)** 跳跃表负责维持所有成员按分数从小到大的顺序排列。即使存在大量重复分数的情况,跳跃表仍然能保持高效的操作性能,平均时间复杂度为 O(log N)[^4]。 以下是两者的关系描述: - Dict 对象中的 key 是成员本身,而 value 存储的是该成员的分数。 - SkipList 对象则是一个双向链表加多级指针构成的数据结构,其中每个节点包含成员、分数以及指向其他节点的链接。 --- #### 4. 源码分析 以下是 Redis 中关于 ZSet 的核心源码片段解析: ##### (1)ZSet 节点定义 ```c typedef struct zskiplistNode { sds ele; // 成员字符串 double score; // 分数值 struct zskiplistNode *backward; // 向前回溯的指针 struct zskiplistLevel { struct zskiplistNode *forward; // 下一层前进方向上的指针 unsigned long span; // 当前层跨越的距离 } level[]; } zskiplistNode; ``` 这段代码展示了单个 ZSet 节点的具体字段含义。每个节点都包含了成员名 (`ele`) 及对应分数 (`score`),并通过多个层次连接形成完整的跳跃表。 --- ##### (2)Skiplist 定义 ```c typedef struct zskiplist { struct zskiplistNode *header, *tail; // 首尾节点 unsigned long length; // 总结点数 int level; // 最高层级数目 } zskiplist; ``` 此结构体封装了一个完整的跳跃表实例,记录了头部和尾部位置、总节点计数以及当前最高层级的信息。 --- #### 5. 插入过程概述 当向 ZSet 添加新元素时,Redis 执行以下步骤: 1. 如果目标成员已存在于哈希表中,则更新其分数并调整跳跃表相应位置; 2. 若不存在,则创建新的节点并将之插入至合适的地方,同时同步修改 hash table 和 skip list 的状态[^4]。 --- #### 6. 查询效率评估 由于采用了双保险设计——即既有基于散列函数构建的 dictionary 支持随机访问需求,又有经过精心优化后的 skiplist 来满足范围扫描场景的要求,因此无论是在增删改查还是遍历方面,ZSet 都表现出极高的运行效能。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值