-- 《Redis设计与实现》



ziplist .c



数据结构概述说明:



- ziplist是 列表list 或 hash 的底层实现之一,只有数据量小的时候才会用,举个例子如下:



-- hash结构:键值均<64字节 && 键值对数量 < 512



-  目的是为了节省内存,在数据量少的时候就省内存。由一系列特殊编码连续内存组成的顺序性数据结构



- 时间复杂度O(N),只有极端情况下会造成O(N^2)                // 比如插入或删除极端情况导致的连锁更新



-- 比如原本用一个字节能存储0~254字节长度,但是这时候将一个长度大于254字节的插入,这时候就会导致所有都要重新分配。



- 可保存多个节点,节点数据结构可以是字节数组 或 整数值



 



其核心结如下: 




Redis压缩列表ziplist的实现_ziplist


占用的内存字节数。进行内存 重分配或计算 zlend位置用到。


压缩列起始地址到列表尾结点偏移量。举个例子:zlbyte地址为p,zltail=60,那entryN的地址(p+60)


列表节点数量


- entryX:长度不定。列表对应的节点


标记压缩列表尾端


其节点entry结构如下 :


typedef struct   zlentry {   // 
  保存 
  ziplist 
  节点信息的结构
  
    unsigned int   prevrawlensize;   //
  前置节点的长度
  
    unsigned int   prevrawlen;   //
  编码 
  prevrawlen 
  所需的字节大小
  
    unsigned int   lensize;   //
  编码 
  len 
  所需的字节大小
  
    unsigned int   len;   //
  当前节点值的长度
  
    unsigned int   headersize;   // 
  当前节点 
  header 
  的大小
  ,
  等于 
  prevrawlensize + lensize
  
    unsigned char   encoding;   // 
  当前节点值所使用的编码类型
  
    unsigned char   *p;   // 
  指向当前节点的指针
  
} zlentry;


content部分:


每个节点可以保存一个字节数组或整数值(content部分)


- 字节数组长度有如下三种


-- 长度 <= 63(2^6)-1


-- 长度 <= 16383(2^14)-1


-- 长度 <= 4294967295(2^32)-1


 


- 整数值有如下六种


-- 4bit(0~12无符号整数)、1字节、3字节、int16_t整数、int32_t整数、int64_t整数


==========================================================================


previous_entry_length部分:


其中previous_entry_length(下面会简写为 prevLen )记录了前一个节点的长度情况


- 分为如下两种情况


-- 前一entry节点长度 < 254字节:prevLen=1字节,前一节点长度保存在这1字节里。举例如下:


---- 0x05表示0000 0101,说明上一节点长度=5字节


 


-- 前一entry字节长度 >= 254字节:prevLen=5字节,第一个字节设置为0xFE,后面4个节点保存前一entry节点长度。举例如下:


---- 0xFE 00 00 27 66,0xFE表示前一节点>=254字节,00 00 27 66=10086字节表示前一节点为10086字节长


 


如何从尾节点遍历到头节点?步骤如下


- 1 假设zlbytes起始地址为p,zltail为60,有三个entry节点。那最后一个entry3节点起始地址=p+60 = p3。


- 2 由于prevLen记录上一个节点长度,那entry2 = p3  - prevLen3记录前一节点的长度 = p2


- 3 那entry1 = p2 - prevLen3记录前一节点的长度 = p1


以上就从尾遍历到头节点



Redis压缩列表ziplist的实现_原理分析_02


===================================================================


encoding部分:


记录entry节点中content长度、数据类型。含两种数据结构 字节数组、整数如下:


-  字节数组:1字节-00开头,2字节-01开头,5字节-10开头



Redis压缩列表ziplist的实现_原理分析_03


-  整数:均是1字节-11xxxxxx开头



Redis压缩列表ziplist的实现_Redis_04


==========================================================================


content部分:


保存节点的值,可以是字节数组 或 整数


-  字节数组:00001011,其中00表示长度小于63字节数组,001011=11长度



Redis压缩列表ziplist的实现_Redis_05


-  整数:11000000,其中11表示保存int16_t整数



Redis压缩列表ziplist的实现_数据结构_06


 


什么是压缩列表连锁更新?


什么情况下会触发连锁更新?


添加或删除节点都可能会导致连锁更新,不过可能性都比较小


 


举例:添加可能导致的连锁更新是如何发生的?


有连续多个长度介于250~253字节的e1~eN节点。



Redis压缩列表ziplist的实现_数据结构_07


这时候在头节点插入一个大于254字节的新节点。由于e1长度<1字节,所以他的previous_entry_length只有1字节,是无法记录前一个节点的大小的,这时候prevLen会扩容成5个字节(就够存储大于254字节的前一个节点了)。而后面e2~eN都是这种情况,这时候都需要更新,就会导致连锁更新的情况了。



Redis压缩列表ziplist的实现_Redis_08


为什么是250~253字节之间了??


因为 250 + (5 - 1) = 254 >= 254,其实就是刚好上面e1扩容了4个字节,导致e2的prevLen无法存储e1的长度,从而导致e2也要更新。


 


总结


- 连锁更新复杂度O(n^2),但出现几率低,并且出现了因为压缩列表不长所以不影响(平时几个节点连锁更新也不影响性能)


- 连锁更新触发条件:多个连续长度介于250~253字节之间