12. Redis底层实现 Hash

HASH

config get hash*
hash-max-ziplist-entries: 512(使用压缩列表保存时哈希集合中的最大元素个数。),
hash-max-ziplist-value:64(使用压缩列表保存时哈希集合中单个元素的最大长度。key和value都包括)

以上两个条件都满足时(小于等于),会使用OBJ_ENCODING_ZIPLIST来存储该键,前述条件任意一个不满足则会转换为 OBJ_ENCODING_HT的编码方式
12. Redis底层实现 Hash_第1张图片
12. Redis底层实现 Hash_第2张图片

结论:

  1. 哈希对象保存的键值对数量小于512个
  2. 所有的键值对的键和值的字符串长度都小于等于64byte(一个英文字母一个字节)
  3. ziplist升级到hashtable可以,反过来降级不可以
    一旦从压缩列表转为了哈希表,Hash类型就会一直用哈希表进行保存而不会再转回压缩列表了。
    在节省内存空间方面哈希表就没有压缩列表高效了。

12. Redis底层实现 Hash_第3张图片

  • Ziplist 压缩列表是一种紧凑编码格式,总体思想是多花时间来换取节约空间,即以部分读写性能为代价,来换取极高的内存空间利用率,因此只会用于 字段个数少,且字段值也较小 的场景。压缩列表内存利用率极高的原因与其连续内存的特性是分不开的。
  • 当一个 hash对象 只包含少量键值对且每个键值对的键和值要么就是小整数要么就是长度比较短的字符串,那么它用 ziplist 作为底层实现

ziplist什么样:

ziplist是一个经过特殊编码的双向链表,它不存储指向上一个链表节点和指向下一个链表节点的指针,而是存储上一个节点长度和当前节点长度,通过牺牲部分读写性能,来换取高效的内存空间利用率,节约内存,是一种时间换空间的思想。只用在字段个数少,字段值小的场景里面。
12. Redis底层实现 Hash_第4张图片
12. Redis底层实现 Hash_第5张图片
12. Redis底层实现 Hash_第6张图片

明明有链表了,为什么出来一个压缩链表?

  1. 普通的双向链表会有两个指针,在存储数据很小的情况下,存储的实际数据的大小可能还没有指针占用的内存大,得不偿失。ziplist 是一个特殊的双向链表没有维护双向指针:prev next;而是存储上一个 entry的长度和 当前entry的长度,通过长度推算下一个元素在什么地方。牺牲读取的性能,获得高效的存储空间,因为(简短字符串的情况)存储指针比存储entry长度更费内存。这是典型的“时间换空间”。
  2. 链表在内存中一般是不连续的,遍历相对比较慢,而ziplist可以很好的解决这个问题,普通数组的遍历是根据数组里存储的数据类型找到下一个元素的(例如int类型的数组访问下一个元素时每次只需要移动一个sizeof(int)就行),但是ziplist的每个节点的长度是可以不一样的,而我们面对不同长度的节点又不可能直接sizeof(entry),所以ziplist只好将一些必要的偏移量信息记录在了每一个节点里,使之能跳到上一个节点或下一个节点。
  3. 头节点里有头节点里同时还有一个参数 len,和string类型提到的 SDS 类似,这里是用来记录链表长度的。因此获取链表长度时不用再遍历整个链表,直接拿到len值就可以了,这个时间复杂度是 O(1)
    12. Redis底层实现 Hash_第7张图片

压缩列表是 Redis 为节约空间而实现的一系列特殊编码的连续内存块组成的顺序型数据结构,本质上是字节数组
在模型上将这些连续的数组分为3大部分,分别是header+entry集合+end

  • 其中header由zlbytes+zltail+zllen组成,
  • entry是节点,
  • zlend是一个单字节255(1111 1111),用做ZipList的结尾标识符。见下: 压缩列表结构:由zlbytes、zltail、zllen、entry、zlend这五部分组成
    在这里插入图片描述
    zlbytes 4字节,记录整个压缩列表占用的内存字节数。
    zltail 4字节,记录压缩列表表尾节点的位置。
    zllen 2字节,记录压缩列表节点个数。
    zlentry 列表节点,长度不定,由内容决定。
    zlend 1字节,0xFF 标记压缩的结束。
    12. Redis底层实现 Hash_第8张图片

前节点:(前节点占用的内存字节数)表示前1个zlentry的长度,prev_len有两种取值情况:1字节或5字节。取值1字节时,表示上一个entry的长度小于254字节。虽然1字节的值能表示的数值范围是0到255,但是压缩列表中zlend的取值默认是255,因此,就默认用255表示整个压缩列表的结束,其他表示长度的地方就不能再用255这个值了。所以,当上一个entry长度小于254字节时,prev_len取值为1字节,否则,就取值为5字节。
**enncoding:**记录节点的content保存数据的类型和长度。
**content:**保存实际数据内容

12. Redis底层实现 Hash_第9张图片

通过指向表尾节点的位置指针p1, 减去节点的previous_entry_length,得到前一个节点起始地址的指针。如此循环,从表尾遍历到表头节点。从表尾向表头遍历操作就是使用这一原理实现的,只要我们拥有了一个指向某个节点起始地址的指针,那么通过这个指针以及这个节点的previous_entry_length属性程序就可以一直向前一个节点回溯,最终到达压缩列表的表头节点。

12. Redis底层实现 Hash_第10张图片
12. Redis底层实现 Hash_第11张图片

为什么数据量大时不用ziplist?

因为ziplist是一段连续的内存,插入的时间复杂化度为O(n),而且每当插入新的元素需要realloc做内存扩展;而且如果超出ziplist内存大小,还会做重新分配的内存空间,并将内容复制到新的地址。如果数量大的话,重新分配内存和拷贝内存会消耗大量时间。所以不适合大型字符串,也不适合存储量多的元素。

hashtable

hashtable 被称为字典(dictionary),它是一个数组+链表的结构
12. Redis底层实现 Hash_第12张图片

你可能感兴趣的:(redis6,redis,缓存,nosql)