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

详情Redis的Zset结构

一、什么是 ZSet?

ZSet(Sorted Set)是 Redis 提供的一种兼具SetHash特性的数据结构。

  • 像 Set 一样:它内部的成员(member)是唯一的,不允许重复。这使得它天然支持去重。

  • 像 Hash 一样:每个成员都会关联一个分数(score),这是一个双精度的浮点数(double)。

  • 核心特性:ZSet 中的所有成员会按照这个 score 进行从小到大的排序。正因为这个排序特性,它可以实现非常多的排行榜类业务场景。

简单来说,ZSet 就是一个有序的、去重的字符串集合,排序的依据是每个字符串对应的分数。

二、ZSet 的常见使用场景

正是因为score + 排序的特性,ZSet 的应用场景非常广泛:

  • 排行榜:这是最经典的应用。

    • 游戏排行榜:玩家分数作为 score,玩家ID作为 memberZREVRANGE 命令可以轻松获取前N名。

    • 热搜榜:热搜词的热度作为 score,关键词作为 member。每次点击事件执行 ZINCRBY 命令增加热度。

  • 带权重的队列

    • 将任务的执行时间戳作为 score,任务内容作为 member。消费者使用 ZRANGEBYSCORE 查询当前时间戳之前的所有任务来处理,实现延迟队列。

  • 范围查找

    • 例如,统计年收入在 20W 到 50W 之间的用户。将收入作为 score,用户ID作为 member,可以快速通过 ZRANGEBYSCORE 命令获取。

  • 优先级系统

    • 不同用户或任务有不同的优先级,用 score 表示,系统可以优先处理高优先级(score 小或大)的项目。

三、ZSet 的底层实现

这是理解 ZSet 性能的关键。Redis 为了在内存使用和性能之间取得平衡,为 ZSet 设计了两种内部编码(底层数据结构),会根据一定的规则自动切换:

  • ziplist(压缩列表)/ listpack (紧凑列表)

  • skiplist(跳跃表) + dict(字典)

3.1 ziplist和listpack有什么区别

ziplist 和 listpack 都是Redis设计的用于存储字符串元素的紧凑型数据结构,旨在节省内存。listpack 被设计用来取代 ziplist,是其现代化的继承者。

特性ziplist (压缩列表)listpack (紧凑列表)优势
设计目标节省内存的双向链表节省内存、更安全的单向列表更简单、更安全
遍历方式双向(前向和后向)单向(仅前向)实现简化
关键缺陷级联更新无级联更新性能稳定,无最坏情况
节点结构[prevlen][encoding][data][encoding][data][backlen]解耦,自包含
内存顺序头部 -> 尾部头部 -> 尾部基本相同
现状逐渐被废弃新一代的默认实现代表未来

3.2 为什么redis使用listpack替换ziplist

  • 核心缺陷:级联更新 (Cascade Update)

    • ziplist的级联更新问题:

      • 原因: ziplist的每个节点都包含一个 prevlen 字段,用于记录前一个节点的长度,以便实现反向遍历。

      • 问题触发: 当一个新的节点被插入或一个已有节点被更新,导致其长度发生变化时,它后面的节点的 prevlen 字段可能需要更新。

      • 连锁反应: 如果后一个节点因为 prevlen 变化(例如从1字节变为5字节)而导致自身长度也发生变化,那么再后面的节点又需要更新。在最坏情况下,会导致一连串的节点都需要重新分配空间和复制数据,性能从O(1)退化到O(N²)。虽然实际中发生概率不高,但一旦发生对性能影响很大。

    • listpack的解决之道:

      • 消除根源: listpack完全移除了 prevlen 字段,因此一个节点的长度变化绝不会影响后续的节点

      • 实现方式: 它将表示节点长度的字段 backlen(称为“反向长度”或“条目长度”)放在了节点的末尾。并且这个 backlen 记录的是自身节点的长度,而不是前一个节点的。

      • 结果: 任何插入、删除、修改操作都只影响操作点附近的有限节点,性能稳定在O(N),没有最坏情况下的性能灾难。

  • 节点结构 (Entry Structure):

    • ziplist节点:[prevlen][encoding][data]

      • prevlen:前一个节点的长度。长度可变(1字节或5字节)。

      • encoding:编码方式,标识数据的类型和长度。

      • data:实际存储的数据。

    • listpack节点:[encoding][data][backlen]

      • encoding:编码方式,标识数据的类型和长度。

      • data:实际存储的数据。

      • backlen自身节点(从encoding开始到backlen结束)的总长度。长度可变(1-5字节)。

    • listpack结构的优势在于“自包含”。要解析一个listpack,只需要从前往后或从后往前读取即可。要找到上一个节点,不需要知道前一个节点的任何信息,只需通过当前节点的 backlen 跳转到上一个节点的起始位置。这种设计使得节点之间完全解耦。

  •  遍历方式:

    • ziplist: 由于有 prevlen,它可以非常高效地进行双向遍历(从头到尾或从尾到头)。

    • listpack: 由于没有指向前一个节点的直接指针,反向遍历相对麻烦一些。需要通过当前节点的 backlen 来计算前一个节点的起始位置,然后向前跳转。因此,它的反向遍历效率低于ziplist

    • Redis的作者认为,在绝大多数使用场景下,需要的是正向遍历或随机访问(通过索引),反向遍历的需求很少。用一点点反向遍历的性能损失,换来整个结构的稳定性和简单性,是非常值得的交易。

  • 对比项:

    对比项ziplistlistpack
    设计哲学功能优先(双向遍历),容忍缺陷稳健优先(消除缺陷),牺牲次要功能
    关键问题存在级联更新风险无级联更新,性能稳定
    复杂度实现复杂,难以维护实现更简单,更健壮
    适用性已过时,逐渐被废弃现代选择,是Redis当前和未来的默认配置

3.3 skiplist

        跳跃表(SkipList) 是Redis中另一个核心数据结构,它与ziplist/listpack的设计目标和应用场景完全不同。如果说ziplist/listpack是为了节省内存而设计的紧凑型、线性结构,那么跳跃表就是为了实现高效范围查询而设计的索引型、多层结构。

  • 核心概念:

    • 跳跃表(SkipList)的本质是在普通有序链表的基础上,添加了多级索引来加速查找。它可以看作是一种“以空间换时间”的算法。

      想象一下《新华字典》:

    • 普通链表:一页一页翻着找,效率是O(N)。

    • 跳跃表:

      • 第一级索引:拼音首字母目录(比如A, B, C...章)

      • 第二级索引:每个字母章内部的更细分类(比如A章又分为A, Ai, An...节)

      • 第三级索引:...直到最后的详细页码(数据本身)

    • 通过这种多级“目录”,你可以快速跳过大量不需要的页面,使查找效率逼近二分查找。

3.4 结构转换

当一个有序集合满足以下条件时,会使用跳跃表+字典dict)的组合来实现:

  • zset-max-listpack-entries 128

    • 含义:当有序集合中的元素(成员)数量小于或等于128个时,会使用listpack来存储。

    • 为什么:对于少量元素,listpack这种紧凑的、连续的内存结构非常节省内存,且效率足够高。

    • 超过时:如果元素数量大于128,Redis会自动将底层实现从listpack转换为跳跃表(skiplist) + 字典(dict) 的组合。

  • zset-max-listpack-value 64

    • 含义:当有序集合中任何一个成员(member)的字符串长度小于或等于64字节时,会使用listpack来存储。

    • 为什么:较短的字符串可以高效地编码并存储在listpack中。

    • 超过时:如果任何一个成员的字符串长度大于64字节(即使总元素数量很少),Redis也会自动将底层实现转换为跳跃表 + 字典

两个条件是“或”的关系。只要违反其中任意一个,有序集合的底层编码就会升级为跳跃表。

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

相关文章:

  • STM32 之BMP280的应用--基于RTOS的环境
  • React学习教程,从入门到精通, ReactJS - 优点与缺点(5)
  • 学习stm32 窗口看门狗
  • 鸿蒙ArkUI 基础篇-12-List/ListItem-界面布局案例歌曲列表
  • Shell脚本命令扩展
  • 回归问题的损失函数
  • 06.《STP 基础原理与配置详解》
  • 学习python第14天
  • Spark mapGroups 函数详解与多种用法示例
  • 神经网络正则化三重奏:Weight Decay, Dropout, 和LayerNorm
  • 嵌入式硬件电路分析---AD采集电路
  • pyqt5的简单开发可视化界面的例子
  • 【重学 MySQL】九十三、MySQL的字符集的修改与底层原理详解
  • Linux学习----归档和传输文件实用指南
  • java报错问题解析
  • 在 MyBatis 中oracle基本数值类型的 JDBC 类型映射
  • Let‘s Encrypt证书自动续期
  • 【数据分享】上市公司-创新投入、研发投入数据(2007-2022)
  • 【Python 后端框架】总结
  • Transformer:从入门到精通
  • 第二十六天-待机唤醒实验
  • 【GaussDB】在逻辑复制中剔除指定用户的事务
  • Java动态代理
  • Redis-基数统计、位图、位域、流
  • ⚡ Linux find 命令参数详解
  • 【开题答辩全过程】以 云智课为例,包含答辩的问题和答案
  • SQL语言增删改查之C与R
  • 05MySQL多表查询全解析
  • Flutter 跨平台开发环境搭建指南
  • Spring Boot 后端接收多个文件的方法