目录
redisObject
当我们在redis里面创建一个键值对的时候,实际上我们是创建了两个对象,一个对象作为键(键对象),并且键总是一个字符串对象,一个对象作为值(值对象),我们把redis里面的对象称为redisObject。每个redisObject都有三个比较重要的属性:
类型type
编码encoding
指向底层具体数据结构的指针ptr
首先类型type有以下几种:
注:键对象总是一个字符串对象,但是值对象可以是上面的任意一种对象。
注:我们平时所叫的“字符串键”表示这个键对应的值是一个字符串对象。
然后编码encoding有以下这些,他们和类型type的对应关系如下:
redis通过编码encoding来设置对象所使用的某一种底层数据结构,而不是把一个对象固定关联一种特定的数据结构,这使得redis更加灵活。比如:
对于列表对象,当里面的元素较少的时候,他使用压缩列表作为底层实现,因为压缩列表比双端链表更加节约内存,并且在内存中是连续保存的,可以比双端链表更快的加载到缓存。
而当元素越来越多的时候,列表对象就会降低实现从压缩列表转为双端链表,因为它更适合保存大量元素。
各种具体的对象
【1】字符串对象
如果字符串对象保存的是一个整数值并且这个整数可以用long类型来表示,那么会直接将整数值保存在ptr属性里,设置编码为int。
如果字符串对象保存的是一个字符串值,并且这个字符串值长度大于39字节,那么它将使用一个SDS结构来保存这个字符串值,设置编码为raw。
而如果字符串长度小于39,就会使用embstr编码的方式保存这个字符串值,这种编码方式主要用于保存短字符串,特点是sdshdr的数据区和对象的内存区是连着的。embstr编码和raw编码的区别如下:
【2】列表对象
列表对象的底层是使用压缩列表或者链表实现的,也就是redisobejct的prt指针指向一个压缩列表或者链表的结构。而如果选择链表形式,其实链表里面的每个元素又是一个字符串对象(即【1】),因此,字符串对象是唯一一个还会被使用在别的对象里面的对象。
只有当列表对象同时满足:
1、所有字符串长度小于64字节;
2、保存的元素数量小于512个
才会使用压缩列表作为实现。
【3】哈希对象
哈希对象底层使用压缩列表或者哈希表(字典)来实现。如果使用压缩列表的话,新进来的元素放到压缩列表的末尾,prt指向的压缩列表结构如下图所示:
同样的,只有当哈希表对象同时满足:
1、所有字符串长度小于64字节;
2、保存的键值对数量小于512个;
才会使用压缩列表作为实现,否则使用哈希表来实现。
【4】集合对象
集合对象的底层数据结构可以是整数集合或者哈希表。他们的结构分别如下所示:
我们放了三个值:“apple”,“banana”,“cherry”。
类似的,只有1、需要保存的所有元素都是整数,2、对象元素不超过512个,才会使用整数集合作为底层,否则使用哈希表。
【5】有序集合对象
有序集合的底层可以是压缩列表或者跳表(其实是跳表+字典)。
如果使用压缩列表,那么里面的元素会按照分值自动从小到大排序,分值小的会自动放在靠近表头的位置。比如我们随便放了三个元素和分数,那么结果如下:
而如果使用跳表来实现,那么会同时使用跳表+字典。跳表结构主要用来进行范围操作,比如排序等等,在跳表里面保存了分值,以及对应的集合成员,而字典则是用来查找成员对应的分值,比如说用字典查找只需要O(1)。为了使得查找和范围型的操作都尽量的快,因此将他们两个进行结合。
只有当有序集合同时满足1:保存的元素数量小于128;2、所有成员长度都小于64字节;时才会使用压缩列表结构,否则会使用跳表结构。
小小总结一下:每种redisobject的底层数据类型都不止一种,起码都提供了2种底层,这使得redis的使用更加灵活,因为可以根据数据量的大小,选择适合的底层数据结构。
其他
1、因为C语言不会实现自动的内存回收,因此在redis里面利用引用计数法实现了一个内存自动回收,具体来说在每个redisObject里面有一个引用计数的变量,用来记录这个对象被程序使用的次数,一旦这个引用计数器的值变成0,占用的内存就会被释放。
2、引用计数器除了会使用在自动的内存回收中,还带有对象共享的作用。比如键A创建了一个整数值为100的字符串对象作为值,键B也创建了一个整数100的字符串对象作为值,那么实际上这两个键共享一个字符串对象,那么此时,引用计数器就会+1,这种共享对象机制,对于节约内存很有帮助:
目前来说,redis会在初始化服务器的时候,创建1万个字符串对象,包含了从0~9999的所有整数值,当需要用到0~9999的字符串值的时候,就会直接使用这些共享对象,而不是创建新对象。比如我们如果创建了值为100的键A,直接查询键A 的值对象的引用计数的话,可以发现直接就是2了,而不是1:
注:在使用共享对象之前,会检查已有的共享对象和想创建的目标对象的值是否一样,检查整数是否一样很快,但是检查字符串是否一样却慢很多,因此redis只会对包含整数值的字符串对象进行共享。
3、lru属性:这个属性记录了对象最后一次被程序访问的时间,因此可以通过使用当前时间减去lru得到这个给定键的空转时长。也就是这个键有多久没有被使用过了。
参考书籍:《Redis设计与实现》