redis的压缩列表(ziplist)是列表键和哈希键的底层实现之一。当一个列表键只包含少量列表项,并且每个列表项要么就是小整数值,要么就是长度比较短的字符串,那么Redis就会使用压缩列表来做列表键的底层实现。这个在redis配置文件里有几个配置选项。
hash-max-zipmap-entries 512 //entry的总数不能大于512,否则就采用hashmap/链表/跳跃表结构存储
hash-max-zipmap-value 64 //每个entry值的长度不能大于64个字节,
list-max-ziplist-entries 512
list-max-ziplist-value 64
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
上面的几个选项都可以调整,压缩表的存在也是为了提升性能,毕竟是一个内存存储数据。
ziplist结构
一个压缩表的结构由以下几个部分构成:
zlbytes | zltail | zllen | entry | entry | ... | zlend |
结构 | 类型 | 布局说明 |
---|---|---|
zlbytes | uint32_t (4个字节) | ziplist总字节数 |
zltail | uint32_t (4个字节) | 列表中最后一个entry元素起始距离的偏移量,也就是距离开始由多少个字节 |
zllen | uint16_t (2个自己 | 列表里有多少个entry,当有超过216-2个条目,此值设置为216-1,超过的话需要遍历整个ziplist才知道 |
entry | 列表里的每一个存储的值 | |
zlend | uint8_t(1个字节) | 表示ziplist结尾的一个特殊值,编码为单字节等于255 |
压缩表中的每个entry都大体上包含以下3个部分:
上一个entry的长度 | 编码 | 数据(0-12小的整数时没有) |
---|---|---|
prevlen | encoding | entry-data |
-
prevlen存储是为了能够逆序遍历,有了上一个。
-
entry编码是为了区分字符串和整数,如果是整数(0-12),值也是存储在编码中的。
-
entry-data 这个部分存储字符串类型的值。
prevlen 上一个entry长度
如果entry的长度如果小于254个字节,它只占用一个单字节表示长度为8个位的无符号数,当长度大约或等于254时,占用5个字节,第一个字节是设置为254(fe)作为一个标记,代表后边有一个大的值,其余4个字节表示上一个entry长度的值。看起来大概会是这样
小于254个字节 | ||
---|---|---|
prevlen | encoding | entry-data |
或者
0xFE(标记)+prevlen(4个字节的数代表entry真正的长度) | ||
---|---|---|
prevlen | encoding | entry-data |
但这有个问题,如果已有的压缩表中每个元素的长度都小于254个字节,现在在表头添加一个元素entry,并且这个元素长度大于了254,那么这个时候一个单字节明显就存储不下,需要5个字节存储,那后边的每个元素都需要重新分配存储空间,除了更新,删除也是因为每个元素都会存储上一个元素的长度,所以同样存在这种问题。
虽然看起来每次都要重新分配存储空间很浪费性能,其实不然,首先压缩表这种数据结构本身存储的数据就很少,其次也得有很多连续存在的元素的长度都是小于254个字节的才有可能引发更新,再者更新也是针对需要重新分配空间的节点,不多的情况下也不会影响性能。
encoding
entry的编码取决于他的值,如果是整数那encoding的前2个bit(位)都是11,后边的2个bit用户指定后边存储哪种整数,如果是字符串的话,那前2个bit表示存储字符串长度的编码类型,后边跟上字符串的实际长度。
字符串:
整数:
11000000 | 长度 | |
---|---|---|
|11000000| | 1个字节 | 代表后边的entry-data的值存储int16_t的整数值 |
|11010000| | 1个字节 | int32_t整数类型的值 |
|11100000| | 1个字节 | int64_t整数类型的值 |
|11110000| | 1个字节 | 24 bit,也就是3个字节的整数的值 |
|11111110| | 1个字节 | 8 bit,1个字节的整数的值 |
|1111xxxx| | 1个字节 | 这种的话entry-data就为空了,整数的值在编码里包含了,xxxx介于0000到1101之间,也就是0-12之间。实际是1-13,因为0000和1111不能使用,所以应该从4位值中减去以获得正确的值。(1111会跟zlend冲突,试想如果是最后一个元素。0000是因为会跟前面几个类型冲突,当然1110也会冲突,这也就是为什么只能表示介于0000到1101之间) |
entry-data:
这里存储的就是真正的值了,可以是一个字节数组或者整数,值的类型和长度由节点的encodingb编码决定。
prevlen | encoding | entry-data |
---|---|---|
假设上一个长度12 | 00001011 | hello world |
官方的例子:
假设有2和5两个字符串需要存储,那么压缩表的结构如下(16进制表示):
zlbytes | zltail | zllen | entry | entry | zlend |
---|---|---|---|---|---|
0f 00 00 00 | 0c 00 00 00 | 02 00 | 00 f3 | 02 f6 | ff |
占用15个字节数 | 偏移量为12 | 2个元素 | prevlen为0,(占用1个单字节) | prevlen占用2个字节所以为2 | 结束标记 |
encoding对应1111xxxx,XXXX介于 0001和1101之间,也就是1到12之间,f3的二进制11110011根据1111xxxx去掉 1111剩下0011减去1等于2,2的二进制 00000010加1,00000011 2属于0到12之间满足1111xxxx 编码,那么存储就是11110011 转为16进制就是f3(占用一个字节) | 和上一个entry一样 | ||||
entry-data 丢失 |
如果继续查入一个字符串,比如"hello world",那么entry表示如下:
prevlen | encoding | entry-data |
---|---|---|
02 | 0b | 48 65 6c 6c 6f 20 57 6f 72 6c 64 |
上一个entry的字节长度为2 | 小于254占用一个字节,匹配00pppppp,00代表编码pppppp表示实际长度 字符长度11,11的二进制是1011, 001011转为16进制就是0b |
ziplist API
未完待续。。。
网友评论