redis 数据结构「ziplist 存储结构」

563 阅读2分钟

这是我参与8月更文挑战的第9天,活动详情查看:8月更文挑战

ziplistredis 为了节约内存而实现的顺序数据结构。它采取变长编码,将数据+类型+编码信息存储在连续内存中。

先从存储说起:

存储

  1. 字节数组,一段 连续的内存

  2. 压缩 ⇒ 整体结构就要足够紧凑:

    • header ⇒ [zlbytes, zltail, zllen][字节长度,尾部元素偏移量,元素个数]
    • body data ⇒ [entry1, entry2, ...]
    • tailer ⇒ 尾部元素,标记结束,恒为 0xFF

大致的结构如下图:

image.png

主要关注 header 的组成:

  1. zlbytes <32 bit> ⇒ 占用总字节数
  2. zltail <32 bit> ⇒ 最后一个 entry 在整个 ziplist 的偏移量【存在价值:方便直接遍历到最后一项】→ ZIPLIST_TAIL_OFFSET
  3. zllen <16 bit>entry 数量。因为只有 16 bit,如果超过表达范围,那就只能遍历 ziplist 才能知道总计数

我们来看一个例子:

image.png

总结一下,这个 ziplist 存储了 4 个元素,这个可以在 zllen 得出。具体存储内容看后续的 entries

  1. "name" → string
  2. "tielei" → string
  3. "age" → string
  4. 20 → int

所以这里就知道表达一个 entry 的 3 个结构的含义:

  • pre_entry_lenth ⇒ 前一项 entry 占用的 总字节数
  • encoding+len ⇒ 编码以及实际内容占用长度【编码:string, int, int16, int65...
  • content ⇒ 具体内容

操作流程

上面看到的是实际存储的结构,这个也对应前面说的连续内存。但是这个结构不适合操作,为什么呢?

  1. 每次都需要计算前一个元素长度,存储类型,存储内容
  2. 以上的过程都需要经过复杂的解码计算
  3. 实际上我们获取的是一个 char *p ,但是在程序中需要处理每个部分的编码,自然需要一个 struct 承接计算的结果

所以为了可以在操作中更为方便,可以将解码的接过缓存到一个固定的结构体中。源码中实际操作的结构体是:zlentry

typedef struct zlentry {
    unsigned int prevrawlensize;    // 编码 prevrawlen 所需的字节大小 
    unsigned int prevrawlen;        // 前置节点的长度
    unsigned int lensize;           // 编码 len 所需的字节大小
    unsigned int len;               // 当前节点值的长度
    unsigned int headersize;        // 当前节点 header 的大小, 等于prevrawlensize + lensize
    unsigned char encoding;         // 当前节点值所使用的编码类型
    unsigned char *p;               // 指向当前节点的指针,也就是内存entry的prelen字段
} zlentry;

我们看看解码的过程,这个也涉及到后续的计算过程:

// p 指向一段内存开始,然后按照特定的编码 -> 解码成 e 这个entry
static inline void zipEntry(unsigned char *p, zlentry *e) {
    ZIP_DECODE_PREVLEN(p, e->prevrawlensize, e->prevrawlen);
    ZIP_ENTRY_ENCODING(p + e->prevrawlensize, e->encoding);
    ZIP_DECODE_LENGTH(p + e->prevrawlensize, e->encoding, e->lensize, e->len);
    assert(e->lensize != 0); /* check that encoding was valid. */
    e->headersize = e->prevrawlensize + e->lensize;
    e->p = p;
}
  1. 解码 previous_entry_length 字段
  2. 解码 encoding 字段
  3. 解码 lensize 字段

这个过程和上述讲 entry 结构基本一致。