Redis Module原理(一)
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的加载提供大致的说明, 下期来详细讲讲实现细节