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

「这是我参与2022首次更文挑战的第37天,活动详情查看:2022首次更文挑战」。

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

image-20220220153530869.png

数据结构如下: image-20220220155346028.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-20220220162511826.png

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

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

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

list-compress-depth 0 
复制代码

image-20220220162306674.png

  • 0 是一个特殊值,表示都不压缩,这个是 redis 的默认值。
  • 1 表示 quicklist 两端各有1个节点不压缩,中间节点压缩
  • 2 表示 quicklist 两端各有2个节点不压缩,中间节点压缩
  • 3 表示 quicklist 两端各有3个节点不压缩,中间节点压缩

以此类推

  1. recompress

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

可以看看插入过程

image-20220220163236870.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

Supongo que te gusta

Origin juejin.im/post/7067565444869128228
Recomendado
Clasificación