Redis对象属性详解
Redis的对象结构如下:
1 typedef struct redisObject{ 2 3 //类型 4 unsigned type:4; 5 6 //编码 7 unsigned encoding:4; 8 9 //指向底层数据的指针 10 void *ptr; 11 12 //对象引用计数 13 int refCount; 14 15 //最后一次被程序访问的时间 16 unsigned lru:22; 17 }
下面对每个对象进行详情的解释:
1.类型 type(redisObject # type)
Redis对象的type属性记录了对象的类型,这个属性值可以是REDIS_STRING(字符串对象)、REDIS_LIST(列表对象)、REDIS_HASH(哈希对象)、REDIS_SET(集合对象)、REDIS_ZSET(有序集合对象)五种的一种。
在Redis的客户端中可以使用type命令来查看值对象的类型(这里强调值对象的类型的原因是,Redis是键值对数据库,键都是字符串对象,值则有上述的五种类型)
#类型为字符串对象 redis > SET msg "hello" OK redis > TYPE msg string #类型为list对象 redis > RPUSH numbers 1 2 3 25 (integer) 4 redis > TYPE numbers list
下表列出了TYPE命令对于不同的对象的输出
对象 | 对象type属性的值 | type命令的输出 |
字符串对象 | REDIS_STRING | string |
列表对象 | REDIS_LIST | list |
哈希对象 | REDIS_HASH | hash |
集合对象 | REDIS_SET | set |
有序集合对象 | REDIS_ZSET | zset |
2.编码(redisObject # encoding)
Redis底层存储的数据结构是由对象的encoding属性决定的。encoding属性的值可以是下表中列出的常量中的一个
编码常量 | 编码所对应的底层数据结构 |
REDIS_ENCODING_INT | long类型的整数 |
REDIS_ENCODING_EMBSTR | embstr编码的简单动态字符串 |
REDIS_ENCODING_RAW | 简单动态字符串 |
REDIS_ENCODING_HT | 字典 |
REDIS_ENCODING_LINKEDLIST | 双端链表 |
REDIS_ENCODING_ZIPLIST | 压缩列表 |
REDIS_ENCODING_INTSET | 整数集合 |
REDIS_ENCODING_SKIPLIST | 跳跃表和字典 |
Redis每种类型的对象都至少使用了两种不同的编码,下表列出来每种对象可以使用的编码
类型 | 编码 | 对象 | OBJECT ENCODING命令输出 |
REDIS_STRING | REDIS_ENCODING_INT | 值是整数的字符串对象 | int |
REDIS_STRING | REDIS_ENCODING_EMBSTR | 值是embstr类型简单动态字符串的字符串对象 | embstr |
REDIS_STRING | REDIS_ENCODING_RAW | 值是简单动态字符串的字符串对象 | raw |
REDIS_LIST | REDIS_ENCODING_ZIPLIST | 值是压缩列表的列表对象 | ziplist |
REDIS_LIST | REDIS_ENCODING_LINKEDLIST | 值是双端链表的列表对象 | linkedlist |
REDIS_HASH | REDIS_ENCODING_ZIPLIST | 值是压缩列表的哈希对象 | ziplist |
REDIS_HASH | REDIS_ENCODING_HT | 值是字典的哈希对象 | hashtable |
REDIS_SET | REDIS_ENCODING_INTSET | 值是整数集合的集合对象 | intset |
REDIS_SET | REDIS_ENCODING_HT | 值是字典的集合对象 | hashtable |
REDIS_ZSET | REDIS_ENCODING_ZIPLIST | 值是压缩列表的有序集合对象 | ziplist |
REDIS_ZSET | REDIS_ENCODING_SKIPLIST | 值是跳跃表和字典的有序集合对象 | skiplist |
命令:OBJECT ENCODING
使用这个命令可以查看Redis的值对象的编码:
redis > SET msg "hello" OK redis > OBJECT ENCODING msg embstr redis > SADD num 1 3 4 5 (integer) 4 redis > OBJECT ENCODING num intset
使用encoding的优点:
通过encoding属性来设定对象所使用的编码,而不是为特定的类型定制一种固定的编码,这样极大的提高了Redis的灵活性和效率,因为Redis可以根据不同的使用场景来为一种对象设置不同的编码,从而优化对象在某一场景下的效率
举个例子,在列表对象包含的对象比较少时,Redis使用压缩列表作为列表的底层实现:
(1)因为压缩列表比双端列表更节约内存,并且在元素较少时,在内存中以连续块方式保存的压缩列表比起双端链表可以更快的载入缓存中
(2)随着列表包含的元素越来越多,使用压缩列表保存的优势逐渐降低时,列表对象就会将底层实现从压缩列表转向功能更强,也更适合保存大量元素的双端链表上面;
Redis的其他类型对象通过使用不同的编码来进行类似的优化。
3.PTR(RedisObject # *ptr)
*ptr是一个指针,指向实际保存值的数据结构,这个数据结构由type和encoding属性决定。
举个例子, 如果一个redisObject 的type 属性为REDIS_LIST , encoding 属性为REDIS_ENCODING_ZIPLIST ,那么这个对象就是一个Redis 列表(List),
它的值保存在一个aiplist的数据结构内,而ptr 指针就指向ziplist的对象;
4.引用计数(redisObject # refCount)
因为C语言不具备内存自动回收功能,所以Redis在自己的对象系统中构建了引用计数技术来实现内存回收机制
Redis中对象中的引用计数会随着对象的使用而不断的变化:
(1)当创建一个新对象的时候,这个对象的引用计数值会被初始化为1;
(2)当对象被程序进行共享时,这个对象的引用计数refCount加1;
(3)当程序使用完一个对象或者消除对一个对象的引用时,这个对象的引用计数refCount减1;
(4)当对象的的引用计数refCount为0时,对象所占用的内存会被释放(这个对象的redisObject结构以及它引用的底层数据结构的内存都会被释放);
4.1对象共享
Redis对象中的引用计数除了用于实现内存回收机制外,还带有对象共享的属性
假设键A创建了一个整数100的字符串对象作为值对象,也就是
redis > SET A 100 OK
如果键B同样要创建一个整数100的字符串对象,那么键B有两种做法:
(1)为键B新建一个整数100的字符串对象
(2)让键A和键B共享一个字符串对象
上面两种方法明显(2)更加的节约内存,Redis就是使用的方法二来实现的
在Redis中,让多个键共享同一个值对象需要执行两个步骤:
(1)将数据库中键的值的指针指向一个现有的值对象
(2)将被共享的值对象的引用计数(refCount)加1
在Redis会在初始化的时候,创建0到9999这10000个整数值字符串对象,当服务器需要用到值为0到9999的整数字符串对象时,服务器就会共享这个对象,而不是新创建对象
同时,这些对象不仅仅只是字符串键可以使用,其他的数据结构中嵌套了字符串对象的对象也可以使用。比如linkedList编码的列表对象、hashtable编码的哈希对象、hashtable编码的集合对象、zset编码的有序集合对象。
注意:
在Redis版本2.8或者以前,上述0~9999的整数字符串对象,在Redis服务器初始化后,那么这10000个整数值对象的refCount就已经为1,当我们在使用这些值时,refCount就会相应的加1,那么这些值的refCount最低也就为1,所以这些对象不会被回收。
在新版本中(我也不知道具体的版本,目前我使用的是5.0版本),Redis服务器初始化后,这10000个整数值对象的refCount为2147483647=2 ^ 32 / 2 - 1。
并且源码中可以看到当把一个对象设置为共享时候就会把refcount设置为INT_MAX
robj *makeObjectShared(robj *o) { serverAssert(o->refcount == 1); o->refcount = OBJ_SHARED_REFCOUNT; //使得对象为共享,设置为obj_shared_refcount 其大小为int_max return o; }
5.lru 对象空转时长(redisObject # lru)
lru属性记录了这个对象最后一次被程序访问的时间(unix的时间戳)
OBJECT IDLETIME命令可以打印出指定键的空转时长(单位为秒)
#清空,防止以前设置过一样的key redis > flushdb OK redis > set a hello OK redis >OBJECT IDLETIME a (integer)10
注意:OBJECT IDLETIME 命令实现是特殊的,这个命令在访问键的值对象时,不会修改值对象的lru属性。
lru属性还有另外的一个作用:如果Redis服务器设置了最大内存(maxmemory)选项,并且服务器用的回收内存的算法是volatile-lru或者allkeys-lru,那么当服务器的内存超过maxmemory设置的上限值时,空转时间较高的那部分键会被优先释放,回收内存。