参考:<<Redis 设计与实现>>
Redis 类型系统
主要功能包括:
- redisObject 对象。
- 基于 redisObject 对象的类型检查。
- 基于 redisObject 对象的显式多态函数。
- 对 redisObject 进行分配、共享和销毁的机制。
1 redisObject
typedef struct redisObject {
// 类型
unsigned type:4;
// 对齐位
unsigned notused:2;
// 编码方式
unsigned encoding:4;
// LRU 时间(相对于 server.lruclock)
unsigned lru:22;
// 引用计数
int refcount;
// 指向对象的值
void *ptr;
} robj;
1.1 type
记录了对象所保存的值的类型
REDIS_STRING 0 // 字符串
REDIS_LIST 1 // 列表
REDIS_SET 2 // 集合
REDIS_ZSET 3 // 有序集合
REDIS_HASH 4 // 哈希表
1.2 encoding
记录了对象所保存的值的编码
REDIS_ENCODING_RAW 0 // 编码为字符串
REDIS_ENCODING_INT 1 // 编码为整数
REDIS_ENCODING_HT 2 // 编码为哈希表
REDIS_ENCODING_ZIPMAP 3 // 编码为 zipmap
REDIS_ENCODING_LINKEDLIST 4 // 编码为双端链表
REDIS_ENCODING_ZIPLIST 5 // 编码为压缩列表
REDIS_ENCODING_INTSET 6 // 编码为整数集合
REDIS_ENCODING_SKIPLIST 7 // 编码为跳跃表
1.3 ptr
指针,指向实际保存值的数据结构。
1.4 引用计数
见4
2 类型检查和多态
当执行一个处理数据类型的命令时, Redis 执行以下步骤:
- 根据给定 key ,在数据库字典中查找和它相对应的 redisObject ,如果没找到,就返回 NULL 。
- 检查 redisObject 的 type 属性和执行命令所需的类型是否相符,如果不相符,返回类型错误。
- 根据 redisObject 的 encoding 属性所指定的编码,选择合适的操作函数来处理底层的数据结构。
- 返回数据结构的操作结果作为命令的返回值。
3 对象共享
- Redis内部维护[0-9999]的整数对象池
- 创建共享字符串对象的数量可以通过修改 redis.h/REDIS_SHARED_INTEGERS(default=10000) 常量来修改。
- 这些共享对象不单单只有字符串键可以使用, 那些在数据结构中嵌套了字符串对象的对象(linkedlist 编码的列表对象、 hashtable 编码的哈希对象、 hashtable 编码的集合对象、以及 zset 编码的有序集合对象)都可以使用。
- 让多个键共享同一个值对象需要执行以下两个步骤:
- 将数据库键的值指针指向一个现有的值对象;
- 将被共享的值对象的引用计数+1。
3.1 为什么 Redis不共享包含字符串的对象?
当服务器考虑将一个共享对象设置为键的值对象时, 程序需要先检查给定的共享对象和键想创建的目标对象是否完全相同, 只有在共享对象和目标对象完全相同的情况下, 程序才会将共享对象用作键的值对象, 而一个共享对象保存的值越复杂, 验证共享对象和目标对象是否相同所需的复杂度就会越高, 消耗的 CPU 时间也会越多:
- 共享对象是保存整数值的字符串对象, 那么验证操作的复杂度为 O(1) ;
- 共享对象是保存字符串值的字符串对象, 那么验证操作的复杂度为 O(N) ;
- 共享对象是包含了多个值(或者对象的)对象, 比如列表对象或者哈希对象, 那么验证操作的复杂度将会是 O(N^2) 。
因此, 尽管共享更复杂的对象可以节约更多的内存, 但受到 CPU 时间的限制, Redis 只对包含整数值的字符串对象进行共享。
4 引用计数以及对象的销毁
引用计数技术来负责维持和销毁对象, 它的运作机制如下:
- 每个 redisObject 结构都带有一个 refcount 属性,指示这个对象被引用了多少次。
- 在创建一个新对象时,引用计数的值会被初始化为 1 ;
- 当对象被一个新程序使用时,它的引用计数值会被+1;
- 当对象不再被一个程序使用时,它的引用计数值会被-1;
- 当对象的引用计数值变为 0 时,对象所占用的内存会被释放。
- 修改对象引用计数的 API :incrRefCount、decrRefCount、resetRefCount。