本文剖析redis的ziplist的实现。
ziplist是一个存储高效的双链表,存储的元素类型有字符串和整数;虽然存储高效,但每次插入或删除ziplist中的元素都会引起重新分配内存,所以,ziplist作为大型只读表非常高效,频繁的插入或删除ziplist不太合适;
ziplist的内部结构:
<zlbytes><zltail><zllen><entry><entry><zlend>
- zlbytes是一个unsigned int数字,表示整个ziplist占用的字节数;
- zltail是最后一个元素的偏移量,也是unsigned int;
- zllen是unsigned short数字,存储ziplist的元素个数,如果个数超过0xFFFF,需要遍历list才能得到元素个数;
- entry就是元素本身;
- zlend是0xFF,特殊标记;
entry内部结构:
<prelen><curlen><body>
- prelen表示上一个元素的大小;
- curlen表示当前元素的大小;
- body存储元素内容;
prelen有两种存储方式:
- 小于254时,直接用一个字节存储;
- 大于或等于254时,用五个字节存储,第一个字节存储254,接下来四个字节存储prelen;
curlen有六种存储方式:
元素为字符串类型:
- 字符串长度小于或等于0xFFFFFF(63),用一个字节表示,|00pppppp|,00是type,pppppp是值;
- 字符串长度大于0xFFFFFF(63)小于或等于0xFFFFFFFFFFFFFF(16383),用两个字节表示,|01pppppp|qqqqqqqq|,01是type,ppppppqqqqqqqq是值;
- 字符串长度大于0xFFFFFFFFFFFFFF(16383),用五个字节表示,|10______|qqqqqqqq|rrrrrrrr|ssssssss|tttttttt|,10是type,后面四个字节是值;
元素为整数类型:
- 一个字节表示,|1100____|表示整数用int16_t存储,隐含值为2;
- 一个字节表示,|1101____|表示整数用int32_t存储,隐含值为4;
- 一个字节表示,|1110____|表示整数用int64_t存储,隐含值为8;
因为ziplist是紧凑的存储方式,所有东西都存放在连续的内存中,所以,插入删除元素特别费劲,需要重新分配内存;
插入元素:
对字符串元素尝试压缩为整数;
需要的内存量:lensize(prevlen) + lensize(curlen) + curlen + nextdiff;
对于下一个entry来说,前一个entry发生变化,需要更新prelen;
因为prelen采用变长存储,所以lensize(prelen)可能发生变动,这样会导致下一个entry自身的大小也发生变化;
这个更新可能会继续下去直到链尾或者收敛;
nextdiff的含义就是下一个entry的大小变化情况,值为:<调整后大小> - <调整前大小>;
当nextdiff为0时,调整收敛;
删除元素:
元素被删除,需要move后续元素以保持内存紧凑;
对被删除元素的下一个entry来说,可能需要更新prelen,这个更新也是级联的;
元素的插入和删除都需要更新zltail;