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

Redis Zset的底层秘密:跳表(Skip List)的精妙设计

在Redis众多数据结构中,有序集合(Zset)是一个非常强大的工具,它不仅支持集合的唯一性,还能根据分数进行自动排序。当我们使用zaddzrangezrank等命令时,背后正是一个高效的数据结构在支撑——跳表(Skip List)。今天,我们就来深入探索Redis中Zset的底层实现,重点剖析跳表这一精妙的数据结构。

什么是跳表?

跳表是一种基于链表的数据结构,它通过在链表中添加多级索引,实现快速的查找、插入和删除操作。想象一下,普通的链表查找需要O(n)的时间复杂度,而跳表则通过在不同层级上"跳跃",将时间复杂度降低到O(log n)。

跳表的核心思想是:在链表的基础上,构建多级索引。每一级索引都包含当前层级的节点,通过这些索引,我们可以快速跳过大量节点,直接定位到目标区域。

跳表的结构

Redis中的跳表实现非常精巧,它由zskiplistNode结构体组成:

typedef struct zskiplistNode {sds ele;                // 成员字符串double score;           // 分数值struct zskiplistLevel {struct zskiplistNode *forward; // 指向下一节点unsigned int span;          // 跨越节点数} level[];                  // 动态数组,表示不同层级的连接
} zskiplistNode;

每个节点包含:

  • ele:成员字符串
  • score:分数值
  • level[]:动态数组,表示该节点在不同层级的连接

跳表还包含一个zskiplist结构体,用于管理整个跳表:

typedef struct zskiplist {struct zskiplistNode *header, *tail;unsigned long length;int level;
} zskiplist;

跳表的工作原理

查找操作

跳表的查找过程类似于二分查找:

  1. 从最高层开始
  2. 比较当前节点的分数
  3. 如果当前节点的分数小于目标分数,则继续向后查找
  4. 如果当前节点的分数大于目标分数,则向下一层
  5. 重复直到找到目标节点或到达最低层
zskiplistNode *zslGetNode(zskiplist *zsl, double score, sds ele) {zskiplistNode *x = zsl->header;for (int i = zsl->level-1; i >= 0; i--) {while (x->level[i].forward && x->level[i].forward->score < score) {x = x->level[i].forward;}}x = x->level[0].forward;if (x && score == x->score && sdslen(ele) == sdslen(x->ele) && memcmp(ele, x->ele, sdslen(ele)) == 0) {return x;}return NULL;
}

插入操作

插入操作包含两个关键步骤:

  1. 查找插入位置
  2. 创建新节点并更新索引

Redis使用概率算法决定新节点的层数,确保较高的层数较少,平衡空间和效率:

int zslRandomLevel() {int level = 1;while ((random() & 0xFFFF) < ZSKIPLIST_P * 0xFFFF)level += 1;return (level < ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}

删除操作

删除操作与插入类似,先找到节点,然后从所有层级中移除该节点。

为什么Redis选择跳表而非红黑树或B+树?

Redis的作者Salvatore Sanfilippo曾明确表示:“跳跃表更易于实现、调试和扩展”。这背后有多个原因:

1. 实现简单

跳表的代码实现比红黑树简单得多。红黑树需要处理复杂的旋转、颜色标记和平衡操作,而跳表只需要简单的指针操作。

2. 内存效率

在Redis的内存环境中,跳表的平均每个元素占用64字节,而红黑树平均需要72字节(包含父指针和颜色标记)。对于内存敏感的Redis来说,这是重要的优势。

3. 缓存友好

跳表的连续内存访问模式比红黑树的指针跳转更缓存友好,现代CPU架构下性能更优。

4. 区间查询效率

跳表在区间查询时效率更高。它可以在O(log n)时间内定位到区间的起点,然后在原始链表中线性遍历,而红黑树需要遍历所有节点。

5. 并发友好

跳表在并发环境下可以通过改变索引构建策略,有效平衡执行效率和内存消耗。红黑树的平衡依赖于复杂的旋转操作。

跳表与哈希表的结合

Redis的Zset不仅使用跳表,还配合哈希表一起工作:

  • 跳表:用于存储数据的排序和快速查找
  • 哈希表:用于存储成员->分数的映射,提供快速查找

这种设计使得Zset既能高效进行范围查询(通过跳表),又能高效进行单点查询(通过哈希表)。

何时使用压缩列表?

Redis在Zset元素数量较少时,会使用压缩列表(ziplist)来节省内存:

  • 元素个数 ≤ 128
  • 每个元素的成员名和分数长度 ≤ 64字节

当不满足上述条件时,Redis会切换到跳表+哈希表的实现。

实际应用:Redis Zset的高效性能

在实际应用中,跳表的特性使Redis Zset在以下场景表现出色:

  1. 排行榜系统:快速获取前N名或特定分数范围内的成员
  2. 带优先级的任务队列:根据优先级排序任务
  3. 实时分析:按分数范围查询统计

例如,一个热门游戏的排行榜系统,使用Zset可以轻松实现:

# 添加玩家分数
ZADD leaderboard 1000 "player1"
ZADD leaderboard 1500 "player2"# 获取前3名
ZRANGE leaderboard 0 2 WITHSCORES

总结

跳表是Redis Zset底层实现的核心数据结构,它的设计体现了Redis作者对简单性、效率和实用性的追求。通过多级索引,跳表在O(log n)的时间复杂度下实现了快速的查找、插入和删除操作,同时保持了实现的简洁性。

Redis的Zset之所以能成为如此强大的数据结构,正是因为它巧妙地结合了跳表的有序性和哈希表的快速查找能力。这种设计不仅满足了Redis对高性能的要求,还保持了代码的简洁和可维护性。

在Redis的世界里,跳表就像一位默默工作的"幕后英雄",以优雅而高效的方式支撑着无数应用的有序集合需求。理解跳表的工作原理,不仅能帮助我们更好地使用Redis,也能让我们欣赏到数据结构设计的精妙之处。

http://www.dtcms.com/a/438023.html

相关文章:

  • 广州金融网站建设2017网站开发语言排名
  • C++ priority_queue优先级队列
  • Kafka 授权与 ACL 深入实践
  • 西宁市住房和城乡建设局网站做一个个人网站
  • 瑞安做网站多少钱东莞网站建设找谁
  • 谷歌云+Apache Airflow,数据处理自动化的强力武器
  • 小红书自动化运营:智能体+RPA自动化+MCP实现采集仿写和自动发布
  • 网站域名和网站网址建筑培训网 江苏
  • 定制开发开源AI智能名片S2B2C商城小程序的会员制运营研究——以“老铁用户”培养为核心目标
  • 【aigc】chrome-devtools-mcp怎么玩?
  • 从《Life of A Pixel》来看Chrome的渲染机制
  • 【项目实战 Day9】springboot + vue 苍穹外卖系统(用户端订单模块 + 商家端订单管理模块 完结)
  • Mac 安装Neo4j教程
  • blender 解决shift快捷键和中英切换重复的问题
  • 网站动态图怎么做阳明拍卖公司网站
  • 01_Docker 部署 Ollama 模型(支持 NVIDIA GPU)
  • 苏州新区网站制作wordpress视频格式
  • 一位Android用户的科技漫游手记
  • android中调用相册
  • 安卓基础组件031-Retrofit 网络请求框架
  • Redis 黑马点评-商户查询缓存
  • Android geckoview 集成,JS交互,官方demo
  • 【APK安全】Android 权限校验核心风险与防御指南
  • 单调队列与单调栈
  • 设计与优化Java API:构建高效、可维护的接口
  • Locality Sensitive Hashing (LSH) 详解:高效检测语言语句重复的利器
  • 阿里云网站开发零起步如何做设计师
  • 后端开发基础概念MVC以及Entity,DAO,DO,DTO,VO等概念
  • 七大排序算法的基本原理
  • Gateway-过滤器