Redis 源码分析列表对象(z_list)

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: Redis 源码分析列表对象(z_list)

新版 redis 的 list 实际上只有一种数据结构 quicklist ,而且是一种双向链表, 代码如下:


image.png


数据结构如下:


image.png


我们再来看看源码:


/* quicklistNode is a 32 byte struct describing a listpack for a quicklist.
 * We use bit fields keep the quicklistNode at 32 bytes.
 * count: 16 bits, max 65536 (max lp bytes is 65k, so max count actually < 32k).
 * encoding: 2 bits, RAW=1, LZF=2.
 * container: 2 bits, PLAIN=1, PACKED=2.
 * recompress: 1 bit, bool, true if node is temporary decompressed for usage.
 * attempted_compress: 1 bit, boolean, used for verifying during testing.
 * extra: 10 bits, free for future use; pads out the remainder of 32 bits */
typedef struct quicklistNode {
    // 双向链表前驱节点
    struct quicklistNode *prev;
    // 双向链表的后节点
    struct quicklistNode *next;
    // 不设置压缩数据参数 recompres 指向一个 ziplist 结构
    // 不设置压缩数据参数 recompres 指向一个 quicklistLZF 结构
    unsigned char *entry;
    // 压缩列表 ziplist 的总长度
    size_t sz;             /* entry size in bytes */
    // 每个 ziplist 中 entry 的个数
    unsigned int count : 16;     /* count of items in listpack */
    // 表示是否采用了 LZF 压缩 quickList 节点 1 表示压缩过,2 表示没有压缩站 2bit 长度
    unsigned int encoding : 2;   /* RAW==1 or LZF==2 */
    // 表示是否开启 ziplist 进行压缩
    unsigned int container : 2;  /* PLAIN==1 or PACKED==2 */
    // 表示该节点是否被压缩过
    unsigned int recompress : 1; /* was this node previous compressed? */
    // 测试使用
    unsigned int attempted_compress : 1; /* node can't compress; too small */
    // 额外拓展位,占 10bit 长度
    unsigned int extra : 10; /* more bits to steal for future usage */
} quicklistNode;
/* 
  当指定使用 lzf 压缩算法压缩 ziplist entry 节点时
  quicklistNode 结构的 zl 成员执行 quicklistLZF 结构
  quicklistLZF is a 8+N byte struct holding 'sz' followed by 'compressed'.
 * 'sz' is byte length of 'compressed' field.
 * 'compressed' is LZF data with total (compressed) length 'sz'
 * NOTE: uncompressed length is stored in quicklistNode->sz.
 * When quicklistNode->entry is compressed, node->entry points to a quicklistLZF */
typedef struct quicklistLZF {
    // 表示被LZF 压缩后的 ziplist 的大小
    size_t sz; /* LZF size in bytes*/
    // 压缩有的数据,柔性数组
    char compressed[];
} quicklistLZF;


quicklist 结构体定义:


/* quicklist is a 40 byte struct (on 64-bit systems) describing a quicklist.
 * 'count' is the number of total entries.
 * 'len' is the number of quicklist nodes.
 * 'compress' is: 0 if compression disabled, otherwise it's the number
 *                of quicklistNodes to leave uncompressed at ends of quicklist.
 * 'fill' is the user-requested (or default) fill factor.
 * 'bookmarks are an optional feature that is used by realloc this struct,
 *      so that they don't consume memory when not used. */
typedef struct quicklist {
    // 链表表头
    quicklistNode *head;
    // 链表表尾巴
    quicklistNode *tail;
    // 所有 quicklistNode 节点中所有的 entry 个数
    unsigned long count;        /* total count of all entries in all listpacks */
    // quickListNode 节点个数,也就是 quickList 的长度
    unsigned long len;          /* number of quicklistNodes */
    //单个节点的填充因子,也就是 ziplist 的大小
    signed int fill : QL_FILL_BITS;       /* fill factor for individual nodes */
    // 保存压缩成都只,配置文件设置,64位操作系统占 16bit , 6 表示压缩
    unsigned int compress : QL_COMP_BITS; /* depth of end nodes not to compress;0=off */
    unsigned int bookmark_count: QL_BM_BITS;
    quicklistBookmark bookmarks[];
} quicklist;


为什么这样设计?


  • 双向链表,插入和删除效率高,但是对内存不友好,而且需要存取额外的前后两个指针


  • 数组代表的是连续的内存存储空间,插入和删除时间复杂度高,但是对内存友好(符合局部性原理),因此综合了两个, 产生了 quicklist 数据结构,其实在 C++ 的 stl 中的 deque 也是种设计模式。


思考: 如何设置来均衡链表长度和数组(ziplist)为代表的比例呢?我们来考虑一下两者极端情况,如果链表长度为 1 那么就退化成一个 ziplist 了 ,此时可能是找不到一块很大的连续内存而导致 OOM;如果ziplist 为1 , 就退化成双向链表了,此时对内存是非常不友好的,而且会造成大量的内存碎片,影响性能?对于处理方案,看我们后面的具体分析。


核心参数


  1. fill : 16bit ,控制 ziplist 大小,存放 list-max-ziplist-size 参数值。


image.png


可以看出正值表示 quicklist 上 ziplist 的 entry 个数


  1. compress: 16bit 节点压缩深度值,存放 list-compress-depth 参数值。

由于当前数据很多, 容易被访问的可能是两端的数据,中间的数据访问频率比较低(访问起来性能也比较低),如果应用场景符合这个特点,那么list 还提供了一个选项,能够把中间的数据节点进行压缩,从而进一步节省内存空间。


list-compress-depth 0 


image.png


  • 0 是一个特殊值,表示都不压缩,这个是 redis 的默认值。


  • 1 表示 quicklist 两端各有1个节点不压缩,中间节点压缩


  • 2 表示 quicklist 两端各有2个节点不压缩,中间节点压缩


  • 3 表示 quicklist 两端各有3个节点不压缩,中间节点压缩


以此类推


  1. recompress


执行 index 等操作会对节点进行暂时解压缩, recompress 表示后某个时刻在进行压缩。


可以看看插入过程


image.png


更具 AllowInsert 判断是否需要新增节点


列表特征



  1. 阻塞和非阻塞


list 的部分操作支持阻塞和非阻塞。 重点看阻塞--- 当所给定的 key 不存在,或者 key 中包含的是 空列表, 那么 blrpop 命令就会被阻塞链接, 知道另外一个 client 对这些 key 执行 「LR」PSUH 命令将一个新的数据在任意的 key 列表中, 那么这个命令解除调用BLPOP或者 BRPOP la命令 clent 的阻塞状态 。


其实很简单,判断是否超时,或者有数据,否则不返回就可以了。


使用场景



  • 常见数据结构


  • lpush + lpop = Stack (栈)


  • lpush + rpop = Queue (队列)


  • lpush + ltrim = Capped Collection (有限集合)


  • lpush + brpop = MessageQueue (消息队列)


  • 文章列表


常见命令


image.png



相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
相关文章
|
4月前
|
存储 NoSQL 算法
Redis设计与实现——数据结构与对象
Redis 是一个高性能的键值存储系统,其数据结构设计精妙且高效。主要包括以下几种核心数据结构:SDS、链表、字典、跳跃表、整数集合、压缩列表。此外,Redis 对象通过类型和编码方式动态转换,优化内存使用,并支持引用计数、共享对象和淘汰策略(如 LRU/LFU)。这些特性共同确保 Redis 在性能与灵活性之间的平衡。
|
6月前
|
人工智能 Java
Java 中数组Array和列表List的转换
本文介绍了数组与列表之间的相互转换方法,主要包括三部分:1)使用`Collections.addAll()`方法将数组转为列表,适用于引用类型,效率较高;2)通过`new ArrayList&lt;&gt;()`构造器结合`Arrays.asList()`实现类似功能;3)利用JDK8的`Stream`流式计算,支持基本数据类型数组的转换。此外,还详细讲解了列表转数组的方法,如借助`Stream`实现不同类型数组间的转换,并附带代码示例与执行结果,帮助读者深入理解两种数据结构的互转技巧。
259 1
Java 中数组Array和列表List的转换
|
9月前
|
C语言 Python
[oeasy]python054_python有哪些关键字_keyword_list_列表_reserved_words
本文介绍了Python的关键字列表及其使用规则。通过回顾`hello world`示例,解释了Python中的标识符命名规则,并探讨了关键字如`if`、`for`、`in`等不能作为变量名的原因。最后,通过`import keyword`和`print(keyword.kwlist)`展示了Python的所有关键字,并总结了关键字不能用作标识符的规则。
145 9
|
9月前
|
数据挖掘 大数据 数据处理
python--列表list切分(超详细)
通过这些思维导图和分析说明表,您可以更直观地理解Python列表切分的概念、用法和实际应用。希望本文能帮助您更高效地使用Python进行数据处理和分析。
209 14
|
9月前
|
数据挖掘 大数据 数据处理
python--列表list切分(超详细)
通过这些思维导图和分析说明表,您可以更直观地理解Python列表切分的概念、用法和实际应用。希望本文能帮助您更高效地使用Python进行数据处理和分析。
467 10
|
10月前
|
索引 Python
List(列表)
List(列表)。
174 4
|
10月前
|
存储 消息中间件 NoSQL
Redis数据结构:List类型全面解析
Redis数据结构——List类型全面解析:存储多个有序的字符串,列表中每个字符串成为元素 Eelement,最多可以存储 2^32-1 个元素。可对列表两端插入(push)和弹出(pop)、获取指定范围的元素列表等,常见命令。 底层数据结构:3.2版本之前,底层采用**压缩链表ZipList**和**双向链表LinkedList**;3.2版本之后,底层数据结构为**快速链表QuickList** 列表是一种比较灵活的数据结构,可以充当栈、队列、阻塞队列,在实际开发中有很多应用场景。
|
10月前
|
JavaScript 数据管理 虚拟化
ArkTS List组件基础:掌握列表渲染与动态数据管理
在HarmonyOS应用开发中,ArkTS的List组件是构建动态列表视图的核心。本文深入探讨了List组件的基础,包括数据展示、性能优化和用户交互,以及如何在实际开发中应用这些知识,提升开发效率和应用性能。通过定义数据源、渲染列表项和动态数据管理,结合虚拟化列表和条件渲染等技术,帮助开发者构建高效、响应式的用户界面。
656 2
|
10月前
|
存储 NoSQL Java
Redis命令:列表模糊删除详解
通过本文的介绍,我们详细探讨了如何在Redis中实现列表的模糊删除。虽然Redis没有直接提供模糊删除命令,但可以通过组合使用 `LRANGE`和 `LREM`命令,并在客户端代码中进行模糊匹配,来实现这一功能。希望本文能帮助你在实际应用中更有效地操作Redis列表。
322 0
|
10月前
|
存储 NoSQL Redis
Redis常见面试题:ZSet底层数据结构,SDS、压缩列表ZipList、跳表SkipList
String类型底层数据结构,List类型全面解析,ZSet底层数据结构;简单动态字符串SDS、压缩列表ZipList、哈希表、跳表SkipList、整数数组IntSet