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数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
10天前
|
索引 Python
List(列表)
List(列表)。
18 4
|
24天前
|
存储 消息中间件 NoSQL
Redis数据结构:List类型全面解析
Redis数据结构——List类型全面解析:存储多个有序的字符串,列表中每个字符串成为元素 Eelement,最多可以存储 2^32-1 个元素。可对列表两端插入(push)和弹出(pop)、获取指定范围的元素列表等,常见命令。 底层数据结构:3.2版本之前,底层采用**压缩链表ZipList**和**双向链表LinkedList**;3.2版本之后,底层数据结构为**快速链表QuickList** 列表是一种比较灵活的数据结构,可以充当栈、队列、阻塞队列,在实际开发中有很多应用场景。
|
23天前
|
JavaScript 数据管理 虚拟化
ArkTS List组件基础:掌握列表渲染与动态数据管理
在HarmonyOS应用开发中,ArkTS的List组件是构建动态列表视图的核心。本文深入探讨了List组件的基础,包括数据展示、性能优化和用户交互,以及如何在实际开发中应用这些知识,提升开发效率和应用性能。通过定义数据源、渲染列表项和动态数据管理,结合虚拟化列表和条件渲染等技术,帮助开发者构建高效、响应式的用户界面。
158 2
|
1月前
|
NoSQL 关系型数据库 MySQL
Redis 列表(List)
10月更文挑战第16天
18 2
|
1月前
|
消息中间件 存储 监控
redis 的List类型 实现 排行榜
【10月更文挑战第8天】
37 2
|
16天前
|
存储 NoSQL Java
Redis命令:列表模糊删除详解
通过本文的介绍,我们详细探讨了如何在Redis中实现列表的模糊删除。虽然Redis没有直接提供模糊删除命令,但可以通过组合使用 `LRANGE`和 `LREM`命令,并在客户端代码中进行模糊匹配,来实现这一功能。希望本文能帮助你在实际应用中更有效地操作Redis列表。
29 0
|
24天前
|
存储 NoSQL Redis
Redis常见面试题:ZSet底层数据结构,SDS、压缩列表ZipList、跳表SkipList
String类型底层数据结构,List类型全面解析,ZSet底层数据结构;简单动态字符串SDS、压缩列表ZipList、哈希表、跳表SkipList、整数数组IntSet
|
5月前
|
安全 Java
java线程之List集合并发安全问题及解决方案
java线程之List集合并发安全问题及解决方案
946 1
|
4月前
|
Java API Apache
怎么在在 Java 中对List进行分区
本文介绍了如何将列表拆分为给定大小的子列表。尽管标准Java集合API未直接支持此功能,但Guava和Apache Commons Collections提供了相关API。
|
4月前
|
运维 关系型数据库 Java
PolarDB产品使用问题之使用List或Range分区表时,Java代码是否需要进行改动
PolarDB产品使用合集涵盖了从创建与管理、数据管理、性能优化与诊断、安全与合规到生态与集成、运维与支持等全方位的功能和服务,旨在帮助企业轻松构建高可用、高性能且易于管理的数据库环境,满足不同业务场景的需求。用户可以通过阿里云控制台、API、SDK等方式便捷地使用这些功能,实现数据库的高效运维与持续优化。
下一篇
无影云桌面