一 序:
前面整理了edis基本的数据结构分别进行了简单的介绍,包括字符串、链表、哈希表、整数集合、压缩列表、压缩字典等,但是redis并不是直接使用这些数据结构来实现key-value对数据库的,而是基于这些数据结构为每一个对象创建一个对象robject。通过这五种不同类型的对象, Redis 可以在执行命令之前, 根据对象的类型来判断一个对象是否可以执行给定的命令。 使用对象的另一个好处是, 我们可以针对不同的使用场景, 为对象设置多种不同的数据结构实现, 从而优化对象在不同场景下的使用效率。书上第8章比较长,接下来几篇分别整理,本篇整理object。
二 对象类型与编码
Redis 使用对象来表示数据库中的键和值, 每次当我们在 Redis 的数据库中新创建一个键值对时, 我们至少会创建两个对象, 一个对象用作键值对的键(键对象), 另一个对象用作键值对的值(值对象)。
Redis 中的每个对象都由一个 redisObject
结构表示,源码在server.h:
typedef struct redisObject {
unsigned type:4;
unsigned encoding:4;
unsigned lru:LRU_BITS; /* lru time (relative to server.lruclock) */
int refcount;
void *ptr;
} robj;
type:4bit,对象的类型
encoding:4bit,对象的编码方式
lru:24bit,记录访问时间
refcount:引用计数
ptr:指向具体的数据
结合上面的属性,refcount基于引用计数技术的内存回收机制: 当程序不再使用某个对象的时候, 这个对象所占用的内存就会被自动释放; 另外, Redis 还通过引用计数技术实现了对象共享机制, 这一机制可以在适当的条件下, 通过让多个数据库键共享同一个对象来节约内存。lru:访问时间记录信息, 该信息可以用于计算数据库键的空转时长, 在服务器启用了 maxmemory 功能的情况下, 空转时长较大的那些键可能会优先被服务器删除。怎么实现的还没看。刨去这两个属性,跟保存数据有关的三个属性分别是 type 属性、 encoding 属性和 ptr 属性,下面分别来看。
2.1 type
对象的 type
属性记录了对象的类型, 这个属性的值可以是下面列出的常量的其中一个。
| 字符串对象 |
| 列表对象 |
| 哈希对象 |
| 集合对象 |
| 有序集合对象 |
对应的源码在server.h:
/* Object types */
#define OBJ_STRING 0
#define OBJ_LIST 1
#define OBJ_SET 2
#define OBJ_ZSET 3
#define OBJ_HASH 4
对于 Redis 数据库保存的键值对来说, 键总是一个字符串对象, 而值则可以是字符串对象、列表对象、哈希对象、集合对象或者有序集合对象的其中一种, 因此当我们称一个键为“字符串键”时,是指这个键对应的value是字符串类型的。
2.2 编码
对象的 ptr 指针指向对象的底层实现数据结构, 而这些数据结构由对象的 encoding 属性决定。encoding 属性记录了对象所使用的编码, 也即是说这个对象使用了什么数据结构作为对象的底层实现。
/* Objects encoding. Some kind of objects like Strings and Hashes can be
* internally represented in multiple ways. The 'encoding' field of the object
* is set to one of this fields for this object. */
// encoding 的10种类型
#define OBJ_ENCODING_RAW 0 /* Raw representation */ //原始表示方式,字符串对象是简单动态字符串
#define OBJ_ENCODING_INT 1 /* Encoded as integer */ //long类型的整数
#define OBJ_ENCODING_HT 2 /* Encoded as hash table */ //字典
#define OBJ_ENCODING_ZIPMAP 3 /* Encoded as zipmap */ //不在使用
#define OBJ_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */ //双端链表,不在使用
#define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */ //压缩列表
#define OBJ_ENCODING_INTSET 6 /* Encoded as intset */ //整数集合
#define OBJ_ENCODING_SKIPLIST 7 /* Encoded as skiplist */ //跳跃表和字典
#define OBJ_ENCODING_EMBSTR 8 /* Embedded sds string encoding */ //embstr编码的简单动态字符串
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */ //由压缩列表组成的双向列表-->快速列表
每种类型的对象都至少使用了两种不同的编码,下面列出了每种对象可使用的编码方式。
类型 type | 编码 encode | 描述 |
OBJ_STRING | OBJ_ENCODING_INT | 使用整数实现的字符串对象 |
OBJ_STRING | OBJ_ENCODING_EMBSTR | 使用embstr编码实现的字符串对象 |
OBJ_STRING | OBJ_ENCODING_RAW | 使用sds实现的字符串对象 |
OBJ_LIST | OBJ_ENCODING_QUICKLIST | 使用quicklist实现的列表对象 |
OBJ_HASH | OBJ_ENCODING_ZIPLIST | 使用压缩表实现的hash对象 |
OBJ_HASH | OBJ_ENCODING_HT | 使用字典实现的hash对象 |
OBJ_SET | OBJ_ENCODING_INSET | 使用整数集合实现的集合对象 |
OBJ_SET | OBJ_ENCODING_HT | 使用字典实现的集合对象 |
OBJ_ZSET | OBJ_ENCODING_ZIPLIST | 使用压缩列表实现的有序集合对象 |
OBJ_ZSET | OBJ_ENCODING_SKIPLIST | 使用跳跃表实现的有序集合对象 |
注意之前的list 使用链表在3.2代码已经没有了,只有quicklist, 通过 encoding
属性来设定对象所使用的编码, 而不是为特定类型的对象关联一种固定的编码, 极大地提升了 Redis 的灵活性和效率, 因为 Redis 可以根据不同的使用场景来为一个对象设置不同的编码, 从而优化对象在某一场景下的效率。
三 源码
看下object.c的源码:
/*
* 为对象的引用计数增一
*/
void incrRefCount(robj *o) {
o->refcount++;
}
/*
* 为对象的引用计数减一
*
* 当对象的引用计数降为 0 时,释放对象。
*/
void decrRefCount(robj *o) {
if (o->refcount <= 0) redisPanic("decrRefCount against refcount <= 0");
// 释放对象
if (o->refcount == 1) {
switch(o->type) {
case REDIS_STRING: freeStringObject(o); break;
case REDIS_LIST: freeListObject(o); break;
case REDIS_SET: freeSetObject(o); break;
case REDIS_ZSET: freeZsetObject(o); break;
case REDIS_HASH: freeHashObject(o); break;
default: redisPanic("Unknown object type"); break;
}
zfree(o);
// 减少计数
} else {
o->refcount--;
}
}
上面所以redis通过refcount记录robj共享的次数。当refcount为0时即robj对象没有在被应用,表示该robj对象应该被释放,回收内存。释放对象时,根据对象的类型type,释放对象保存的数据结构,再释放对象。看下具体的释放对象,根据encoding去对应的不同的类型:
void freeStringObject(robj *o) {
if (o->encoding == OBJ_ENCODING_RAW) {
sdsfree(o->ptr);
}
}
void freeListObject(robj *o) {
if (o->encoding == OBJ_ENCODING_QUICKLIST) {
quicklistRelease(o->ptr);
} else {
serverPanic("Unknown list encoding type");
}
}
void freeSetObject(robj *o) {
switch (o->encoding) {
case OBJ_ENCODING_HT:
dictRelease((dict*) o->ptr);
break;
case OBJ_ENCODING_INTSET:
zfree(o->ptr);
break;
default:
serverPanic("Unknown set encoding type");
}
}
void freeZsetObject(robj *o) {
zset *zs;
switch (o->encoding) {
case OBJ_ENCODING_SKIPLIST:
zs = o->ptr;
dictRelease(zs->dict);
zslFree(zs->zsl);
zfree(zs);
break;
case OBJ_ENCODING_ZIPLIST:
zfree(o->ptr);
break;
default:
serverPanic("Unknown sorted set encoding");
}
}
void freeHashObject(robj *o) {
switch (o->encoding) {
case OBJ_ENCODING_HT:
dictRelease((dict*) o->ptr);
break;
case OBJ_ENCODING_ZIPLIST:
zfree(o->ptr);
break;
default:
serverPanic("Unknown hash encoding type");
break;
}
}
LRU这块需要单独整理。
剩下的广域对象的创建,复制等结合具体的 类型来整理。