redisObject结构
实际上每一个redis都是一个redisObject对象。redis对象的类型检查,内存回收,对象共享都是基于redisObject完成的,下面来看一下redisObject的结构。
typedef struct redisObject {
// 类型
unsigned type:4;
// 对齐位
unsigned notused:2;
// 编码方式
unsigned encoding:4;
// LRU 时间(相对于 server.lruclock)
unsigned lru:22;
// 引用计数
int refcount;
// 指向对象的值
void *ptr;
} robj;
复制代码
- type:对象类型。
- encoding: 对象编码方式。
- lru: 对象的最近使用时间,配合内存回收使用。
- refcount: 引用次数。配合内存回收使用。
- ptr: 指向具体底层数据结构的指针。
redisObject是这样的结构主要是为了方便redis灵活的切换对象的编码及实时的回收redis内失效的内存,防止内存泄漏。
五大类型对象
redis的五大数据对象分别是字符串、列表、集合、有序集合、哈希表,下面来分别介绍。
字符串
字符串对象一共有三种实现——整数、SDS(简单动态字符串)和embstr编码的SDS。 当字符串的长度小于44时,会使用embstr编码的SDS,大于44时会使用SDS。这里主要有两个问题,一是什么是embstr编码,二是为什么偏偏要选择44为临界点。 <1> embstr编码 首先需要了解redisObject对象,如下图所示,一般的string的redisObject在内存中是以这样的形式存在的,需要分配两块空间,且要分配两次。
<2> 44字节 从2.4版本开始,redis开始使用jemalloc内存分配器,jemalloc会分配8,16,32,64等字节的内存。embstr由redisObject和sds组成,其中redisObject有16个字节,如果embstr有44个字节,则sds的长度为44+3+1=48,加起来刚好为64字节。
列表
当列表对象可以同时满足以下两个条件时, 列表对象使用 ziplist 编码:
- 列表对象保存的所有字符串元素的长度都小于 64 字节;
- 列表对象保存的元素数量小于 512 个;
当这两个条件中的一个不满足时,将使用双端列表,而且这种变化是动态的。
哈希表
当哈希表对象满足ziplist编码的条件时,会使用ziplist作为底层数据结构,当不满足条件时,会使用字典作为底层数据结构。
集合
当集合对象可以同时满足以下两个条件时, 对象使用 intset 编码:
- 集合对象保存的所有元素都是整数值;
- 集合对象保存的元素数量不超过 512 个; 当这两个条件中的一个不满足时,将使用hashtable,而且这种变化是动态的。
有序集合
当集合对象可以同时满足以下两个条件时, 对象使用 ziplist编码:
- 列表对象保存的所有字符串元素的长度都小于 64 字节;
- 列表对象保存的元素数量小于 512 个;
类型检查和命令多态
redis会对命令进行检查,以确保命令被正确的运用到正确的对象之上,当命令和对象不匹配时,会向客户端返回一个错误,例如当要对一个字符串执行自增操作时,就会返回一个错误。下表显示了这种匹配关系。
SET 、 GET 、 APPEND 、 STRLEN 等命令只能对字符串键执行; HDEL 、 HSET 、 HGET 、 HLEN 等命令只能对哈希键执行; RPUSH 、 LPOP 、 LINSERT 、 LLEN 等命令只能对列表键执行; SADD 、 SPOP 、 SINTER 、 SCARD 等命令只能对集合键执行; ZADD 、 ZCARD 、 ZRANK 、 ZSCORE 等命令只能对有序集合键执行;
上面说过,redis的对象都是redisObject,类型检查就是通过redisObject的type属性完成的。同时依赖于type属性,redis还可以实现命令的多态,即相同的命令作用的不同的对象上,执行不同的操作。例如当对整数型字符串执行append操作时,redis会首先将值转化为sds,之后执行append操作,而对sds型的字符串执行append操作时,则直接执行append操作。
内存回收
redis采用了引用计数法来回收内存,在redisObject结构中有一个refCount属性便是为此服务,当创建对象时,refCount置为1,当对象被引用则refCount+1,不被引用则refCount-1,当refCount为0时,对象所占用的内存就会被回收。
内存共享
redis 会在初始化服务器时, 创建一万个字符串对象, 这些对象包含了从 0 到 9999 的所有整数值, 当服务器需要用到值为 0到 9999 的字符串对象时, 服务器就会使用这些共享对象, 而不是新创建对象。
对象空转时间
redisObject的lru属性记录了对象最后一次被命令程序访问的时间,此属性主要是为了配合redis的回收内存算法,例如volatile-lru 或者 allkeys-lru,当内存超过设置的maxmemory时,会配合回收算法计算要被回收的内存。