Redis是key-value存储系统
在Redis7中涉及到listpack紧凑列表的调整,listpack是用来替代ziplist的心数据类型,在7.0版本已经没有ziplist的配置了(6.0版本仅部分数据类型作为过渡阶段在使用)listpack已经替换了ziplist,类似hash-max-ziplist-entries的配置。
为了便于操作,Redis采用redisObject结构来统一五种不同的数据类型,这样所有的数据类型都可以以相同的形式在函数间传递而不用使用特定的类型结构。同时,为了识别不同的数据类型,redisObject中定义了type和encoding字段对不同的数据类型加以区别。简单地说,redisObject 就是String、hash、list、set、zset的父类
。可以在函数间传递时隐藏具体的类型信息,所以作者抽象了redisObject结构来达到同样的目的。
Debug Object key…
Redis没有直接复用C语言的字符串,而是新建了属于自己的结构 ---- SDS。
在Redis数据库里,包含字符串值的键值对都是由SDS实现的(Redis中所有的键都是由字符串对象实现的即底层是由SDS实现,Redis中所有的值对象中包含的字符串对象底层也是由SDS实现)。
Redis内部会根据用户给的不同键值而使用不同的编码格式,自适应地选择较优化的内部编码格式,而这一切对用户完全透明。
Ziplist压缩列表是一种紧凑编码格式,总体思想是多花时间来换取节约空间,即以部分读写性能为代价,来换取极高的内存空间利用率,因此只会用于字段个数少,且字段值比较小的场景。压缩列表内存利用率极高的原因与其连续内存的特性是分不开的。
类似于一种GC垃圾回收机制:标记–压缩算法。当一个hash对象,只包含少量键值对且每个键值对的键和值亚朵就是小整数,要么就是长度比较短的字符串,那么它拥ziplist作为底层实现。
ZipList为了节约内存而开发,它是由连续内存块组成的顺序型数据结构,有点类似于数组
zipList是一个经过特殊编码的双向链表,它不存储指向前一个链表节点prev和指向下一个链表节点的指针next
而是存储上一个节点长度和当前节点长度, 通过牺牲部分读写性能,来换取高效的内容空间利用率,节约内存,是一种时间换空间的思想。只用在字段个数少,字段值小的场景里面
。
ziplist各个组成单元:
ziplist为了节省内存,采用了紧凑的连续存储。
ziplist是一个双向链表,可以在时间复杂度为O(1)下从头部、尾部进行pop或push。
新增或更新元素可能会出现连锁更新现象(致命缺掉导致被listpack替换)。
不能保存过多的元素,否则查询效率就会降低,数量小和内容小的情况下可以使用。
Hash类型键的字段个数小于
hash-max-listpack-entries且每个字段名和字段值的长度小于
hash-max-listpack-value时,Redis才会使用OBJ_ENCODING_LISTPACK来存储该键,前述条件任意一个不满足则会转换为OBJ_ENCODING_HT的编码方式。
明明有ziplist了,为什么出来一个listpack紧凑列表?
ziplist的连锁更新问题:
listpack是Redis设计用来取代掉ziplist的数据结构,它通过每个节点记录自己的长度且放在节点的尾部,来彻底解决掉ziplist存在的连锁更新的问题。
listpack由4部分组成,total Bytes、Num Elem、Entry以及End。
entry结构:
和ziplist列表项类似,listpack列表项也包含了元数据信息和数据本身。不过,为了避免ziplist引起的连锁更新问题,listpack中的每个列表项不再像ziplist列表项那样保存其前一个列表项的长度。
Redis6版本前的List的编码格式:list用quicklist来存储,quicklist存储了一个双向链表,每个节点都是一个ziplist
在Redis3.0之前,list采用的底层数据结构是ziplist压缩列表+linkedList双向链表。
在高版本的Redis中底层数据结构是quicklist(替换了ziplist + linkedList),而quicklist也用到了ziplist。
quicklist就是【双向链表+压缩链表】组合,因为一个quicklist就是一个链表,而链表中的每个元素又是一个压缩列表。
在较早版本的Redis中,list有两种底层实现:
两者各自的缺点:
为了结合两者的优点,在redis3.2之后,list的底层时间变为快速列表quicklist。
quicklist实际上是ziplist和linkedList的混合体,它将linkedList按段切分,每一段使用ziplist来紧凑存储,多个zipList之间使用双向指针串接起来。
Redis7的List是用quickList来存储,quickList存储了一个双向链表,每一个节点都是一个listpack。
Redis用intset或hashtable存储set。如果元素都是整数类型,就用insert存储。如果不是整数类型,就用hashtable(数组+链表的存储结构)。key就是元素的值,value为null。
当有序结合中包含的元素数量超过服务器属性server.zset_max_ziplist_entries/server.zset_max_listpack_entries的值(默认值为128),或者有序集合中新添加元素的member的长度大于服务器属性server.zset_max_ziplist_value/server.zset_max_listpack_value的值(默认值为64)时,redis会使用跳跃表
作为有序集合的底层实现。否则会使用ziplist/listpack作为有序集合的底层实现。
为什么引出跳表?
对于一个单链表来讲,即便链表中存储的数据是有序的
,如果我们要想在其中查找某个数据,也只能从头到尾遍历链表。这样查找效率会很低,时间复杂度会很高O(N)。
尝试空间换时间(升维),给链表加个索引,称为“索引升级”。
跳表是可以实现二分查找的有序链表,是一种以空间换取时间的结构。由于链表无法进行二分查找,因此借鉴数据库索引的思想,提取出链表中关键节点(索引),先在关键节点上查找,再进入下层链表查找,提取多层关键节点,就形成了跳跃表。但是,由于索引也要占据一定空间的,所以索引添加的越多,空间占用的越多。
总结来讲,跳表 = 链表 + 多级索引
跳表的时间复杂度是O(logN)
跳表的空间复杂度是O(N)
数据量较大的情况下
才能体现出来优势。而且应该是读多写少的情况下
才能使用,所以它的适用范围应该还是比较有限的。