redis并没有直接使用我们之前介绍的那些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统,这个系统包含字符串对象、列表对象、哈希对象、集合对象、有序集合对象五种类型的对象,每种对象都用到了至少一种我们前面所介绍的数据结构。通过这五种不同类型的对象,redis可以在执行命令之前,根据对象的类型来判断一个对象是否可以执行给定的命令。除此之外,redis的对象系统还实现了基于引用计数计数的内存回收机制,当程序不再使用某个对象的时候,这个对象所占用的内存就会被自动释放;另外,redis还通过引用计数计数实现了对象共享机制,这一机制可以在适当的条件下,通过多个数据库键共享同一个对象来节约内存。最后redis的对象带有访问时间记录信息,该信息可以用于计算数据库键的空转时长。
对象类型及编码

typedef struct redisObject {

    // 类型
    unsigned type:4;

    // 编码
    unsigned encoding:4;

    // 对象最后一次被访问的时间
    unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */

    // 引用计数
    int refcount;

    // 指向实际值的指针
    void *ptr;

} robj

每次当我们在redis的数据库中新创建一个键值对时,我们至少会创建两个对象,一个对象用作键值对的键,另一个对象用作键值对的值。

type:类型

redis 替代共享内存 redis 共享对象_redis

ptr:指向对象的底层实现数据结构,而这些数据结构仅有对象的encoding属性决定

encoding:记录了对象所使用的编码

redis 替代共享内存 redis 共享对象_引用计数_02


每种类型的对象都至少使用了两种不同的编码

redis 替代共享内存 redis 共享对象_引用计数_03


字符串对象

字符串对象的编码可以是int、raw、embstr

如果一个字符串对象保存的时整数值,并且这个数值可以用long类型来表示,那么字符串对象会将整数值保存在字符串对象结构的ptr属性里面,并将字符串对象的编码设置为int。

如果字符串对象保存的是一个字符串值,并且这个字符串值的长度大于32字节,那么字符串对象将使用一个简单动态字符串来保存这个字符串值,并将对象的编码设置为raw。

如果字符串对象保存的是一个字符串值,并且这个字符串值的长度小于等于32字节,那么字符串对象将使用embstr编码的方式来保存这个二字符串值。

embstr编码是专门用于保存短字符串的一种优化编码方式,raw编码会调用两次内存分配函数来分别创建redisObject结构和sdshdr结构,而embstr编码则通过调用一次内存分配函数来分配一块连续的空间,空间一次包含redisObject和sdshdr。

列表对象

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

ziplist编码的列表对象使用压缩列表作为底层实现,每个压缩列表节点保存了一个列表元素

redis 替代共享内存 redis 共享对象_redis 替代共享内存_04

linkedlist编码的列表对象使用双端链表作为底层实现,每个双端链表节点都保存了一个字符串对象,而每个字符串对象都保存了一个列表元素

redis 替代共享内存 redis 共享对象_redis_05

当列表对象可以同时满足以下两个条件时,列表对象使用ziplist编码:

1、列表对象保存的所有字符串元素的长度都小于64字节

2、列表对象保存的元素数量小于512个;不能满足这两个条件的列表对象需要使用linkedlist编码

哈希对象

哈希对象的编码可以是ziplist或者hashtable

ziplist编码的哈希对象使用压缩列表作为底层实现,每当有新的键值对要加入到哈希对象时,程序会先将保存了键的压缩列表节点推入到压缩列表表尾,然后再将保存了值的压缩列表节点推入到压缩列表的表尾

redis 替代共享内存 redis 共享对象_redis 替代共享内存_06


redis 替代共享内存 redis 共享对象_redis 替代共享内存_07


hashtable编码的哈希对象使用字典作为底层实现:

redis 替代共享内存 redis 共享对象_字符串_08

当哈希对象可以同时满足以下两个条件时,哈希对象使用ziplist编码:

1、哈希对象保存的所有键值对的键和值的字符串长度都小于64字节

2、哈希对象保存的键值对数量小于512个

集合对象

集合对象的编码可以是inset或者hashtable

redis 替代共享内存 redis 共享对象_redis_09


当集合对象可以同时满足以下两个条件时,对象使用inset编码:

1、集合对象保存的所有元素都是整数值

2、集合对象保存的元素数量不超过512个

有序集合对象

有序集合对象的编码可以是ziplist或者skiplist

skiplist编码的有序集合对象使用zset结构作为底层实现,一个紫色调结构同时包含一个字典和一个跳跃表

redis 替代共享内存 redis 共享对象_引用计数_10


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

1、有序集合保存的元素数量小于128个

2、有序集合保存的所有元素成员的长度都小于64字节

类型检查与命令多态

redis中用于操作键的命令基本可以分为两种类型

1、可以对任何类型键执行

2、只能对特定类型的键执行

类型检查的实现

类型特定命令锁进行的类型检查是通过redisObject结构的type属性来实现的:

1、在执行一个类型特定命令前,服务器会先检查输入数据库键的值对象是否为执行命令所需类型,如果是的话, 服务器就对键执行指定的命令

2、否则,服务器将拒绝执行命令,并向客户端返回一个类型错误

redis 替代共享内存 redis 共享对象_redis_11


多态命令的实现

redis除了会根据值对象的类型来判断键是否能够执行指定命令之外,还会根据值对象的编码方式,选择正确的命令实现代码来执行命令

redis 替代共享内存 redis 共享对象_字符串_12


内存回收

redis在自己的对象系统中构建了一个引用计数技术实现的内存回收机制,通过这一机制,程序可以通过跟踪对象的引用计数信息,在适当的时候自动释放对象并进行内存回收。

对象的引用计数信息会随着对象的使用状态而不断变化:

1、在创建一个新对象时,引用计数的值湖北初始化为1

2、当一个新程序使用时,它的引用计数值会被赠一

3、当对象不再被一个程序使用时,它的引用计数值会被减一

4、当对象的引用计数值变为0时,对象所占用的内存会被释放

对象共享

除了用于实现引用计数内存回收机制之外,对象的引用计数属性还带有对象的共享作用。目前来说,redis会在初始化服务器时,创建一万个字符串对象,这些对象包含了从0到9999的所有整数值,当服务器需要用到值为0到9999的字符串对象时,服务器就会使用这些共享对象。

对象的空转时长:lru记录了对象最后一次被命令程序访问的时间。