Redis外部扩展模块

Redis从4.0版本开始加入了对外部扩展模块的支持。外部扩展模块可以实现新的Redis命令,新的Redis数据结构,总之基本上可以做到所有Redis内核可以做的事情。

我个人认为这是迄今为止,Redis最重要的一个改进。友好的API、完善的文档和健壮的基础结构,会快速吸引大量的第三方开发者不断贡献新的内容,Redis的用途必然会更加广泛,用户群也会随之扩大。

模块的实现

Redis模块需要引入redismodule.h,用C、C++或其他提供C binding的开发语言实现,并编译成动态库.so文件。基于redismodule.h提供的API实现的模块,在API不变的情况下(API版本号相同),可以兼容不同Redis版本而不需要重新编译。

在开发模块的时候并不需要依赖Redis的源代码或开发库,只需要把redismodule.h拷贝到工程下边引用,实现并导出方法RedisModule_OnLoad即可。

模块可以做什么

访问Redis数据空间

Redis提供了两套数据访问的API,一套是较高层的,类似于Lua脚本的API,往往用来调用API没有提供支持的Redis命令。另一套是底层API,速度很快,基本和Redis原生命令一样快,也提供了一些对各种数据结构的进行处理的函数,是推荐的数据访问方式。

高层API使用RedisModule_Call调用Redis命令,如:

1
2
RedisModuleCallReply *reply;
reply = RedisModule_Call(ctx,"INCR","sc",argv[1],"10");

底层API使用RedisModule_OpenKey打开并获取Key指针继而进行后续处理,如:

1
2
RedisModuleKey *key;
key = RedisModule_OpenKey(ctx,"somekey",REDISMODULE_READ);

实现新的数据结构

对于简单的数据结构,可以使用DMA(direct memory access)将结构编码保存到Redis的String类型中,如:

1
2
3
4
5
6
// 获取字符串内存指针继而修改其内容
size_t len;
char *myptr = RedisModule_StringDMA(key,&len,REDISMODULE_WRITE);
// 增大,减少或创建字符串
RedisModule_StringTruncate(key,1024);

也可以使用API注册并实现新的数据结构,可以控制内存的分配与释放,RDB序列化,AOF重写等。这里是Redis官方的一个例子,好奇的同学可以自己点进去看看。

实现阻塞命令

阻塞命令(Blocking commands)会阻塞客户端,直到某个期望的事件发生才会返回,比如List的BLPOP命令。模块API也提供了实现阻塞命令的功能,在写这篇文章的时候,Blocking Command的相关API还处于试验阶段,其设计还没有最终确定,所以这里就不详细说明了,以后再将这部分补全。

加载模块

模块有两种加载方式,一是在配置文件redis.conf中使用loadmodule /path/to/mymodule.so在Redis启动时加载。另一种方式在运行时使用命令MODULE LOAD /path/to/mymodule.so加载。加载的模块可以使用命令MODULE LIST查看,使用MODULE UNLOAD mymodule卸载。

在载入的模块的时候可以传入参数,如:loadmodule mymodule.so foo bar 1234,参数会被传入模块的OnLoad方法中。

对Redis Cluster的支持

模块是可以支持Redis Cluster的,但是目前还找不到相关的介绍,只是在官方文档里提到两个相关的API函数:RedisModule_IsKeysPositionRequest(ctx)RedisModule_KeyAtPos(ctx,pos)。于是仔细的看了一下官方API文档中的相关部分,并对照了一下其他模块的实现,猜了一下大概的实现方式。

首先,在创建Redis模块的命令时,需要调用API方法:

1
int RedisModule_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep);

其中的strflags参数的一个flag为getkeys-api,意思是这个方法是否支持返回参数中key。一个方法接收的参数有多个,但并不是每一个都是key,比如LRANGE key start stop中,只有第一个参数是key。而Redis需要知道一个命令涉及到哪些key,才能在集群中找到对应的服务器并执行命令。

如果一个命令支持getkeys-api,那么在集群环境下,RedisModule_IsKeysPositionRequest(ctx)方法就会返回true,就是说需要方法标出参数中的key,这就用到了RedisModule_KeyAtPos(ctx,pos)方法,其中pos是参数的位置。下边是rxzsets中相关的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int ZUnionTopKCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
...
if (RedisModule_IsKeysPositionRequest(ctx)) {
for (int i = 0; i < numkeys; i++) {
RedisModule_KeyAtPos(ctx, 3 + i);
}
return REDISMODULE_OK;
}
...
}
int RedisModule_OnLoad(RedisModuleCtx *ctx) {
...
if (RedisModule_CreateCommand(ctx, "zuniontop", ZUnionTopKCommand,
"readonly getkeys-api", 1, 1,
1) == REDISMODULE_ERR)
...
}

Redis Module Hub

Redis Module Hub是Redis官方的模块仓库,目前已经有将近二十个模块,其中一大半是Redis Labs自己贡献的,算是抛砖引玉吧,下边随便拿出几个做一下简单的介绍:

  • 对现有数据结构功能的扩展,如:
    • rxkeys提供了按正则表达式批量获取与删除条目的功能
    • rxhashes提供了在Hash中改变现有条目的值并返回原值的原子操做
    • rxlists提供了7个新的列表操作方法。
  • 新数据结构,如:
    • rejson提供了对原生JSON格式支持,允许对JSON数据内的值进行获取与修改
    • Redis Graph添加了对图数据库的支持
    • redisearch实现了全文搜索,使用特殊的压缩数据结构,加快了搜索的速度,并且减少了内存占用
    • redis-ml实现了多个机器学习常用的数据结构及相关方法
  • 对功能的扩展,如:
    • graphicsmagick提供类似GraphicsMagick的图片处理功能,从此生成缩略图,打水印都可以在Redis里做了
    • redablooms基于RedisString的Bloom filter,可以用于ID生成
    • password提供加密的密码存储

参考