Redis 对象

一、对象的类型与编码

Redis 的键总是字符串类型,值是五种对象类型之一。

字符串键:这个键对应的值为字符串对象。

列表键:这个键对应的值为列表对象。

1、对一个数据库的键执行 type 命令时,返回的结果是值对象的类型。

对象 对象 type 属性的值 type 命令的输出
字符串对象 REDIS_STRING "string"
列表对象 REDIS_LIST "list"
哈希对象 REDIS_HASH "hash"
集合对象 REDIS_SET "set"
有序集合对象 REDIS_ZSET "Zset"

Redis 构造了五种对象类型:

字符串对象,列表对象,哈希对象,集合对象和有序集合对象。

每一个对象都由一个 RedisObject 结构表示。

typedef struct redisObject{
    //类型
    unsigned type:4;
    //编码
    unsigned encoding:4;
    //指向底层实现数据结构的指针
    void *ptr;
}robj;

2、编码和底层实现

encoding 属性决定了对象的底层数据结构,ptr指针指向该数据结构。总共有8 种编码

编码常量 对应的底层数据结构
REDIS_ENCODING_INT long 类型整数
REDIS_ENCODING_EMBSTR emstr 编码的动态字符串
REDIS_ENCODING_RAW 简单动态字符串
REDIS_ENCODING_HT 字典
REDIS_ENCODING_LINKEDLIST 双端链表
REDIS_ENCODING_ZIPLIST 压缩列表
REDIS_ENCODING_INTSET 整数集合
REDIS_ENCODING_SKIPLIST 跳跃表和字典

每种类型的对象至少使用了两种不同的编码。每种类型可以使用的类型如下:

RE类型 编码 对象
REDIS_STRING REDIS_ENCODING_INT 使用整数值实现的字符串对象
REDIS_STRING REDIS_ENCODING_EMBSTR 使用 embstr编码实现的字符串对象
REDIS_STRING REDIS_ENCODING_RAW 使用简单动态字符串实现的字符串对象
REDIS_LIST REDIS_ENCODING_ZIPLIST 使用压缩列表实现的列表对象
REDIS_LIST REDIS_ENCODING_LINKEDLIST 使用双端列表实现的列表对象
REDIS_HASH REDIS_ENCODING_ZIPLIST 使用压缩列表实现的哈希对象
REDIS_HASH REDIS_ENCODING_HT 使用字典实现的哈希对象
REDIS_SET REDIS_ENCODING_INTSET 使用整数集合实现的集合对象
REDIS_SET REDIS_ENCODING_HT 使用字典表实现的集合对象
REDIS_ZSET REDIS_ENCODING_ZIPLIST 使用压缩表实现的有序集合对象
REDIS_ZSET REDIS_ENCODING_SKIPLIST 使用跳跃表和字典表实现的有序集合对象

使用 OBJECT ENCODING 命令可以查看一个数据库值对象的编码。

二、字符串对象

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

embstr 编码是专门用于保存短字符串的一种优化编码方式。

区别是:

  • raw 编码会调用两次内存分配函数来创建 RedisObject 结构和 sdshdr 结构
  • 而 embstr 编码只需要调用一次内存分配函数,释放时也是一样

long double类型表示的浮点数,可以用字符串来保存的。

编码的转换

  • int 加上一个字符串,就会整体转换为 raw 编码
  • embstr 编码实际是只读的,修改之后总会变成 raw

三、列表对象

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

ziplist 结构:

linkedlist结构:

linkedlist 编码的列表对象,在底层双端列表中包含了多个字符串对象。

当 ziplist 使用条件不被满足时,对象就会转换编码成 linkedlist。

四、哈希对象

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

ziplist 编码:

hashtable 编码:

两种编码可以转换。

五、集合对象

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

intset 编码:

hashtable:
两种编码可以转换

六、有序集合对象

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

ziplist 编码:

  • 每个集合元素使用两个相邻的压缩列表节点来保存。第一个保存成员,第二个保存分值。
  • 集合按照分值从小到大排序。

skiplist编码的有序集合使用zset 结构作为底层实现,为了能快速实现查找和范围类型操作,一个 zset 同时包含一个字典和一个跳跃表。

zset 中的跳跃表按照分值从小到大保存了所有集合元素。每个跳跃表节点都保存了一个集合元素:object 保存成员,score 保存元素分值。跳跃表使得程序可以对有序集合进行范围操作。

zset结构中的字典为有序集合创建了一个从成员到分值的映射。键保存成员,值保存元素的分值。字典使得程序可以用 O(1)复杂度查找给定成员的分值。

有序集合的成员都是字符串对象,分值都是 double 类型的浮点数。

zset 中的跳跃表和字典通过指针来共享元素和分值,所以不会浪费内存。


两种编码可以转换。

七、类型检查与命令多态

  • DEL、EXPIRE、RENAME、TYPE、OBJECT 适用所有类型键
  • SET、GET、APPEND、STRLEN 适用字符串键
  • HDEL、HSET、HGET、HLEN 适用哈希键
  • RPUSH、LPOP、LINSERT、LLEN 适用列表键
  • SADD、SPOP、SINTER、SCARD 适用集合键
  • ZADD、ZCARD、ZRANK、ZSCORE 适用有序集合键

类型检查是通过 RedisObject 的 type 属性来实现的。

八、内存回收

  • 在创建一个新对象时,引用计数的值会被初始化为 1
  • 当对象被一个新程序使用时,他的引用计数值会被增 1
  • 当对象不再被一个程序使用时,引用计数值减 1
  • 当引用计数值变为 0 时,对象所占用的内存会被释放

九、对象共享

  • 将数据库键的值指针指向一个现在的值对象
  • 将被共享的值对象引用计数加 1

共享对象对于节约内存非常有帮助。

Redis 会共享0-9999 的字符串对象。

十、对象的空转时长

对象的 lru 属性记录了对象最后一次被命令程序访问的时间。

OBJECT IDLETIME可以打印出给定键的空转时长。这一空转时长就是通过将当前时间减去键对象的 lru 时间计算得出的。