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 结构的存在, 在执行处理数据类型的命令时, 进行类型检查和对编码进行多态操作就简单得多了.
当执行一个处理数据类型的命令时, Redis 执行以下步骤:
- 根据给定 key, 在数据库字典中查找和它相对应的 redisObject, 如果没找到, 就返回 NULL.
- 检查 redisObject 的 type 属性和执行命令所需的类型是否相符,如果不相符,返回类型错误.
- 根据 redisObject 的 encoding 属性所指定的编码,选择合适的操作函数来处理底层的数据结构.
- 返回数据结构的操作结果作为命令的返回值.
作为例子,以下展示了对键 key 执行 LPOP 命令的完整过程: