ziplist 的组成结构
为了节约内存,Redis 开发了 ziplist 数据结构。一个压缩列表包含任意多个节点entry,每个entry 可以保存1个字节数组,或1个整数值。
- zlbytes:整个压缩列表占用的字节数;
- zltail:尾节点相对列表起始地址的偏移量,等于 尾节点起始地址 - 压缩列表起始地址;
- zllen:节点 entry 的个数,当列表节点总数大于 65535 时,及超过2字节能表示的最大值时,需要遍历整个列表才能算出真实数量;
- entry:列表节点,长度不定,由节点保存的内容决定;
- zlend:特殊值 0xFF 用于标记列表的末尾,相当与 C 语言用 ‘\0’ 表示字符串的结束符。
entry 的构成
压缩列表的节点由 previous_entry_length、encoding、content 三部分构成。
- previous_entry_length:记录前一个节点的长度。可以是 1字节 也可以是 5字节(类似 DB 中的 VARCHAR)。如果前一节点长度小于 254 字节,就是 1 字节;否则为 5 字节;
- encoding:表示 content 所保存的数据类型(字节数组 或 整数)及长度。最高位以 00、01、10 开头,表示字节数组;以 11 开头表示整数编代码,具体见下面两图:
连锁更新
由于每个节点的 previous_entry_length 保存的是前一个节点的长度,如果前一个节点长度小于 254 时,previous_entry_length 占用 1 字节,否则使用 5 字节。
假如列表中 entry 长度均为 253,现在要插入一个长度为 256 的节点 entryX,这时保存 entryX 的 previous_entry_length 不能用1字节了,需要扩展到 5 字节。相应的后续 entry 里的 previous_entry_length 长度均要升级。这就叫做连锁更新,实际上,在删除节点时也可能遇到。
连锁更新,在最坏情况下要对 ziplist 进行 N次空间重分配操作,每次空间重分配最坏的复杂度为 O(N) ,所以连锁更新最坏的复杂度为 O(N^2)。但现实中遇到该情况的概率非常低,ziplistPush 等命令的平均复杂度为 O(N)。