• 对象
  • 结构
  • type取值
  • encondig取值和对应的ptr类型
  • 对象和编码的对应关系(一对多)
  • 对象共享
  • 对象的空转时长
  • 部分对象实现要点
  • 各个对象encoding选择的策略
  • 字符串对象
  • 列表对象
  • 哈希对象
  • 集合对象
  • 有序集合
  • 内存回收策略


对象

redis将内部的数据结构封装,包含在redis对象中,redis有五种对象:字符串对象、列表对象、哈希对象、集合对象和有序集合对象。每一种对象对应底层的至少一种数据结构。也就是说,同一种对象,可能内部有多种实现,这是受数据量影响的策略。
redis键值对的键总是一个字符串对象。

结构

typedef struct redisObject{
    unsigned type:4;//表明是哪一种对象
    unsigned encoding:4;//表明这种对象到底是由什么数据结构实现的
    void * ptr;//底层对应的数据结构
    // others
    int refcount;//用于垃圾回收的引用计数
    unsigned lru:22;//对象空转时长,用于记录对象最后一次被访问的时间
}
type取值
  • REDIS_STRING:字符串对象
  • REDIS_LIST:列表对象
  • REDIS_HASH:哈希对象
  • REDIS_SET:集合对象
  • REDIS_ZSET:有序集合对象
encondig取值和对应的ptr类型
  • REDIS_ENCODING_INT:long类型整数
  • REDIS_ENCODING_EMBSTR:embstr编码的简单动态字符串,和下面的raw不同的是,embstr申请一次内存空间将redisObject和sds连续存放,而raw是分两次申请内存,分别存放redisObject和sds,所以embstr的好处有降低内存申请和销毁次数,能更好利用缓存。但是带来的缺点是,embstr是只读的,对它的操作会将它转变位raw类型
  • REDIS_ENCODING_RAW:简单动态字符串
  • REDIS_ENCODING_HT:字典
  • REDIS_ENCODING_LINKEDLIST:双向链表
  • REDIS_ENCODING_ZIPLIST:压缩列表
  • REDIS_ENCODING_INTSET:整数集合
  • REDIS_ENCODING_SKIPLIST:跳跃表和字典
对象和编码的对应关系(一对多)

type

enconding

说明

REDIS_STRING

REDIS_ENCODING_INT,REDIS_ENCODING_EMBSTR,REDIS_ENCODING_RAW

使用整型数,embstr编码的简单动态字符串,简单动态字符串,实现的字符串对象

REDIS_LIST

REDIS_ENCODING_ZIPLIST,REDIS_ENCODING_LINKEDLIST

使用压缩列表,双向链表实现的列表对象对象

REDIS_HASH

REDIS_ENCODING_ZIPLIST,REDIS_ENCODING_HT

使用压缩列表,字典实现的哈希对象

REDIS_SET

REDIS_ENCODING_INTSET,REDIS_ENCODING_HT

使用整数集合,字典实现的集合对象

REDIS_ZSET

REDIS_ENCODING_ZIPLIST,REDIS_ENCODING_SKIPLIST

使用压缩列表,跳表和字典实现的有序集合对象

对象共享

redis会让被多个键引用的整数类型的字符串值被多个键共享,通过引用计数来控制回收,同时redis会在初始化服务区的时候,创建从0到9999的一万个字符串对象,用来共享。
共享可以提高内存的使用效率,但是为什么只针对整数值的字符串共享呢?这是为了平衡内存利用率和CPU性能消耗两方面:因为判断对象是相等的,是可以共享的需要CPU计算,对整数型的字符串,这种计算是o(1)复杂度(转化为数字),而其他字符串,复杂度是o(n),对于更复杂的对象,则更高,权衡之下,只允许整数型的字符串共享。

对象的空转时长

lru,这是用于记录该对象最后一次被访问的时间的,通过命令OBJECT IDLETIME命令可以看到这个信息,同时这个命令不会令lru属性变化。通过这个属性,在特定设置下,可以帮助服务器回收无用的过期对象。

部分对象实现要点

  • 对于用压缩列表实现的哈希对象:使用压缩列表存储键值对时,键和值前后排列,新的键值对放到列表的末尾。
  • 对于用压缩列表实现的有序集合:和哈希对象类似,也是将元素成员(member)和元素分值(score)顺序存放,并且多个元素按分数的升序排列
  • 对于用跳表和字典实现的有序集合:底层使用zset的结构体:
    c
    typedef struct zset{
    zskiplist *zsl;
    dict *dict;
    }

    其中,zskiplist保存一份数据用于计算rank,排序,范围查询,而dict保存一份数据用于精确查找。当然,这里说保留一份数据并不是指需要同时存在两份相同的数据,数据通过指针在zskiplist和dict之间是共享的,只是二者结构上相对独立

各个对象encoding选择的策略

字符串对象

当字符串保存的是整型值,且没超过long的上限,则使用long型整数;
当保存的是大于32个字节的字符串或者是浮点数时,则使用sds;
当保存的是小于等于32个字节的字符串或者较小的浮点数时,则使用embstr编码的sds。

列表对象

(1)当列表对象内所有元素长度小于64字节,并且(2)元素个数小于512个时,使用压缩列表,否则使用双向链表。
不过这两个条件可以通过修改redis配置文件的list-max-ziplist-valuelist-max-ziplist-entries而调节

哈希对象

和列表对象的条件一样,当(1)键值对的键和值都小于64个字节并且(2)键值对数目小于512个的时候,使用压缩列表存储,否则使用字典。
这两个条件也可以通过配置hash-max-ziplist-valuehash-max-ziplist-entries修改。

集合对象

当集合对象满足(1)所有元素都是整数值并且(2)元素个数小于512个时,使用整数集合,否则使用字典。
元素个数的条件也可以通过配置set-max-intset-entries调节。

有序集合

当集合满足(1)保存的元素数量小于128个,并且(2)所有元素的长度都是小于64字节,则使用压缩列表,否则使用跳表和字典。
可以使用配置zset-max-ziplist-entrieszset-max-ziplist-value来修改条件限制

内存回收策略

redis使用redisObject的refcount来实现引用计数方式的内存回收,以下几点说明:
1. 当创建一个对象的时候,refcount=1;
2. 当对象被一个新的程序使用的时候,refcount++;
3. 当对象不再被一个程序使用的时候,refcount–;
4. 当对象的refcount==0的时候,会释放对象占用的内存