Redis经典五大类型源码及底层实现
Hash数据结构介绍
redis7
listpack+hashtable
hash-max-listpack-entries:使用压缩列表保存时哈希集合中的最大元素个数。
hash-max-listpack-value:使用压缩列表保存时哈希集合中单个元素的最大长度。
Hash类型键的字段个数 小于 hash-max-listpack-entries且每个字段名和字段值的长度 小于 hash-max-listpack-value 时,Redis才会使用OBJ_ENCODING_LISTPACK来存储该键,前述条件任意一个不满足则会转换为 OBJ_ENCODING_HT的编码方式
结论:
1.哈希对象保存的键值对数量小于512个
2.所有的键值对的键和值的字符串长度都小于等于64byte(一个英文字母一个字节)时用listpack,反之用hashtable
3.listpack升级到hashtable可以,反过来降级不可以
源码分析
实现:object.c
实现:listpack.c
lpNew 函数创建了一个空的 listpack,一开始分配的大小是 LP_HDR_SIZE 再加 1 个字节。LP_HDR_SIZE 宏定义是在 listpack.c 中,它默认是 6 个字节,其中 4 个字节是记录 listpack 的总字节数,2 个字节是记录 listpack 的元素数量。
此外,listpack 的最后一个字节是用来标识 listpack 的结束,其默认值是宏定义 LP_EOF。
和 ziplist 列表项的结束标记一样,LP_EOF 的值也是 255
实现:object.c
明明已经有ziplist了,为什么出来一个listpack紧凑列表呢?
ziplist的连锁更新问题
压缩列表新增某个元素或修改某个元素时,如果空间不不够,压缩列表占用的内存空间就需要重新分配。而当新插入的元素较大时,可能会导致后续元素的 prevlen 占用空间都发生变化,从而引起「连锁更新」问题,导致每个元素的空间都要重新分配,造成访问压缩列表性能的下降。
案例说明:压缩列表每个节点正因为需要保存前一个节点的长度字段,就会有连锁更新的隐患
第一步:现在假设一个压缩列表中有多个连续的、长度在 250~253 之间的节点,如下图:
因为这些节点长度值小于 254 字节,所以 prevlen 属性需要用 1 字节的空间来保存这个长度值,一切OK,O(∩_∩)O哈哈~
第二步:这时,如果将一个长度大于等于 254 字节的新节点加入到压缩列表的表头节点,即新节点将成为entry1的前置节点,如下图:
因为entry1节点的prevlen属性只有1个字节大小,无法保存新节点的长度,此时就需要对压缩列表的空间重分配操作并将entry1节点的prevlen 属性从原来的 1 字节大小扩展为 5 字节大小。
第三步:连续更新问题出现
entry1节点原本的长度在250~253之间,因为刚才的扩展空间,此时entry1节点的长度就大于等于254,因此原本entry2节点保存entry1节点的 prevlen属性也必须从1字节扩展至5字节大小。entry1节点影响entry2节点,entry2节点影响entry3节点…一直持续到结尾。这种在特殊情况下产生的连续多次空间扩展操作就叫做「连锁更新」
结论:listpack 是 Redis 设计用来取代掉 ziplist 的数据结构,它通过每个节点记录自己的长度且放在节点的尾部,来彻底解决掉了 ziplist 存在的连锁更新的问题
listpack结构
Total Bytes | 为整个listpack的空间大小,占用4个字节,每个listpack最多占用4294967295Bytes。 |
num-elements | 为listpack中的元素个数,即Entry的个数占用2个字节 |
element-1~element-N | 为每个具体的元素 |
listpack-end-byte | 为listpack结束标志,占用1个字节,内容为0xFF。 |
entry结构
- 当前元素的编码类型
- 元素数据
- 以及编码类型和元素数据这两部分的长度
ziplist内存布局 VS listpack内存布局
和ziplist 列表项类似,listpack 列表项也包含了元数据信息和数据本身。不过,为了避免ziplist引起的连锁更新问题,listpack 中的每个列表项
不再像ziplist列表项那样保存其前一个列表项的长度。
List数据结构
Redis6
(1) ziplist压缩配置:list-compress-depth 0
表示一个quicklist两端不被压缩的节点个数。这里的节点是指quicklist双向链表的节点,而不是指ziplist里面的数据项个数
参数list-compress-depth的取值含义如下:
0: 是个特殊值,表示都不压缩。这是Redis的默认值。
1: 表示quicklist两端各有1个节点不压缩,中间的节点压缩。
2: 表示quicklist两端各有2个节点不压缩,中间的节点压缩。
3: 表示quicklist两端各有3个节点不压缩,中间的节点压缩。
依此类推…
(2) ziplist中entry配置:list-max-ziplist-size -2
当取正值的时候,表示按照数据项个数来限定每个quicklist节点上的ziplist长度。比如,当这个参数配置成5的时候,表示每个quicklist节点的ziplist最多包含5个数据项。当取负值的时候,表示按照占用字节数来限定每个quicklist节点上的ziplist长度。这时,它只能取-1到-5这五个值,
每个值含义如下:
-5: 每个quicklist节点上的ziplist大小不能超过64 Kb。(注:1kb => 1024 bytes)
-4: 每个quicklist节点上的ziplist大小不能超过32 Kb。
-3: 每个quicklist节点上的ziplist大小不能超过16 Kb。
-2: 每个quicklist节点上的ziplist大小不能超过8 Kb。(-2是Redis给出的默认值)
-1: 每个quicklist节点上的ziplist大小不能超过4 Kb。
Redis版本前List的一种编码格式
list用quicklist存储,quicklist存储了一个双向链表,每个节点都是一个ziplist
在Redis3.0之前,list采用的底层数据结构是ziplist压缩列表+linkedList双向链表
然后在高版本的Redis中底层数据结构是quicklist(替换了ziplist+linkedList),而quicklist也用到了ziplist
结论:quicklist就是「双向链表 + 压缩列表」组合,因为一个 quicklist 就是一个链表,而链表中的每个元素又是一个压缩列表
quicklist 实际上是 zipList 和 linkedList 的混合体,它将 linkedList按段切分,每一段使用 zipList 来紧凑存储,多个 zipList 之间使用双向指针串接起来。
源码分析
quicklist.h,head和tail指向双向列表的表头和表尾
quicklist结构
Redis系列-14.Redis经典五大类型源码及底层实现(二)(下):https://developer.aliyun.com/article/1414748