Redis中的对象系统包含字符串对象,列表对象,集合对象,有序集合对象,哈希对象这五种类型。同时在Redis中还实现了基于引用计数的内存回收机制和对象共享机制。Redis中的对象带有访问时间记录信息,能够用于计算数据库键的空转时常。
对象类型
Redis使用对象来表示数据库中的键和值。每次新创键一个键值对时,会创建两个对象,一个用作键,一个用作值。
对象类型结构
1 | typedef struct redisObject { |
其中type属性判断该对象是字符串对象,列表对象,集合对象,有序集合对象,哈希对象中的哪一个类型。encoding属性决定ptr指针指向的对象的底层实现数据结构,其对应的方式为:
类型 | 编码 | 对象 |
---|---|---|
REDIS_STRING | REDIS_ENCODING_INT | 整数值的字符串对象 |
REDIS_STRING | REDIS_ENCODING_EMBSTR | 使用embstr编码的字符串对象 |
REDIS_STRING | REDIS_ENCODING_RAW | 使用简单字符串实现的字符串对象 |
REDIS_LIST | REDIS_ENCODING_ZIPLIST | 使用压缩列表实现的列表对象 |
REDIS_LIST | REDIS_ENCODING_LINKEDLIST | 使用链表实现的列表对象 |
REDIS_HASH | REDIS_ENCODING_HT | 使用字典实现的哈希对象 |
REDIS_HASH | REDIS_ENCODING_ZIPLIST | 使用压缩列表实现的哈希对象 |
REDIS_SET | REDIS_ENCODING_HT | 使用字典实现的集合对象 |
REDIS_SET | REDIS_ENCODING_INTSET | 使用整数集合实现的集合对象 |
REDIS_ZSET | REDIS_ENCODING_ZIPLIST | 使用压缩列表实现的有序集合对象 |
REDIS_ZSET | REDIS_ENCODING_SKIPLIST | 使用跳跃表和字典实现的有序集合对象 |
使用命令OBJECT ENCODING可以查看一个数据库键值对象的编码。
1 | 127.0.0.1:6379> set testKey testValue |
通过encoding属性来设定对象所使用的编码,提高了Redis的灵活性。例如在列表元素比较少的时候,Redis会使用压缩列表来作为底层实现,因为此时压缩列表比链表更加节省内存,当元素越来越多时,则会转换为功能更强大的双向链表来实现。
Redis在自己的对象系统中使用了引用计数的方法来实现内存回收。通过这一机制,程序可以通过对象的引用计数信息,在适当的时候释放对象并进行垃圾回收。refcount属性记录了引用计数的数量,当创建一个新对象时,该值会初始化为1,当该对象被一个新的程序使用时,会增1,不再被一个程序使用时减1。当调用decrRefCount函数使其值为1时,会释放该对象。在Redis中,可以使用OBJECT REFCOUNT命令来查看对象的引用计数。
1 | 127.0.0.1:6379> SET a 100 |
Redis在初始服务器的时候,会创建10000个字符串对象,其值为0-9999中所有的整数值。当需要用到这些对象的时候,会共享这些对象而不是新创键对象,这些值的引用计数会固定为2147483647。
lru属性记录了对象最后一次被命令程序访问的时间,通过OBJECT IDLETIME命令可以打印给定键的空转时长。如果服务器打开了maxmemory选项,当服务器占用的内存超过了所设定的上限值,空转时长比较长的那部分键会优先被释放,从而回收内存。
字符串对象
字符串的编码方式可以为int,raw或者embstr。
如果一个字符串保存的对象为整数值,并且该对象可以用long类型来表示,那么该字符串对象会将整数值保存在ptr属性中。
如果一个字符串保存的对象为一个字符串值,如果该字符串长度大于44字节(不同Redis版本该值不同),那么会使用SDS来保存这个字符串值,并将编码设置为raw。否则会使用embstr编码的方式来保存这个字符串值。
embstr编码专门用于保存短字符串。其与raw编码的区别在于SDS和RedisObject的内存布局是否在一起。embstr将SDS和RedisObject合成一块连续的内存布局,其示意图如下:
采用embstr有以下好处
内存分配与释放次数由两次变为一次
所有数据保存在连续的内存之中,能够更好地利用缓存。
对一个int编码的字符串执行了命令使其不再是一个整数值或者对embstr对象执行任何修改命令时,Redis会将这些对象的编码转化为raw。
1 | 127.0.0.1:6379> SET number 1 |
列表对象
列表对象的编码可以为ziplist或者linkedlist。
当同时满足以下条件时,使用ziplist作为底层实现:
- 列表对象保存的所有字符串长度都小于64字节
- 保存的元素个数小于512个
哈希对象
列表对象的编码可以为ziplist或者hashtable。
当同时满足以下条件时,使用ziplist作为底层实现:
- 哈希对象保存的所有键值对的键和值的字符串长度都小于64字节
- 哈希对象保存的键值对数量小于512个
集合对象
集合对象的编码可以为intset或者hashtable。
当同时满足以下条件时,使用intset作为顶层实现:
- 集合对象的所有元素都是整数值
- 集合对象保存的元素个数不超过512个
有序集合对象
有序对象的编码可以为ziplist和skiplist。
在使用skiplist作为底层实现的时候,Redis会同时将元素保存到字典和跳跃表中。其中dict字典为有序集合创建了一个从成员到分值的映射,字典的键保存了成员,值保存了元素。通过该字典,使得程序可以通过O(1)的复杂度查找到给定成员的分值。
当有序集合满足以下两个条件时,对象使用ziplist
- 有序集合保存的元素个数小于128个
- 有序集合保存的所有元素个数都小于64字节