redis 数据结构「ziplist 操作流程」

328 阅读2分钟

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

插入流程

image.png

上图是 ziplist 初始化的情况:

只有 zlbytes + zltail + zllen 初始化出来,结尾处加一个 ZIP_END 。返回 char *zl ⇒ 连续内存的头指针。


初始化完毕,我们拿着 ziplist 首地址,开始插入新的 entry

unsigned char *ziplistPush(unsigned char *zl, unsigned char *s, unsigned int slen, int where) {
    unsigned char *p;
		// p 为插入点,是一个精确的地址值:分头插/尾插
    p = (where == ZIPLIST_HEAD) ? ZIPLIST_ENTRY_HEAD(zl) : ZIPLIST_ENTRY_END(zl);
    return __ziplistInsert(zl,p,s,slen);
}

// first_entry_ptr = zl(首地址) + header_size
#define ZIPLIST_ENTRY_HEAD(zl)  ((zl)+ZIPLIST_HEADER_SIZE)

// last_entry_ptr = zl(首地址) + zltail_ptr(尾部entry偏移量)
#define ZIPLIST_ENTRY_TAIL(zl)  ((zl)+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)))

// tail_entry_ptr = zltail_ptr = zl(首地址) + zlbytes_size
#define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t)))) 

可以看的出来:__ziplistInsert 是实际插入执行函数,支持插入任意节点位置:

  1. ziplistentry 为空时,不存在前一个元素,即:pre_entry_length = 0
  2. 新的 entry 作为最后一个节点插入时,即:p2 → 直接偏移到最后一个 entry

具体可以看下图。

__ziplistInsert

简述一下步骤,不然会迷失在偏移计算当中:

image.png

  • 将元素内容编码

    1. 传入的 zl 头指针【需要找到最后一个 tail_entry 的头指针位置,这个和 **p 插入点** 不一样】

    2. 根据传入的位置,计算 pre_entry_length

      1. ziplist 为空,pre_entry_length = 0
      2. p1ZIP_DECODE_PREVLEN() 直接获取上一个 entry 编码所需的长度
      3. p2 → 判断规则:p[0] == ZIP_END ⇒ 通过 zl 头指针找到最后一个 entry ,计算这个 tail_entry 长度
    3. 紧接处理 数据 encoding

    4. 最终确定当前元素所需空间大小 ⇒ reqlen

  • 将元素插入 ziplist,重新分配内存空间

Hash

hash 是开发者存储一个对象结构比较理想的数据结构。一个对象的各个属性,正好对应 <field, value>

hash 底层存储会随着数据量的增大而发生变化:

  • field 比较少,value所占空间也比较小时 ⇒ ziplist
  • field 增多,value 空间增大 ⇒ dict