Redis经典五大类型源码以及底层实现

文章目录

  • RedisKV键值对到底是什么?
  • String数据结构
    • int类型
    • embstr
    • raw
    • 总结
  • Hash数据结构
    • hashtable
    • zipList
    • listpack
    • ziplist和listpack的对比
  • List数据结构
    • quickList
  • Set数据结构
  • Zset
  • 总结

RedisKV键值对到底是什么?

Redis的key一般是String类型的字符串对象。
value类型则是Redis对象(redisObject)
Redis一切皆KV,键值对俗称dict字典
从底层C语言 KV(redis命令+lua脚本),字典dict一直到用户api RedisObject对象的高度抽象。
bitmap,hyperLogLog底层是String,GEO底层是Zset
每个键值对都会有一个dictEntry
dictEntry:
Redis经典五大类型源码以及底层实现_第1张图片
RedisObject:
Redis经典五大类型源码以及底层实现_第2张图片
type表示当前值对象的数据类型
encoding表示值对应的底层存储的编码类型
lru表示当内存超限时采用LRU算法清除内存中的对象
refcount表示对象引用的计数
这个ptr指针就是指向真真正正数据结构的(指向真正底层数据结构的指针)
Redis经典五大类型源码以及底层实现_第3张图片
redis结构:
Redis经典五大类型源码以及底层实现_第4张图片
l前后对比
Redis经典五大类型源码以及底层实现_第5张图片
为了便于操作,Redis采用redisObject结构来统一来统一五种不同的数据类型,这样所有的数据类型就都可以以相同的形式在函数间传递而不用使用特定的类型结构。同时为了识别不同的数据类型,redisObject中定义了type和encoding字段对不同数据类型加以区分,redisObject是五种数据类型的父类,可以在函数间传递时隐藏具体的类型信息。
Redis经典五大类型源码以及底层实现_第6张图片

String数据结构

同一种数据类型有不同种编码方式。
string类型有三种编码方式:

  • int
  • embstr
  • raw

Redis经典五大类型源码以及底层实现_第7张图片
RedisObject结构:
type:类型
encoding:编码 OBJECT ENCODING key
lru:最近被访问的时间,对象最后一次被命令程序访问的时间,与内存回收有关
refcount:当前对象被引用的次数,为0时表示不被任何对象引用,可以进行垃圾回收了
ptr:value的值是多少,指向对象实际的数据结构
Redis经典五大类型源码以及底层实现_第8张图片

int类型

数字长度大于等于20转化成字符串
Redis经典五大类型源码以及底层实现_第9张图片
Redis经典五大类型源码以及底层实现_第10张图片

embstr

C语言没有String类型,只能靠自己的char[]来实现,计算长度就要重新遍历,直到遇到’\0’为止,如果中间就出现\0就计算错误了,因此redis自己构建了一种名为简单动态字符串SDS的抽象类型,并将SDS作为Redis的默认字符串。
alloc可以空间预分配以及惰性空间释放。
在这里插入图片描述
Redis经典五大类型源码以及底层实现_第11张图片

  • len 表示 SDS 的长度,使我们在获取字符串长度的时候可以在 O(1)情况下拿到,而不是像 C 那样需要遍历一遍字符串。
  • alloc 可以用来计算 free 就是字符串已经分配的未使用的空间,有了这个值就可以引入预分配空间的算法了,而不用去考虑内存分配的问题。
  • buf 表示字符串数组,真存数据的。

raw

保存长度大于44字节的字符串

对于embstr,由于其实现是只读的,因此在对embstr对象进行修改的时候,都会先转化为raw再进行修改,因此只要修改embstr对象,修改后的对象一定是raw的,无论是否达到44个字节。

总结

Redis经典五大类型源码以及底层实现_第12张图片

只有整数才会使用 int,如果是浮点数, Redis 内部其实先将浮点数转化为字符串值,然后再保存。
embstr 与 raw 类型底层的数据结构其实都是 SDS (简单动态字符串,Redis 内部定义 sdshdr 一种结构)。
Redis内部会根据用户给的不同键值而使用不同的编码格式自适应地选择较优化的内部编码格式,这一切对用户都是透明的。

Hash数据结构

hash的两种编码格式:

  • redis6:ziplist hashtable
  • redis7:listpack hashtable
    hash-max-ziplist-entries:使用压缩列表保存时哈希集合中的最大元素个数
    hash-max-ziplist-value:使用压缩列表保存时哈希集合中单个元素的最大长度
    Redis经典五大类型源码以及底层实现_第13张图片
    一旦从压缩列表转为了哈希表,Hash类型就会一直用哈希表进行保存而不会再转回压缩列表。在节省内存空间方面哈希表就没有压缩列表高效了。

hashtable

在Redis中,hashtable被称为字典,它是一个数组+链表的结构。
== 每个键值对都会有一个dictEntry。==
在 Redis内部,从 OBJ_ENCODING_HT类型到底层真正的散列表数据结构是一层层嵌套下去的,组织关系见面图:
Redis经典五大类型源码以及底层实现_第14张图片
Redis经典五大类型源码以及底层实现_第15张图片

zipList

为了节约内存而开发的,它是由连续内存块组成的顺序型数据结构,有点类似于数组
ziplist是一个经过特殊编码的双向链表,=它不存储指向前一个链表节点prev和指向下一个链表节点的指针next而是存储上一个节点长度和当前节点长度,通过牺牲部分读写性能,来换取高效的内存空间利用率,节约内存,是一种时间换空间的思想。只用在字段个数少,字段值小的场景里面。
Redis经典五大类型源码以及底层实现_第16张图片
Redis经典五大类型源码以及底层实现_第17张图片
java hash 封装了一个Node类型数组。
redis hash 封装了一个新的特有数据结构ziplist
压缩列表zlentry节点结构,每个zlentry由前一个节点的长度,encoding和entry-data三部分组成

  • prevlen:记录前一个节点的长度
  • encoding:记录当前节点实际数据类型以及长度
  • entrydata:记录当前节点的实际数据
    Redis经典五大类型源码以及底层实现_第18张图片

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

enncoding:记录节点的content保存数据的类型和长度

content:保存实际数据内容

  • 为什么entry这么设计?记录前一个节点的长度?
    链表在内存中一般不连续遍历慢,ziplist解决了这个问题,如果知道当前起始地址,因为entry是连续的,entry后一定是另一个entry,想知道下一个entry地址,只要将当前起始地址加上当前entry总长度,以此类推。

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

  • ziplist为了节省内存,采用了紧凑的连续存储。

  • ziplist是一个双向链表,可以在时间复杂度为 O(1) 下从头部、尾部进行 pop 或 push。

  • 新增或更新元素可能会出现连锁更新现象(致命缺点导致被listpack替换)。

  • 不能保存过多的元素,否则查询效率就会降低,数量小和内容小的情况下可以使用。

  • 什么是ziplist的连锁更新问题?
    由于压缩列表每个节点需要保存前一个节点的长度字段,就会有连锁更新的隐患。

listpack

redis7出现紧凑列表listpack。
在这里插入图片描述
明明有ziplist了,为什么出来一个listpack紧凑列表?
为了解决连锁更新
下面是连锁更新的例子
Redis经典五大类型源码以及底层实现_第19张图片
连续多次的空间扩展
Redis经典五大类型源码以及底层实现_第20张图片
listpack是Redis设计用来取代掉ziplist的数据结构,它通过每个节点记录自己的长度且放在节点的尾部,来彻底解决掉ziplist存在的连锁更新问题。

  • encoding:记录content的类型和长度
  • content:实际数据
  • length:encoding+content总长度
    Redis经典五大类型源码以及底层实现_第21张图片
    Redis经典五大类型源码以及底层实现_第22张图片

ziplist和listpack的对比

Redis经典五大类型源码以及底层实现_第23张图片

listpack和ziplist列表类似,listpack列表项也包含了元数据信息和数据本身,不过,为了避免ziplist引起的连锁更新问题,listpack中的每个列表项不再像ziplist列表项那样保存其前一个列表项的长度

Redis经典五大类型源码以及底层实现_第24张图片

List数据结构

quickList

list用quickList来存储,quickList存储一个双向链表,每个节点都是一个ziplist。
Redis经典五大类型源码以及底层实现_第25张图片
在Redis3.0之前,list采用的底层数据结构是ziplist压缩列表+linkedList双向链表,然后在高版本的Redis中底层数据结构是quicklist(替换了ziplist+linkedList),而quicklist也用到了ziplist。
结论:quicklist就是==「双向链表 + 压缩列表」==组合,因为一个 quicklist 就是一个链表,而链表中的每个元素又是一个压缩列表,quicklist 实际上是 zipList 和 linkedList 的混合体,它将 linkedList按段切分,每一段使用 zipList 来紧凑存储,多个 zipList 之间使用双向指针串接起来。
Redis经典五大类型源码以及底层实现_第26张图片

Set数据结构

set的两种编码格式:

  • intset
  • hashtable
    Redis使用Intset或hashtable存储set,如果元素都是整数类型,就用intset存储,如果不是整数类型,就用hashtable(数组+链表的存储结构),key就是元素的值,value为null。Redis经典五大类型源码以及底层实现_第27张图片

Zset

zset的两种编码模式:
redis6:

  • ziplist
  • skiplist
    redis7:
  • listpack
  • skiplist
    有序集合中包含的元素数量超过服务器属性 server.zset_max_ziplist_entries 的值(默认值为 128 ),
    或者有序集合中新添加元素的 member 的长度大于服务器属性 server.zset_max_ziplist_value 的值(默认值为 64 )时,
    redis会使用跳跃表作为有序集合的底层实现。
    Redis经典五大类型源码以及底层实现_第28张图片
    Redis经典五大类型源码以及底层实现_第29张图片

总结

  1. 字符串
    int:8个字节的长整型。
    embstr:小于等于44个字节的字符串。
    raw:大于44个字节的字符串。
    Redis会根据当前值的类型和长度决定使用哪种内部编码实现。
  2. 哈希
    ziplist(压缩列表):当哈希类型元素个数小于hash-max-ziplist-entries 配置(默认512个)、同时所有值都小于hash-max-ziplist-value配置(默认64 字节)时,
    Redis会使用ziplist作为哈希的内部实现,ziplist使用更加紧凑的 结构实现多个元素的连续存储,所以在节省内存方面比hashtable更加优秀。
    hashtable(哈希表):当哈希类型无法满足ziplist的条件时,Redis会使 用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而hashtable的读写时间复杂度为O(1)。
  3. 列表
    ziplist(压缩列表):当列表的元素个数小于list-max-ziplist-entries配置 (默认512个),同时列表中每个元素的值都小于list-max-ziplist-value配置时 (默认64字节),
    Redis会选用ziplist来作为列表的内部实现来减少内存的使用
    linkedlist(链表):当列表类型无法满足ziplist的条件时,Redis会使用 linkedlist作为列表的内部实现。quicklist ziplist和linkedlist的结合以ziplist为节点的链表(linkedlist)
  4. 集合
    intset(整数集合):当集合中的元素都是整数且元素个数小于set-max-intset-entries配置(默认512个)时,Redis会用intset来作为集合的内部实现,从而减少内存的使用。
    hashtable(哈希表):当集合类型无法满足intset的条件时,Redis会使用hashtable作为集合的内部实现。
  5. 有序集合
    ziplist(压缩列表):当有序集合的元素个数小于zset-max-ziplist- entries配置(默认128个),同时每个元素的值都小于zset-max-ziplist-value配 置(默认64字节)时,
    Redis会用ziplist来作为有序集合的内部实现,ziplist 可以有效减少内存的使用。
    skiplist(跳跃表):当ziplist条件不满足时,有序集合会使用skiplist作为内部实现,因为此时ziplist的读写效率会下降。

时间复杂度对比:
Redis经典五大类型源码以及底层实现_第30张图片

你可能感兴趣的:(redis,redis,数据库,缓存)