Redis Module原理(一)

Redis自4.0版本之后便支持了模块扩展功能, 在使用的过程中发现了一些Redis实现的疑问,

Redis推荐开发人员通过引入redismodule.h, 来调用指定接口来支持扩展, 其中要求实现程序必须实现RedisModule_OnLoad方法, 该方法主要加载模块, 注册相应的api, 对context上下文注入,

那么它是什么时候被调用的呢? 又比如Redis为开发人员提供几组API, 比如RedisModule_StringPtrlen用于返回抽象类型字符串的长度, 但是翻遍所有的代码也没有该函数声明的具体实现, 那么它是什么时候在哪里被实现的呢?
带着这些问题这篇文章会做出解答

(一) 使用

首先根据 官方文档 , 用户扩展模块需要通过配置文件指定需要加载的链接库

Redis Module原理(一)

*通过配置loadmodule指定用户扩展的链接库*

当然动态库需要用户自己去编译生成, 在编译之前需要将指定的module引入redismodule.h头文件, 最简单的方式是直接将源文件拉入modules下, 修改MakeFile文件并执行make编译

Redis Module原理(一)

*修改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函数负责加载模块

Redis Module原理(一)

loadmodule_queue为redis需要加载模块路径数组, 会再redis初始化配置文件时添加

moduleRegisterCoreAPI为注册函数的主要逻辑, 首先创建用于存放api函数的字典, 该字典key提供给module的函数名称, value为module.c中具体的实现, 通过维护字典的方式实现了提供给模块的接口与redis内部实现相分离.

Redis Module原理(一)

REGISTER_API()为宏, 会将模块接口与具体实现函数做映射

Redis Module原理(一)

到此为止已经为用户自定义的模块提供相应的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的加载提供大致的说明, 下期来详细讲讲实现细节

相关推荐