Redis Module實現(xiàn)

Redis Module實現(xiàn)

加載

void moduleCommand(client *c) {
    char *subcmd = c->argv[1]->ptr;
    if (c->argc == 2 && !strcasecmp(subcmd,"help")) {
        const char *help[] = {
"LIST -- Return a list of loaded modules.",
"LOAD <path> [arg ...] -- Load a module library from <path>.",
"UNLOAD <name> -- Unload a module.",
NULL
        };
        addReplyHelp(c, help);
    } else
    if (!strcasecmp(subcmd,"load") && c->argc >= 3) {
        robj **argv = NULL;
        int argc = 0;

        if (c->argc > 3) {
            argc = c->argc - 3;
            argv = &c->argv[3];
        }

        if (moduleLoad(c->argv[2]->ptr,(void **)argv,argc) == C_OK)
            addReply(c,shared.ok);
        else
            addReplyError(c,
                "Error loading the extension. Please check the server logs.");
    } else if (!strcasecmp(subcmd,"unload") && c->argc == 3) {
        if (moduleUnload(c->argv[2]->ptr) == C_OK)
            addReply(c,shared.ok);
        else {
            char *errmsg;
            switch(errno) {
            case ENOENT:
                errmsg = "no such module with that name";
                break;
            case EBUSY:
                errmsg = "the module exports one or more module-side data types, can't unload";
                break;
            default:
                errmsg = "operation not possible.";
                break;
            }
            addReplyErrorFormat(c,"Error unloading module: %s",errmsg);
        }
    } else if (!strcasecmp(subcmd,"list") && c->argc == 2) {
        dictIterator *di = dictGetIterator(modules);
        dictEntry *de;

        addReplyMultiBulkLen(c,dictSize(modules));
        while ((de = dictNext(di)) != NULL) {
            sds name = dictGetKey(de);
            struct RedisModule *module = dictGetVal(de);
            addReplyMultiBulkLen(c,4);
            addReplyBulkCString(c,"name");
            addReplyBulkCBuffer(c,name,sdslen(name));
            addReplyBulkCString(c,"ver");
            addReplyLongLong(c,module->ver);
        }
        dictReleaseIterator(di);
    } else {
        addReplySubcommandSyntaxError(c);
        return;
    }
}

/* 加載一個Module并且初始化它倡缠,成功返回C_OK,失敗返回C_ERR。 */
int moduleLoad(const char *path, void **module_argv, int module_argc) {
    int (*onload)(void *, void **, int);
    void *handle;
    RedisModuleCtx ctx = REDISMODULE_CTX_INIT;

    handle = dlopen(path,RTLD_NOW|RTLD_LOCAL);
    if (handle == NULL) {
        serverLog(LL_WARNING, "Module %s failed to load: %s", path, dlerror());
        return C_ERR;
    }
    onload = (int (*)(void *, void **, int))(unsigned long) dlsym(handle,"RedisModule_OnLoad");
    if (onload == NULL) {
        dlclose(handle);
        serverLog(LL_WARNING,
            "Module %s does not export RedisModule_OnLoad() "
            "symbol. Module not loaded.",path);
        return C_ERR;
    }
    if (onload((void*)&ctx,module_argv,module_argc) == REDISMODULE_ERR) {
        if (ctx.module) {
            moduleUnregisterCommands(ctx.module);
            moduleFreeModuleStructure(ctx.module);
        }
        dlclose(handle);
        serverLog(LL_WARNING,
            "Module %s initialization failed. Module not loaded",path);
        return C_ERR;
    }

    /* Redis Module加載成功,注冊它。 */
    dictAdd(modules,ctx.module->name,ctx.module);
    ctx.module->handle = handle;
    serverLog(LL_NOTICE,"Module '%s' loaded from %s",ctx.module->name,path);
    moduleFreeContext(&ctx);
    return C_OK;
}

/* 這個函數(shù)必須出現(xiàn)在每一個Redis Module中。它用來注冊命令到Redis服務(wù)器中 */
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
    /* 注冊API并檢查是否有名字沖突 */
    if (RedisModule_Init(ctx,"helloworld",1,REDISMODULE_APIVER_1)
        == REDISMODULE_ERR) return REDISMODULE_ERR;

    /* 記錄加載Module時的參數(shù)列表 */
    for (int j = 0; j < argc; j++) {
        const char *s = RedisModule_StringPtrLen(argv[j],NULL);
        printf("Module loaded with ARGV[%d] = %s\n", j, s);
    }

    if (RedisModule_CreateCommand(ctx,"hello.simple",
        HelloSimple_RedisCommand,"readonly",0,0,0) == REDISMODULE_ERR)
        return REDISMODULE_ERR;

    if (RedisModule_CreateCommand(ctx,"hello.push.native",
        HelloPushNative_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
        return REDISMODULE_ERR;

    if (RedisModule_CreateCommand(ctx,"hello.push.call",
        HelloPushCall_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
        return REDISMODULE_ERR;

    if (RedisModule_CreateCommand(ctx,"hello.push.call2",
        HelloPushCall2_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
        return REDISMODULE_ERR;

    if (RedisModule_CreateCommand(ctx,"hello.list.sum.len",
        HelloListSumLen_RedisCommand,"readonly",1,1,1) == REDISMODULE_ERR)
        return REDISMODULE_ERR;

    if (RedisModule_CreateCommand(ctx,"hello.list.splice",
        HelloListSplice_RedisCommand,"write deny-oom",1,2,1) == REDISMODULE_ERR)
        return REDISMODULE_ERR;

    if (RedisModule_CreateCommand(ctx,"hello.list.splice.auto",
        HelloListSpliceAuto_RedisCommand,
        "write deny-oom",1,2,1) == REDISMODULE_ERR)
        return REDISMODULE_ERR;

    if (RedisModule_CreateCommand(ctx,"hello.rand.array",
        HelloRandArray_RedisCommand,"readonly",0,0,0) == REDISMODULE_ERR)
        return REDISMODULE_ERR;

    if (RedisModule_CreateCommand(ctx,"hello.repl1",
        HelloRepl1_RedisCommand,"write",0,0,0) == REDISMODULE_ERR)
        return REDISMODULE_ERR;

    if (RedisModule_CreateCommand(ctx,"hello.repl2",
        HelloRepl2_RedisCommand,"write",1,1,1) == REDISMODULE_ERR)
        return REDISMODULE_ERR;

    if (RedisModule_CreateCommand(ctx,"hello.toggle.case",
        HelloToggleCase_RedisCommand,"write",1,1,1) == REDISMODULE_ERR)
        return REDISMODULE_ERR;

    if (RedisModule_CreateCommand(ctx,"hello.more.expire",
        HelloMoreExpire_RedisCommand,"write",1,1,1) == REDISMODULE_ERR)
        return REDISMODULE_ERR;

    if (RedisModule_CreateCommand(ctx,"hello.zsumrange",
        HelloZsumRange_RedisCommand,"readonly",1,1,1) == REDISMODULE_ERR)
        return REDISMODULE_ERR;

    if (RedisModule_CreateCommand(ctx,"hello.lexrange",
        HelloLexRange_RedisCommand,"readonly",1,1,1) == REDISMODULE_ERR)
        return REDISMODULE_ERR;

    if (RedisModule_CreateCommand(ctx,"hello.hcopy",
        HelloHCopy_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
        return REDISMODULE_ERR;

    if (RedisModule_CreateCommand(ctx,"hello.leftpad",
        HelloLeftPad_RedisCommand,"",1,1,1) == REDISMODULE_ERR)
        return REDISMODULE_ERR;

    return REDISMODULE_OK;
}

/* 注冊一個新的命令到Redis服務(wù)器,將會通過Redis Module協(xié)議來處理對應(yīng)的請求而钞。
 * 如果命令名已經(jīng)存在或者標(biāo)識位出現(xiàn)錯誤將會返回C_ERR,否則返回C_OK拘荡。
 * 這個函數(shù)必須在RedisModule_OnLoad()中調(diào)用臼节,在初始化函數(shù)之外調(diào)用的行為是未定義的。
 * 命令函數(shù)的形式應(yīng)如下所示:
 * int MyCommand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
 *
 * "strflags"是一個集合珊皿,用來指定該命令的行為网缝,形式如"write deny-oom"
 * write: 命令可能會修改數(shù)據(jù)集。
 * readonly: 命令是只讀的蟋定。
 * admin: 管理員命令粉臊,可能會改變復(fù)制或其它類似的任務(wù)。
 * deny-oom: 可能會增加內(nèi)存驶兜,受內(nèi)存淘汰策略影響扼仲。
 * deny-script: 不允許在Lua腳本中執(zhí)行。
 * allow-loading: 允許在加載數(shù)據(jù)集時執(zhí)行抄淑,僅在命令不與數(shù)據(jù)集交互式執(zhí)行屠凶。
 * pubsub: 這個命令會通過Pub/Sub發(fā)布信息。
 * random: 該命令受隨機化影響肆资。
 * allow-stale: 該命令可以在從服務(wù)器用陳舊的數(shù)據(jù)響應(yīng)給客戶端矗愧。
 * no-monitor: 該命令的參數(shù)中有敏感信息,不要傳播給monitor郑原。
 * fast: 該命令的時間復(fù)雜度不大于O(logN)唉韭。
 * getkeys-api: 該命令實現(xiàn)了用來返回keys的接口,使用first/last/step無法描述key犯犁。
 * no-cluster: 該命令不應(yīng)該在Redis Cluster中注冊属愤,可能是不支持報告keys的位置,
 *              以編程方式創(chuàng)建key名酸役,或者其它原因住诸。
 */
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);

    /* 檢查是否有重名的命令 */
    if (lookupCommand(cmdname) != NULL) {
        sdsfree(cmdname);
        return REDISMODULE_ERR;
    }

    /* 創(chuàng)建一個命令代理驾胆,綁定到一個正常的Redis命令上,添加到命令表中只壳,
     * 這樣我們可以從這個正常的Redis命令找到原Module俏拱。
     * 注意:我們使用Redis的getkeys_proc來保存cp結(jié)構(gòu) */
    cp = zmalloc(sizeof(*cp));
    cp->module = ctx->module;
    cp->func = cmdfunc;
    cp->rediscmd = zmalloc(sizeof(*rediscmd));
    cp->rediscmd->name = cmdname;
    cp->rediscmd->proc = RedisModuleCommandDispatcher;
    cp->rediscmd->arity = -1;
    cp->rediscmd->flags = flags | CMD_MODULE;
    cp->rediscmd->getkeys_proc = (redisGetKeysProc*)(unsigned long)cp;
    cp->rediscmd->firstkey = firstkey;
    cp->rediscmd->lastkey = lastkey;
    cp->rediscmd->keystep = keystep;
    cp->rediscmd->microseconds = 0;
    cp->rediscmd->calls = 0;
    dictAdd(server.commands,sdsdup(cmdname),cp->rediscmd);
    dictAdd(server.orig_commands,sdsdup(cmdname),cp->rediscmd);
    return REDISMODULE_OK;
}

/* 將這個從module中導(dǎo)出的命令命令綁定到一個正常的Redis命令上暑塑。 */
void RedisModuleCommandDispatcher(client *c) {
    RedisModuleCommandProxy *cp = (void*)(unsigned long)c->cmd->getkeys_proc;
    RedisModuleCtx ctx = REDISMODULE_CTX_INIT;

    ctx.module = cp->module;
    ctx.client = c;
    cp->func(&ctx,(void**)c->argv,c->argc);
    moduleHandlePropagationAfterCommandCallback(&ctx);
    moduleFreeContext(&ctx);
}

卸載

/* 卸載使用指定名字注冊的module吼句。成功時返回C_OK,否則返回C_ERR事格,并設(shè)置error惕艳。
 * ENONET:沒有指定名稱的模塊。
 * EBUSY:這個模塊導(dǎo)出了新的數(shù)據(jù)類型驹愚,僅僅可以重新加載远搪。*/
int moduleUnload(sds name) {
    struct RedisModule *module = dictFetchValue(modules,name);

    if (module == NULL) {
        errno = ENOENT;
        return REDISMODULE_ERR;
    }

    if (listLength(module->types)) {
        errno = EBUSY;
        return REDISMODULE_ERR;
    }

    moduleUnregisterCommands(module);

    /* Remove any notification subscribers this module might have */
    moduleUnsubscribeNotifications(module);

    /* 注銷所有的hooks. TODO: Yet no hooks support here. */

    /* 卸載所有的動態(tài)連接庫 */
    if (dlclose(module->handle) == -1) {
        char *error = dlerror();
        if (error == NULL) error = "Unknown error";
        serverLog(LL_WARNING,"Error when trying to close the %s module: %s",
            module->name, error);
    }

    /* 從modules中移除module */
    serverLog(LL_NOTICE,"Module %s unloaded",module->name);
    dictDelete(modules,module->name);
    module->name = NULL; /* 這個name已經(jīng)被dictDelete()釋放 */
    moduleFreeModuleStructure(module);

    return REDISMODULE_OK;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市逢捺,隨后出現(xiàn)的幾起案子谁鳍,更是在濱河造成了極大的恐慌,老刑警劉巖劫瞳,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件倘潜,死亡現(xiàn)場離奇詭異,居然都是意外死亡志于,警方通過查閱死者的電腦和手機涮因,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來伺绽,“玉大人养泡,你說我怎么就攤上這事∧斡Γ” “怎么了澜掩?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長杖挣。 經(jīng)常有香客問我输硝,道長,這世上最難降的妖魔是什么程梦? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任点把,我火速辦了婚禮,結(jié)果婚禮上屿附,老公的妹妹穿的比我還像新娘郎逃。我一直安慰自己,他們只是感情好挺份,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布褒翰。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪优训。 梳的紋絲不亂的頭發(fā)上朵你,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機與錄音揣非,去河邊找鬼抡医。 笑死,一個胖子當(dāng)著我的面吹牛早敬,可吹牛的內(nèi)容都是我干的忌傻。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼搞监,長吁一口氣:“原來是場噩夢啊……” “哼水孩!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起琐驴,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤俘种,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后绝淡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宙刘,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年够委,在試婚紗的時候發(fā)現(xiàn)自己被綠了荐类。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡茁帽,死狀恐怖玉罐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情潘拨,我是刑警寧澤吊输,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站铁追,受9級特大地震影響季蚂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜琅束,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一扭屁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧涩禀,春花似錦料滥、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽高每。三九已至,卻和暖如春践宴,著一層夾襖步出監(jiān)牢的瞬間鲸匿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工阻肩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留带欢,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓磺浙,卻偏偏與公主長得像洪囤,于是被迫代替她去往敵國和親徒坡。 傳聞我的和親對象是個殘疾皇子撕氧,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355