?我们知道,redis常用的5种类型底层都是通过redisObject去封装的。看一下redisObject的源码:
typedef struct redisObject {
unsigned type:4;
unsigned encoding:4;
unsigned lru:LRU_BITS;
int refcount;
void *ptr;
} robj;
?这几个属性都很重要,下面就看下他们各自代表什么含义以及有什么作用。
?这个比较熟悉,type的值对应的就是我们平时接触最多的5种数据类型,即:string、list、set、sortedset、hash,当然还包括其他一些不常用的类型,在此不再列举。我们通过 type key
这个命令,可以拿到它的值类型。
?type在redis中也会对客户端输入的操作命令进行检查,校验合法性。在redis中,有两种类型的命令,一种是针对所有类型都适用的,如 del、ttl、expire等,还一种是针对特定类型的操作,比如 get、hget、lpush等,针对后者,type就可以对命令和参数的类型进行校验,校验通过会继续执行,否则就会向客户端返回错误提示。
?encoding这个值记录的每种值对象在底层的结构实现编码,什么意思呢?比如 hash 在redis的实现结构有两种类型,数据量比较少的时候使用 ziplist,数据量比较大的时候是 dict,那么encoding的值分别就是上面提到的 “ziplist” 和 “dict”。使用 object encoding key
这个命令可以查询。
?除了记录编码值之外,redis也会借助于encoding,实现命令的执行函数。还是上面提到的hash,比如我们执行了 hlen 获取hash长度,如果encoding标记的是ziplist,那么redis就会使用ziplist相关len函数去执行,如果encoding标记的值是dict,就会调用dict 相关的len函数去执行。
?lru这个必然是和内存回收相关的了,这里它记录的是当前的值对象最后一次被程序访问的时间,通过这个时间我们可以得到该对象的空转时长,就是当前时间 - lru记录的时间,如果说redis服务器打开了 maxmemory 最大内存管理的选项,并且内存回收使用 volatile-lru 或者 allkeys-lru算法,那么服务器就会优先释放这个空转时长比较长的对象,从而回收内存。
?通过object idletime key
这个命令就可以得到最近一次被访问的时间,需要注意的是,这个命令本身也访问了该对象,但是却不会引起这个时间值的改变。
?refcount记录的是当前这个值对象正在被多少数据结构所共享,redis用这个属性实现了引用计算器,这个值在低版本和高版本的设定有所区别。
?在低版本中,有如下场景,创建了一个键值对,key和val是 a = 100,那么此时a的值对象refcount值就是1,如果我们再新增一个键值对,key和val是b = 100,那么a和b的refcount的值都是2。但是在高版本中,同样使用object refcount key
测试的时候结果截然不同,查看源码得知,在高版本中,redis把一个对象设置为共享状态的时候,就会把refcount的值设置为固定的INT_MAX,所以是2147483647。而且redis把0~9999的数值默认设定为共享的状态,当服务器使用这些值的时候,会使用共享对象而不是新创建。
?虽然共享对象可以节约内存,但是如果共享一些结构比较复杂的对象,会导致对象比较的时间复杂度提高,受制于cpu计算时间的限制,redis只会对包含整数值的字符串对象进行共享。同理,如果refcount的值变为0时,表示对象没有被其他结构所共享了,它所占用的内存就会被服务器释放。
?ptr是个指针,指向了对象真正数据结构的实现,这些数据结构是由上面提到的encoding属性决定的。不同type可以对应不同的ptr实现,所以redisObject这种多态的特性也使得redis更加灵活。