0%

Redis中的对象系统

Redis中的对象系统包含字符串对象,列表对象,集合对象,有序集合对象,哈希对象这五种类型。同时在Redis中还实现了基于引用计数的内存回收机制和对象共享机制。Redis中的对象带有访问时间记录信息,能够用于计算数据库键的空转时常。

对象类型

Redis使用对象来表示数据库中的键和值。每次新创键一个键值对时,会创建两个对象,一个用作键,一个用作值。

对象类型结构

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct redisObject {
// 类型
unsigned type:4;
// 编码
unsigned encoding:4;
// 对象最后一次被访问的时间
unsigned lru:REDIS_LRU_BITS;
// 引用计数
int refcount;
// 指向实际值的指针
void *ptr;
} robj;

其中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
2
3
4
127.0.0.1:6379> set testKey testValue                                           
OK
127.0.0.1:6379> OBJECT ENCODING testKey
"embstr"

通过encoding属性来设定对象所使用的编码,提高了Redis的灵活性。例如在列表元素比较少的时候,Redis会使用压缩列表来作为底层实现,因为此时压缩列表比链表更加节省内存,当元素越来越多时,则会转换为功能更强大的双向链表来实现。

Redis在自己的对象系统中使用了引用计数的方法来实现内存回收。通过这一机制,程序可以通过对象的引用计数信息,在适当的时候释放对象并进行垃圾回收。refcount属性记录了引用计数的数量,当创建一个新对象时,该值会初始化为1,当该对象被一个新的程序使用时,会增1,不再被一个程序使用时减1。当调用decrRefCount函数使其值为1时,会释放该对象。在Redis中,可以使用OBJECT REFCOUNT命令来查看对象的引用计数。

1
2
3
4
5
6
7
8
127.0.0.1:6379> SET a 100                                                       
OK
127.0.0.1:6379> OBJECT REFCOUNT a
(integer) 2147483647
127.0.0.1:6379> SET a 'test'
OK
127.0.0.1:6379> OBJECT REFCOUNT a
(integer) 1

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编码与raw编码

采用embstr有以下好处

  • 内存分配与释放次数由两次变为一次

  • 所有数据保存在连续的内存之中,能够更好地利用缓存。

对一个int编码的字符串执行了命令使其不再是一个整数值或者对embstr对象执行任何修改命令时,Redis会将这些对象的编码转化为raw。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
127.0.0.1:6379> SET number 1                                                    
OK
127.0.0.1:6379> OBJECT ENCODING number
"int"
127.0.0.1:6379> APPEND number 'a'
(integer) 2
127.0.0.1:6379> OBJECT ENCODING number
"raw"

127.0.0.1:6379> SET s "a"
OK
127.0.0.1:6379> OBJECT ENCODING s
"embstr"
127.0.0.1:6379> APPEND s "b"
(integer) 2
127.0.0.1:6379> OBJECT ENCODING s
"raw"

列表对象

列表对象的编码可以为ziplist或者linkedlist。

当同时满足以下条件时,使用ziplist作为底层实现:

  • 列表对象保存的所有字符串长度都小于64字节
  • 保存的元素个数小于512个

哈希对象

列表对象的编码可以为ziplist或者hashtable。

当同时满足以下条件时,使用ziplist作为底层实现:

  • 哈希对象保存的所有键值对的键和值的字符串长度都小于64字节
  • 哈希对象保存的键值对数量小于512个

集合对象

集合对象的编码可以为intset或者hashtable。

当同时满足以下条件时,使用intset作为顶层实现:

  • 集合对象的所有元素都是整数值
  • 集合对象保存的元素个数不超过512个

有序集合对象

有序对象的编码可以为ziplist和skiplist。

在使用skiplist作为底层实现的时候,Redis会同时将元素保存到字典和跳跃表中。其中dict字典为有序集合创建了一个从成员到分值的映射,字典的键保存了成员,值保存了元素。通过该字典,使得程序可以通过O(1)的复杂度查找到给定成员的分值。

当有序集合满足以下两个条件时,对象使用ziplist

  • 有序集合保存的元素个数小于128个
  • 有序集合保存的所有元素个数都小于64字节