压缩列表主要存储int 和 字节数组
压缩队列是Redis 底层一个很重要的数据结构,hash,list,set等Redis 对象在存储的元素个数不多,元素大小不大的情况下。都是使用压缩队列。只有在达到一定的阈值的时候,会升级为hashmap,linkedlist,skiplist等数据结构。
数据结构
| 字段 | 说明 | 字节数 |
|---|---|---|
| zlbytes | 当前已存储的字节数 | 4(也就表明最大的大小只能为int32的最大长度,大概2G多) |
| zltail | 列表尾偏移量, 同 zentry.previous_entry_len来实现倒序遍历 | 4 |
| zllength | 元素个数 | 2,当小于uint16_max(65535)时,该值为列表元素个数,否则固定为 65535,此时需要遍历元素或者列表长度 |
| zentry1 | 存储节点值 | 不定 |
| zentry2 | 存储节点值 | 不定 |
| zentry3 | 存储节点值 | 不定 |
| zlend | 特殊标识值(0xff),标记队尾 | 1 |
zentry
| 字段 | 说明 |
|---|---|
previous_entry_len | 前一个节点的字节长度 |
encoding | 节点编码 |
content | 存储的实际长度,可能为一个整形或者字节数组,应该为void * |
previous_entry_len
- 保存的是前一个节点的所占的字节数。
previous_entry_len本身可以占1个或者5个字节1byte 的previous_entry_len表明前一个节点大小254bytes。1个字节 =255。因为需要区分previous_entry_len是1个还是5个,需要占用FE(254). (那还是还浪费一个啊)- 当
previous_entry_len的第一个字节是FE的时候,表明前一个节点是大于254,此时previous_entry_len后的4个字节表示的int表明前一个节点的大小。例如0xFE00002766.表明前一个节点大小为0x2766(10进制 = 10086)
encoding说明
ziplist 可以存放整数 和 字节数组,所以encoding必须可以区分存放的是整数还是字节数组
encoding使用第一个字节的高两位作为类型判断. 当最高位是11的时候,表示的是整形,非11表示是byte数据。其中encoding所占的空间不固定,受类型影响,可能为1或者2或者5个字节
当第一个字节的最高位为11的时候,encoding长度为1.表示的是整数. 整数又分为各种长度的类型.分布如下
| 标识 | content说明 |
|---|---|
| 11 111110 | 保存有符号int8_t |
| 11 000000 | 保存int16_t |
| 11 010000 | 保存int32_t |
| 11 100000 | 保存int64_t |
| 11 11000 | 保存24位有符号整数(没见过) |
| 11 11xxxx | content不占用空间, 此时 encoding的后四位保存的4位整数. |
当encoding最高位非11的时候(b表示一个16进制数,_ 表示无意义)
| 标识 | encoding长度 | content说明 |
|---|---|---|
| 00 bbbbbb | 1字节 | 长度为0xbbbbbb的字节数组 |
| 01 bbbbbb bbbbbbbb | 2字节 | 长度为2^14的字节数组 |
| 10 ______ bbbbbbbb bbbbbbbb bbbbbbbb bbbbbbbb | 5字节 | 长度为2^32的字节数组 |
连锁更新
假设下面的情况
假设一个ziplist.总共3个节点,每个entry.content内容为253个字节.此时每个节点为内容为 | 1字节的entry_prev | 1字节的encoding | 内容(253-1-1) | ,
此时如果在压缩列表头部或者第二个位置前插入一个大于253字节长度的内容时,此时第二个节点的prev_entry_len的1个字节长度肯定不够(看prev_entry_len说明).必定要进行扩容,当第二个元素扩容后,大小必定大于254,此时原第三个元素的prev_entry_len的1的一个字节必须要变成5字节,如果后面还有相同结构的内容.则所有的元素都要扩容.
上面就是连锁更新。
虽然出现连锁更新的时候,效率很低,但是出现概率较为苛刻必须所有节点的content大小刚好都在253附近.否则任何一个大于pre_entry_len=5byte的都不需要扩展.
同样也会出现删除时连锁降级更新的情况
问题
- 第一个节点的
prev_entry_len存放啥值?0,我猜的 - 向前遍历比向后遍历还麻烦?一样。
- 因为节点的encoding包含了长度信息。通过长度信息,previous的信息。encoding的类型信息可以计算出一个点节点的长度信息