RedisObject 数据结构

Redis类型系统

在 Redis 的命令中, 用于对键 (key) 进行处理的命令占了很大一部分, 而对于键所保存的值的类型, 键能执行的命令又各不相同.

比如说, LPUSH 和 LLEN 只能用于列表键, 而 SADD 和 SRANDMEMBER 只能用于集合键, 等等.

另外一些命令, 比如 DEL、 TTL 和 TYPE, 可以用于任何类型的键, 但是, 要正确实现这些命令, 必须为不同类型的键设置不同的处理方式: 比如说, 删除一个列表键和删除一个字符串键的操作过程就不太一样.

以上的描述说明, Redis 必须让每个键都带有类型信息, 使得程序可以检查键的类型, 并为它选择合适的处理方式.

比如说, 集合类型就可以由字典和整数集合两种不同的数据结构实现, 但是, 当用户执行 ZADD 命令时, 他/她应该不必关心集合使用的是什么编码, 只要 Redis 能按照 ZADD 命令的指示, 将新元素添加到集合就可以了。

这说明, 操作数据类型的命令除了要对键的类型进行检查之外, 还需要根据数据类型的不同编码进行多态处理.

为了解决以上问题, Redis 构建了自己的类型系统, 这个系统的主要功能包括:

  • redisObject 对象.
  • 基于 redisObject 对象的类型检查.
  • 基于 redisObject 对象的显式多态函数.
  • 对 redisObject 进行分配、共享和销毁的机制.

redisObject 是 Redis 类型系统的核心, 数据库中的每个键、值, 以及 Redis 本身处理的参数, 都表示为这种数据类型.

/*
 * Redis 对象
 */
typedef struct redisObject {
    // 类型
    unsigned type:4;
    // 对齐位
    unsigned notused:2;
    // 编码方式
    unsigned encoding:4;
    // LRU 时间(相对于 server.lruclock)
    unsigned lru:22;
    // 引用计数
    int refcount;
    // 指向对象的值
    void *ptr;
} robj;

最重要的三个属性

type

记录了对象所保存的值的类型, 它的值可能是以下常量的其中一个.

/*
 * 对象类型
 */
#define REDIS_STRING 0   // 字符串
#define REDIS_LIST 1     // 列表
#define REDIS_SET 2      // 集合
#define REDIS_ZSET 3     // 有序集
#define REDIS_HASH 4     // 哈希表

encoding

记录了对象所保存的值的编码, 它的值可能是以下常量的其中一个.

/*
 * 对象编码
 */
#define REDIS_ENCODING_RAW 0         // 编码为字符串
#define REDIS_ENCODING_INT 1         // 编码为整数
#define REDIS_ENCODING_HT 2          // 编码为哈希表
#define REDIS_ENCODING_ZIPMAP 3      // 编码为 zipmap
#define REDIS_ENCODING_LINKEDLIST 4  // 编码为双端链表
#define REDIS_ENCODING_ZIPLIST 5     // 编码为压缩列表
#define REDIS_ENCODING_INTSET 6      // 编码为整数集合
#define REDIS_ENCODING_SKIPLIST 7    // 编码为跳跃表

也就是通过 encoding 来对键进行不同的操作.

ptr

是一个指针, 指向实际保存值的数据结构, 这个数据结构由 type 属性和 encoding 属性决定.

举个例子, 如果一个 redisObject 的 type 属性为 REDIS_LIST, encoding 属性为 REDIS_ENCODING_LINKEDLIST, 那么这个对象就是一个 Redis 列表, 它的值保存在一个双端链表内, 而 ptr 指针就指向这个双端链表;

另一方面, 如果一个 redisObject 的 type 属性为 REDIS_HASH, encoding 属性为 REDIS_ENCODING_ZIPMAP, 那么这个对象就是一个 Redis 哈希表, 它的值保存在一个 zipmap 里, 而 ptr 指针就指向这个 zipmap.

下图展示了 redisObject 、Redis 所有数据类型、以及 Redis 所有编码方式(底层实现)三者之间的关系:

RedisObject 数据结构

 

命令的类型检查和多态

有了 redisObject 结构的存在, 在执行处理数据类型的命令时, 进行类型检查和对编码进行多态操作就简单得多了.

当执行一个处理数据类型的命令时, Redis 执行以下步骤:

  1. 根据给定 key, 在数据库字典中查找和它相对应的 redisObject, 如果没找到, 就返回 NULL.
  2. 检查 redisObject 的 type 属性和执行命令所需的类型是否相符,如果不相符,返回类型错误.
  3. 根据 redisObject 的 encoding 属性所指定的编码,选择合适的操作函数来处理底层的数据结构.
  4. 返回数据结构的操作结果作为命令的返回值.

作为例子,以下展示了对键 key 执行 LPOP 命令的完整过程:

RedisObject 数据结构