Redis 模块机制 - 模块编写篇
在 Redis 应用中,模块机制是提及得比较少的一个功能,主要是 Redis 的功能基本上能应付各种需求,很少需要自己编写模块来扩展功能的。但有时候我们希望为 Redis 提供一些较为高级的功能,比如提供支持回滚的事务功能,这时就需要编写模块来进行扩展。
Redis 为模块编写者提供了丰富的 API 来操纵 Redis,下面我们编写一个简单的 Redis 模块来阐明模块编写的过程。
编写一个简单的 Redis 模块
我们要编写的模块只提供一个简单的功能,就是计算一个数的平方数,命令如下
$ 127.0.0.1:6379> math.double 10
(integer) 100
我们先把模块的代码贴出来,然后再解释代码的意义:
#include "redismodule.h"
#include <stdlib.h>
int MathDouble_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
long long num;
if (argc == 2) {
RedisModule_StringToLongLong(argv[1], &num);
RedisModule_ReplyWithLongLong(ctx, num * num);
return REDISMODULE_OK;
}
return RedisModule_ReplyWithError(ctx, "ERR invalid num parameters");
}
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (RedisModule_Init(ctx, "math", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx, "math.double",
MathDouble_RedisCommand, "readonly", 1, 1, 1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
return REDISMODULE_OK;
}
上面引入的 redismodule.h
文件是从 Redis 源码包中复制过来的,编写 Redis 模块时必须引入这个文件。
然后我们可以通过以下命令来编译这个模块:
$ gcc math.c -fPIC -shared -o math.so
上面的命令是把模块编译成 .so
文件,也就是共享库文件。然后可以通过 Redis 的 module load
命令来把模块加载到 Redis 中,如下:
$ module load ./math.so
OK
然后可以通过 module list
命令查看 Redis 已经加载的模块列表:
$ module list
1) 1) "name"
2) "math"
3) "ver"
4) (integer) 1
可以看到,我们的 math
模块已经被加载到 Redis 中。
接下来,我们就可以通过 math
模块提供的 math.double
命令来计算一个数的平方数,如下:
$ 127.0.0.1:6379> math.double 10
(integer) 100
如果看到上面的结果,那么恭喜你,因为你编写的模块已经被加入到 Redis 中。
模块代码解释
每个 Redis 模块都需要提供一个 RedisModule_OnLoad()
函数,这个函数是 Redis 加载模块时会调用的函数,也就是说,Redis 加载一个模块时,会调用这个模块的 RedisModule_OnLoad()
函数。 RedisModule_OnLoad()
函数的定义如下:
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
函数的第一个参数类型必须是 RedisModuleCtx
,表示模块的上下文,主要用来存放模块的一些基本信息。第二个参数类型为 RedisModuleString
,表示载入模块时传入的参数列表。第三个参数类型为 int
,表示参数列表的个数。
在 RedisModule_OnLoad()
函数中一般需要调用 RedisModule_Init()
函数来向 Redis 注册模块,RedisModule_Init()
函数原型如下:
int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver);
第一个参数的类型前面已经介绍过,第二个参数用于指定模块的名称,第三个参数用于指定模块的版本,而第四个参数用于指定API的版本。
注册完模块后,就可以通过 RedisModule_CreateCommand()
函数来为模块创建命令。RedisModule_CreateCommand()
函数原型如下:
int RedisModule_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc,
const char *strflags, int firstkey, int lastkey, int keystep);
RedisModule_CreateCommand()
函数稍微有点复杂,下面介绍各个参数的作用:
ctx
:就是模块的上下文。name
:用于指定命令的名称,比如上面我们定义的math.double
。cmdfunc
:用于指定命令的功能函数,也就是当我们调用一个命令时,就会通过调用这个函数来实现具体的功能。strflags
:用于指定命令的功能,比如write
表示命令对 Redis 进行写操作,而readonly
表示命令只会读取 Redis 的数据等,为什么需要指定命令的功能呢?因为如果命令会对 Redis 进行写操作,那么 Redis 就需要把命令持久化。firstkey
,lastkey
,keystep
:用于指导command getkeys
命令(command getkeys
命令的使用可以参考 Redis 使用手册)获取 keys 列表时使用的,如果不提供这个功能,可以设置为0。
一个模块最重要的是命令的功能函数,如上面模块中的 MathDouble_RedisCommand()
函数,我们把 MathDouble_RedisCommand()
再展示一遍:
int MathDouble_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
long long num;
if (argc == 2) {
RedisModule_StringToLongLong(argv[1], &num);
RedisModule_ReplyWithLongLong(ctx, num * num);
return REDISMODULE_OK;
}
return RedisModule_ReplyWithError(ctx, "ERR invalid num parameters");
}
一个命令的功能函数的原型如下:
int xxx_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
函数的名称可以随便定义,不过按照规范,一般以 _RedisCommand
结尾。第一个参数就是模块上下文,而第二个参数是命令的参数列表,第三个参数是参数列表的个数。
在上面的例子中,MathDouble_RedisCommand()
函数首先判断参数个数是否正确,如果不正确就返回 invalid num parameters
的错误信息。然而,Redis 提供了很多 API 让我们使用,比如 RedisModule_StringToLongLong()
函数就是把参数转换成 long long
类型的数值。而 RedisModule_ReplyWithLongLong()
函数可以让 Redis 回复一个整型数值给客户端。
Redis 提供的 API 非常多,具体可以参考 Redis 官网的模块开发介绍或者查看 redismodule.h
文件。Redis 模块编写的介绍就到这里了,下篇会介绍 Redis 模块内部的实现原理。
参考文献:
Redis模块开发手册:Redis Modules: an introduction to the API
Redis模块API手册 Modules API reference