redis对象

redis并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统,这个系统包含字符串对象、列表对象、哈希对象、集合对象以及有序集合对象这五种类型的对象,每种对象都用到了至少一种redis中的数据结构。

通过这五种不同类型的对象,redis可以在执行命令前,根据对象的类型来判断一个对象是否可以执行给定的命令。使用对象的另一个好处就是,我们可以针对不同的使用场景,对对象设置多种不同的数据结构实现,从而优化对象在不同使用场景下的效率。除此之外,redis对象系统还实现了基于引用计数技术的内存回收机制,当程序不再使用某个对象时,这个对象所占用的内存就会被自动释放;另外,redis还通过引用计数技术实现了对象共享机制,这一机制可以在适当的条件下,通过让多个数据库键共享同一个对象来节约内存。最后,redis的对象带有访问时间记录信息,该信息可以用于计算数据库键的空转时长,在服务器启用了maxmemory功能的情况下,空转时长较大的那些键可能会优先被服务器删除。

redis中的每一个对象都由一个redisObject结构表示,该结构中和保存数据有关的三个属性分别是type属性、encoding属性和ptr属性。

1
2
3
4
5
6
7
8
9
10
11
typedef struct redisObject{
//类型
unsigned type:4;
//编码
unsigned encoding:4;
//指向数据的指针,如果数据为整数的话,ptr直接存储该整数的值
void* ptr;

...

} robj;

对象的类型有以下几种:

REDIS_STRING: 字符串对象 : "string"
REDIS_LIST: 列表对象 : "list"
REDIS_HASH: 哈希对象 : "hash"
REDIS_SET: 集合对象  : "set"
REDIS_ZSET: 有序集合对象  : "zset"

在redis中,所有键的类型都是字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
redis> SET msg "hello world"
OK

redis> TYPE msg
string

redis> RPUSH number 1 3 5
OK

redis> TYPE number

lsit

...

encoding 属性记录对象所使用的编码,也就是对象使用了什么数据结构作为底层的实现。encoding 的类型有以下几种:

REDIS_ENCODING_INT : long 类型的整数 :"int"
REDIS_ENCODING_EMBSTR : embstr编码的简单动态字符串 : "embstr"
REDIS_ENCODING_RAW : 简单动态字符串 : "raw"
REDIS_ENCODING_HT : 字典 : "hashtable"
REDIS_ENCODING_LINKEDLIST : 双端链表 : linkedlist
REDIS_ENCODING_ZIPLIST : 压缩列表 : "ziplist"
REDIS_ENCODING_INTSET : 整数集合 : "inset"
REDIS_ENCODING_SKIPLIST : 跳跃表和字典 : "skiplist"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
redis> SET msg "hello world"
OK

redid> OBJECT ENCODING msg
"embstr"

redis> SET story "long long long ago"
OK

redis> OBJECT ENCODING story
"raw"(SDS)

redis> SADD number 1 3 5
(integer) 3

redis> OBJECT ENCODING number
"inset"

redis> SADD number "seven"
(integer) 1

redis> OBJECT ENCODING number
"hashtable"

通过encoding属性来设定对象所使用的编码,而不是为特定类型的对象关联一种固定的编码,极大的提高了redis的灵活性和效率,因为redis可以根据不同使用场景来为一个对象设置不同的编码,从而优化对象在特定场合的效率。
例如:在列表对象包含的元素比较少的时候,redis使用压缩类表作为列表对象的底层实现:

  1. 因为压缩列表比双端列表更节约内存,而且在元素数量比较少的时候,在内存中以连续块方式保存的压缩列表比起双端列表可以更快的被载入到缓存中。
  2. 随着列表对象包含的元素越来越多,使用压缩列表来保存元素的优势逐渐消失,对象就会将底层的实现从压缩列表改为功能呢个更强大、适合保存大量元素的双端链表上面。

有序集合对象的底层数据实现

redis的有序集合对象zset结构使用了两种数据结构:跳表和字典。跳表来实现有序集合,可以保存执行范围类型操作的所有优点,也可以实现集合的有序,但是对于根据成员key来查找value,将会由于二分查找而时间复制度变为O(logN), 使用字典来保存所有的key-value对的话,那么既可以将查找的时间复杂度降为O(1)。为了实现高效率,有序集合就采用了跳表和字典来作为底层的数据实现。

对象共享

目前来说,redis会在初始化服务器时,创建一万个字符串对象,这些对象包含了从0到9999的所有整数值,当服务器需要用到这些字符串对象的时候,服务器就会使用这些共享对象,而不是创建新对象。另外,这些共享对象不仅只有字符串键可以使用,那些在数据结构中嵌套了字符对象的对象,都可以使用这邪恶共享对象。

redis只共享包含整数值的字符串对象,而不共享包含字符串的对象,是因为对象越复杂时,共享的时候需要检查的东西越多,消耗的CPU时间就越长。如果共享独享是一个保存整数值的字符串,那么需要比较的复杂度为O(1),如果保存的是一个字符串,需要的复杂度为O(n),如果是包含多个值的对象,那么需要的时间复杂度为O(N^2)。所以为了效率的考虑,redis目前只对包含整数值的字符串对象进行共享。