基本上,模块包含命令处理程序——这些是具有以下签名的 C 函数
int MyCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
从签名可以看出,该函数返回一个整数,OK 或 ERR。通常,返回 OK(即使向用户返回错误)也可以。
命令处理程序接受一个 RedisModuleCtx* 对象。对于模块开发人员来说,这个对象是不透明的,但在内部它包含调用客户端的状态,甚至是内部内存管理,我们稍后会讲到。接下来它接收 argv 和 argc, 它们基本上是用户传递给被调用命令的参数。第一个参数是调用本身的名称,其余的只是来自 Redis 协议的已解析参数。请注意,它们被接收为 RedisModuleString 对象,同样,这些对象是不透明的。如果需要操作,可以使用零复制将其转换为普通的 C 字符串。
为了激活模块的命令,模块的标准入口点是一个名为 int RedisModule_OnLoad(RedisModuleCtx *ctx) 的函数。此函数告诉 Redis 模块中包含哪些命令,并将它们映射到它们的处理程序。
在本简短的教程中,我们将重点介绍一个非常简单的模块示例,该模块实现了一个新的 Redis 命令: HGETSET <key> <element> <new value>。 HGETSET 是 HGET 和 HSET 的组合,允许您检索 HASH 对象中的当前值,并以原子方式在其位置设置一个新值。这非常基本,也可以使用简单的事务或 LUA 脚本来完成,但是 HGETSET 的优势在于它非常简单。
int HGetSetCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { return REDISMODULE_OK; }
同样,这目前什么也不做,它只是返回 OK 代码。因此,让我们给它一些实质内容。
请记住,我们的命令是 HGETSET <key> <element> <new value>,这意味着它将始终在 argv 中包含四个参数。因此,让我们确保这确实发生了
/* HGETSET <key> <element> <new value> */ int HGetSetCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { if (argc != 4) { return RedisModule_WrongArity(ctx); } Return REDISMODULE_OK; }
RedisModule_WrongArity 将以以下形式向客户端返回标准错误
(error) ERR get 命令的参数数量错误。
Redis 模块 API 的一个重要特性是自动资源和内存管理。虽然模块作者可以独立地分配和释放内存,但调用 RedisModule_AutoMemory 允许您在处理程序的生命周期内自动创建 Redis 资源并分配 Redis 字符串、键和响应。
RedisModule_AutoMemory(ctx);
现在我们将运行两个 Redis 调用中的第一个 HGET。我们传递 argv[1] 和 argv[2],即键和元素作为参数。我们使用通用的 RedisModule_Call 命令,该命令只是允许模块开发人员调用任何现有的 Redis 命令,很像 LUA 脚本
RedisModuleCallReply *rep = RedisModule_Call(ctx, “HGET”, “ss”, argv[1], argv[2]); // And let’s make sure it’s not an error if (RedisModule_CallReplyType(rep) == REDISMODULE_REPLY_ERROR) { return RedisModule_ReplyWithCallReply(ctx, srep); }
请注意, RedisModule_Call 的第三个参数“ss”表示 Redis 应如何处理传递给该函数的可变参数。“ss”表示“两个 RedisModuleString 对象”。其他说明符是“c”(用于 c 字符串)、“d”(用于双精度浮点数)、“l”(用于长整型)和“b”(用于 c 缓冲区,即字符串及其长度)。
现在让我们执行第二个 Redis 调用 HSET
RedisModuleCallReply *srep = RedisModule_Call(ctx, “HSET”, “sss”, argv[1], argv[2], argv[3]); if (RedisModule_CallReplyType(srep) == REDISMODULE_REPLY_ERROR) { return RedisModule_ReplyWithCallReply(ctx, srep); }
使用 HSET 与 HGET 命令类似,只是我们传递给它三个参数。
在这种简单情况下,我们只需要返回 HGET 的结果,或者我们更改之前的 value。这使用一个简单的函数 RedisModule_ReplyWithCallReply 完成,该函数将回复对象转发给客户端
RedisModule_ReplyWithCallReply(ctx, rep); return REDISMODULE_OK; }
就是这样!我们的命令处理程序已准备就绪;我们只需要正确注册我们的模块和命令处理程序。
所有 Redis 模块的入口点都是一个名为 RedisModule_OnLoad 的函数,开发人员必须实现它。它注册并初始化模块,并将模块的命令注册到 Redis,以便可以调用它们。初始化我们的模块如下所示
int RedisModule_OnLoad(RedisModuleCtx *ctx) { // Register the module itself – it’s called ‘example’ and has an API version of 1 if (RedisModule_Init(ctx, “example”, 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR) { return REDISMODULE_ERR; } // register our command – it is a write command, with one key at argv[1] if (RedisModule_CreateCommand(ctx, “HGETSET”, HGetSetCommand, “write”, 1, 1, 1) == REDISMODULE_ERR) { return REDISMODULE_ERR; } return REDISMODULE_OK; }
就是这样!我们的模块完成了。
剩下要做的就是编译我们的模块。我不会详细介绍如何为其创建 makefile,但您需要知道的是,Redis 模块不需要任何特殊链接。一旦您在模块文件中包含了 redismodule.h 文件并实现了入口点函数,Redis 就可以加载您的模块了。任何其他链接都取决于您。使用 gcc 编译我们的基本模块所需的命令是
On Linux: $ gcc -fPIC -std=gnu99 -c -o module.o module.c $ ld -o module.so module.o -shared -Bsymbolic -lc On OSX: $ gcc -dynamic -fno-common -std=gnu99 -c -o module.o module.c $ ld -o module.so module.o -bundle -undefined dynamic_lookup -lc
构建模块后,需要加载它。假设您已从其最新的稳定版本(支持模块)下载了 Redis,只需使用 loadmodule 命令行参数运行它
redis-server –loadmodule /path/to/module.so
Redis 现在正在运行并且已加载我们的模块。我们可以简单地连接 redis-cli 并运行我们的命令!
此处详细介绍的完整源代码可以在 RedisModuleSDK 中找到,其中包括模块项目模板、makefile 和实用程序库(其中包含自动执行一些在原始 API 中未包含的乏味模块编写工作的函数)。您不必使用它,但请随意使用。我们的模块完成了。