在 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、 encoding 和 ptr 是最重要的三个属性.
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 命令的完整过程:
值得注意的是:
我下载了两个版本的 Redis, 5.0.5 和 3.0.7.
其中 5.0.5 存在 server.h 中, 3.0.7 存在 redis.h 中.
可以使find ./ -name "*" | xargs grep "redisObject"
搜索以下.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。