实际上每个redis都是一个redisObject对象。redis对象的类型检查,内存回收,对象共享都是基于redisObject完成的,下面来看一下redisObject的结构。redis
typedef struct redisObject {
// 类型
unsigned type:4;
// 对齐位
unsigned notused:2;
// 编码方式
unsigned encoding:4;
// LRU 时间(相对于 server.lruclock)
unsigned lru:22;
// 引用计数
int refcount;
// 指向对象的值
void *ptr;
} robj;
复制代码
redisObject是这样的结构主要是为了方便redis灵活的切换对象的编码及实时的回收redis内失效的内存,防止内存泄漏。算法
redis的五大数据对象分别是字符串、列表、集合、有序集合、哈希表,下面来分别介绍。缓存
字符串对象一共有三种实现——整数、SDS(简单动态字符串)和embstr编码的SDS。 当字符串的长度小于44时,会使用embstr编码的SDS,大于44时会使用SDS。这里主要有两个问题,一是什么是embstr编码,二是为何恰恰要选择44为临界点。 <1> embstr编码 首先须要了解redisObject对象,以下图所示,通常的string的redisObject在内存中是以这样的形式存在的,须要分配两块空间,且要分配两次。 bash
<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 编码:数据结构
当这两个条件中的一个不知足时,将使用双端列表,并且这种变化是动态的。app
当哈希表对象知足ziplist编码的条件时,会使用ziplist做为底层数据结构,当不知足条件时,会使用字典做为底层数据结构。 大数据
当集合对象能够同时知足如下两个条件时, 对象使用 intset 编码:ui
当集合对象能够同时知足如下两个条件时, 对象使用 ziplist编码:编码
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时,会配合回收算法计算要被回收的内存。