redis基于SDS、双端链表、字典等数据结构创建了一个对象系统,包括字符串对象、列表对象、哈希对象、集合对象和有序集合对象。
redis使用对象来表示数据库中的键和值,每当在redis中新创建一个键值对时,至少会创建2个对象。reds中的每个对象都是用一个redisObject结构来表示:
image.png
数据库的对象类型一共有如下5种:
image.png
键总是一个字符串对象,而值可以是上述的5种任意一种对象。
redis中的每种对象可以有多少不同的编码和底层实现,可以实现一定条件下灵活的转化,从而优化对象在某一场景下的效率:
image.png
字符串对象
字符串对象的编码可以是int、raw或者embstr。
1.字符串对象保存整数值,且可以用long类型表示,则使用整数值来表示,并且字符串编码为int
image.png
2.字符串对象保存字符串值,且长度大于39字节,则使用一个SDS数据结构来表示,并且字符串编码为raw
image.png
3.字符串对象保存字符串值,且长度小于等于39字节,那么用embstr的字符串编码(同样使用SDS的数据结构,但是redisobject和SDS的内存是连续的,可以很好地利用缓存的优势)
image.png
字符串编码是可以转换的,比如对int编码的字符串可以变成raw等等。
列表对象
列表对象的编码可以是ziplist(压缩列表)或者linkedlist(双端列表)。
对于操作
image.png
如果值对象使用ziplist编码,则如下所示:
image.png
如果值对象使用linkedlist编码,则如下所示:
image.png
每个双端链表节点都保存了一个字符串对象,而每个字符串对象都保存了一个列表元素(双端链表底层使用字符串对象进行嵌套)。
其中,StringObject实际上是下图的一个简化:
image.png
列表编码同样是可以转换的,当列表对象保存的所有字符串长度小于64字节且列表对象保存的元素数量小于512个时,列表对象使用ziplist编码;否则,使用linkedlist进行编码。
哈希对象
哈希对象的编码可以是ziplist或者hashtable。
对于操作:
image.png
如果使用ziplist进行编码,则如下所示:
image.png
即先将键添加到压缩列表表尾,然后将值添加到压缩列表表尾
如果使用hashtable进行编码,则如下所示:
image.png
其中,字典的每个键都是一个字符串对象,每个值也是一个字符串对象。
哈希对象编码同样是可以转换的,当哈希对象保存的所有键值对的键和值的字符串长度小于64字节且哈希对象保存的键值对数量小于512个时,哈希对象使用ziplist编码;否则,使用hashtable进行编码。
集合对象
集合对象的编码可以是intset(整数集合)或者hashtable。
对于操作:
image.png
image.png image.png
image.png
需要注意的是,使用hashtable的集合对象的值都为NULL。
集合对象编码同样可以转化,当集合对象保存的所有元素都是整数且元素数量不超过512个时,使用intset编码;否则,使用hashtable进行编码。
有序集合
有序集合的编码可以是ziplist或者skiplist。
1.ziplist中每个集合元素使用2个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员,第二个元素保存元素的分值。压缩列表内的集合元素分值从小到大排序。
image.png
2.skiplist编码的有序集合使用zset结构作为底层实现,一个zset结构同时包含一个字典和一个跳跃表:
image.png
之所以同时使用字典和跳跃表是为了查找的时候O(1)(字典的特性),以及能够利用跳跃表有序的特性来实现范围查找。
有序集合对象编码同样可以转化,当有序集合保存的元素数量小于128个且有序集合保存的所有元素成员的长度都小于64字节时,使用ziplist编码;否则,使用skiplist进行编码。
除了上述一些特点之外,还有一些需要说明:
1.类型检查与命令多态:redis会根据某个对象的编码调用底层不同数据结构的方法,并且调用之前会检查调用对象的type是否正确。
2.内存回收:引用计数实现内存回收。
3.对象共享:对于相同的值,只会在内存中保留一份,通过键指针指向现有的值对象,并将共享对象的值对象引用计数+1来实现。redis默认初始化服务器的时候就会创建0-9999共10000个字符串对象用于共享。注意:redis值共享包含整数值的字符串对象
4.对象的空转时长:redisObject通过lru属性记录对象最后一次被命令程序访问的时间
网友评论