Redis除了提供5种基本的数据类型和3种扩展类型以外,还可以定制符合自己需求的数据类型了,不管你的应用场景怎么变化,你都不用担心没有合适的数据类型。
RedisObject的组成
要掌握自定义Redis数据类型,先得了解Redis的基本对象结构,也就是RedisObject。
RedisObject 包括元数据和指针。其中,元数据的一个功能就是用来区分不同的数据类型,指针用来指向具体的数据类型的值。RedisObject 的内部组成包括了 type、encoding、lru 和 refcount 4 个元数据,以及 1 个*ptr指针。如下所示:
typedef struct redisObject {
unsigned type:4;
unsigned encoding:4;
unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
* LFU data (least significant 8 bits frequency
* and most significant 16 bits access time). */
int refcount;
void *ptr;
} robj;
type:表示值的类型,涵盖了我们前面学习的五大基本类型;
encoding:是值的编码方式,用来表示 Redis 中实现各个基本类型的底层数据结构,例如 SDS、压缩列表、哈希表、跳表等;
lru:记录了这个对象最后一次被访问的时间,用于淘汰过期的键值对;
refcount:记录了对象的引用计数;
*ptr:是指向数据的指针。
定义一个新的数据类型
定义新类型,我们需要为新数据类型定义好它的底层结构、type 和 encoding 属性值,然后再实现新数据类型的创建、释放函数和基本命令。
第一步:定义新数据类型的底层结构
我们用 newtype.h 文件来保存这个新类型的定义,具体定义的代码如下所示:
struct NewTypeObject {
struct NewTypeNode *head;
size_t len;
}NewTypeObject;
struct NewTypeNode {
long value;
struct NewTypeNode *next;
};
这里我们定义了一个long类型的单向链表作为新的数据类型。
第二步:在 RedisObject 的 type 属性中,增加这个新类型的定义
这个定义是在 Redis 的 server.h 文件中。
#define OBJ_STRING 0 /* String object. */
#define OBJ_LIST 1 /* List object. */
#define OBJ_SET 2 /* Set object. */
#define OBJ_ZSET 3 /* Sorted set object. */
…
#define OBJ_NEWTYPE 7
第三步:开发新类型的创建和释放函数
我们可以在object.c 文件中增加NewTypeObject 的创建函数 createNewTypeObject,事实上Redis 把数据类型的创建和释放函数都定义在object.c中了。
robj *createNewTypeObject(void){
NewTypeObject *h = newtypeNew();
robj *o = createObject(OBJ_NEWTYPE,h);
return o;
}
其中newtypeNew函数用来为新数据类型初始化内存结构的。这个初始化过程主要是用 zmalloc 做底层结构分配空间,以便写入数据,如下所示:
NewTypeObject *newtypeNew(void){
NewTypeObject *n = zmalloc(sizeof(*n));
n->head = NULL;
n->len = 0;
return n;
}
按照惯例,我们可以把这个函数放在一个t_newtype.c的文件中。
createObject 是 Redis 本身提供的 RedisObject 创建函数,它的参数是数据类型的 type 和指向数据类型实现的指针*ptr。
robj *createObject(int type, void *ptr) {
robj *o = zmalloc(sizeof(*o));
o->type = type;
o->ptr = ptr;
...
return o;
}
以上是创建函数的是定义过程,对应的我们还要定义释放函数,用zfree 命令把新结构的内存空间释放掉。
第四步:开发新类型的命令操作
这里的实现可以分为三小步:
1)在 t_newtype.c 文件中增加命令操作的实现。比如说,我们定义 ntinsertCommand 函数,由它实现对 NewTypeObject 单向链表的插入操作:
void ntinsertCommand(client *c){
//基于客户端传递的参数,实现在NewTypeObject链表头插入元素
}
2)在 server.h 文件中,声明我们已经实现的命令,以便在 server.c 文件引用这个命令,例如:
void ntinsertCommand(client *c)
3)在 server.c 文件中的 redisCommandTable 里面,把新增命令和实现函数关联起来。例如,新增的 ntinsert 命令由 ntinsertCommand 函数实现,我们就可以用 ntinsert 命令给 NewTypeObject 数据类型插入元素了。
struct redisCommand redisCommandTable[] = {
...
{"ntinsert",ntinsertCommand,2,"m",...}
}