共计 2846 个字符,预计需要花费 8 分钟才能阅读完成。
Redis 自 4.0 版本之后便支持了模块扩展功能, 在使用的过程中发现了一些 Redis 实现的疑问,
Redis 推荐开发人员通过引入 redismodule.h, 来调用指定接口来支持扩展, 其中要求实现程序必须实现 RedisModule_OnLoad 方法, 该方法主要加载模块, 注册相应的 api, 对 context 上下文注入,
那么它是什么时候被调用的呢? 又比如 Redis 为开发人员提供几组 API, 比如 RedisModule_StringPtrlen 用于返回抽象类型字符串的长度, 但是翻遍所有的代码也没有该函数声明的具体实现, 那么它是什么时候在哪里被实现的呢?
带着这些问题这篇文章会做出解答
(一) 使用
首先根据 官方文档 , 用户扩展模块需要通过配置文件指定需要加载的链接库
* 通过配置 loadmodule 指定用户扩展的链接库 *
当然动态库需要用户自己去编译生成, 在编译之前需要将指定的 module 引入 redismodule.h 头文件, 最简单的方式是直接将源文件拉入 modules 下, 修改 MakeFile 文件并执行 make 编译
* 修改 MakeFile*
那么 redis 是何时开始加载的呢?
(二) 链接库的加载
redis 服务端的入口在 server.c 源文件中, 其主要任务为初始化数据结构, 初始化设置, 启动 eventloop 事件加载器等等, 其中在启动事件加载器之前, redis 会先加载指定的链接库 (redis 也支持通过命令行加载库)
server.sentinel_mode = checkForSentinelMode(argc,argv);
initServerConfig();
ACLInit(); /* The ACL subsystem must be initialized ASAP because the
basic networking code and client creation depends on it. */
// 初始化模块 加载动态链接库
moduleInitModulesSystem();
moduleInitModulesSystem 函数负责加载模块
loadmodule_queue 为 redis 需要加载模块路径数组, 会再 redis 初始化配置文件时添加
moduleRegisterCoreAPI 为注册函数的主要逻辑, 首先创建用于存放 api 函数的字典, 该字典 key 提供给 module 的函数名称, value 为 module.c 中具体的实现, 通过维护字典的方式实现了提供给模块的接口与 redis 内部实现相分离.
REGISTER_API() 为宏, 会将模块接口与具体实现函数做映射
到此为止已经为用户自定义的模块提供相应的 api 函数了, 这也解释了为什么 moduleapi 函数只有在 module.h 中但是找不到同函数名的实现, 原因是 redis 通过字典来维护了这层函数关系
那么 module 中创建的命令行函数又是如何注册到 redis 内部中呢?
(三) 命令行注册
用户自定义的命令行函数需要在 RedisModule_OnLoad 函数中通过以下方式注册
if (RedisModule_CreateCommand(ctx,"api_RedisModule_Milliseconds",redisModule_Milliseconds, "readonly",0,0,0) == REDISMODULE_ERR){return REDISMODULE_ERR;}
函数的声明 (其中 redis 使用了大量的 incomplete type 后面会做出解释)
int REDISMODULE_API_FUNC(RedisModule_CreateCommand)(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep);
在 RedisModule_Init 函数中获取 RedisModule_CreateCommand 命令函数
static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) {void *getapifuncptr = ((void**)ctx)[0];
// 设置 GetApi 函数 具体实现为 RM_GetApi 由 moduleLoad 函数初始化
RedisModule_GetApi = (int (*)(const char *, void *)) (unsigned long)getapifuncptr;
REDISMODULE_GET_API(Alloc);
REDISMODULE_GET_API(Calloc);
REDISMODULE_GET_API(Free);
REDISMODULE_GET_API(Realloc);
REDISMODULE_GET_API(Strdup);
// 创建命令函数获取
REDISMODULE_GET_API(CreateCommand);
...
}
实际上就是从初始创建的函数字典中获取相应的具体实现函数, 具体函数在 module.c 下的 RM_CreateCommand
int RM_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep) {int flags = strflags ? commandFlagsFromString((char*)strflags) : 0;
if (flags == -1) return REDISMODULE_ERR;
if ((flags & CMD_MODULE_NO_CLUSTER) && server.cluster_enabled)
return REDISMODULE_ERR;
struct redisCommand *rediscmd;
RedisModuleCommandProxy *cp;
sds cmdname = sdsnew(name);
/* Check if the command name is busy. */
if (lookupCommand(cmdname) != NULL) {sdsfree(cmdname);
return REDISMODULE_ERR;
}
...
// 注册命令
dictAdd(server.commands,sdsdup(cmdname),cp->rediscmd);
dictAdd(server.orig_commands,sdsdup(cmdname),cp->rediscmd);
cp->rediscmd->id = ACLGetCommandID(cmdname); /* ID used for ACL. */
return REDISMODULE_OK;
}
本次知识对 redis module 的加载提供大致的说明, 下期来详细讲讲实现细节