Redis源碼:內(nèi)存管理與事件處理

Redis內(nèi)存管理

Redis內(nèi)存管理相關(guān)文件為zmalloc.c/zmalloc.h笆环,其只是對(duì)C中內(nèi)存管理函數(shù)做了簡(jiǎn)單的封裝冀泻,屏蔽了底層平臺(tái)的差異住涉,并增加了內(nèi)存使用情況統(tǒng)計(jì)的功能战惊。

void *zmalloc(size_t size) {
    // 多申請(qǐng)的一部分內(nèi)存用于存儲(chǔ)當(dāng)前分配了多少自己的內(nèi)存
    void *ptr = malloc(size+PREFIX_SIZE);

    if (!ptr) zmalloc_oom_handler(size);
#ifdef HAVE_MALLOC_SIZE
    update_zmalloc_stat_alloc(zmalloc_size(ptr));
    return ptr;
#else
    *((size_t*)ptr) = size;
    // 內(nèi)存分配統(tǒng)計(jì)
    update_zmalloc_stat_alloc(size+PREFIX_SIZE);
    return (char*)ptr+PREFIX_SIZE;
#endif
}
內(nèi)存結(jié)構(gòu)圖示

Redis事件處理

Redis的事件類(lèi)型分為時(shí)間事件和文件事件缩挑,文件事件也就是網(wǎng)絡(luò)連接事件但两。時(shí)間事件的處理是在epoll_wait返回處理文件事件后處理的,每次epoll_wait的超時(shí)時(shí)間都是Redis最近的一個(gè)定時(shí)器時(shí)間供置。

Redis在進(jìn)行事件處理前谨湘,首先會(huì)進(jìn)行初始化,初始化的主要邏輯在main/initServer函數(shù)中芥丧。初始化流程主要做的工作如下:

  1. 設(shè)置信號(hào)回調(diào)函數(shù)紧阔。
  2. 創(chuàng)建事件循環(huán)機(jī)制,即調(diào)用epoll_create续担。
  3. 創(chuàng)建服務(wù)監(jiān)聽(tīng)端口擅耽,創(chuàng)建定時(shí)事件,并將這些事件添加到事件機(jī)制中物遇。
void initServer(void) {
    int j;

    // 設(shè)置信號(hào)對(duì)應(yīng)的處理函數(shù)
    signal(SIGHUP, SIG_IGN);
    signal(SIGPIPE, SIG_IGN);
    setupSignalHandlers();
    ...

    createSharedObjects();
    adjustOpenFilesLimit();
    // 創(chuàng)建事件循環(huán)機(jī)制乖仇,及調(diào)用epoll_create創(chuàng)建epollfd用于事件監(jiān)聽(tīng)
    server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
    server.db = zmalloc(sizeof(redisDb)*server.dbnum);

    /* Open the TCP listening socket for the user commands. */
    // 創(chuàng)建監(jiān)聽(tīng)服務(wù)端口,socket/bind/listen
    if (server.port != 0 &&
        listenToPort(server.port,server.ipfd,&server.ipfd_count) == C_ERR)
        exit(1);
    ...

    /* Create the Redis databases, and initialize other internal state. */
    for (j = 0; j < server.dbnum; j++) {
        server.db[j].dict = dictCreate(&dbDictType,NULL);
        server.db[j].expires = dictCreate(&keyptrDictType,NULL);
        server.db[j].blocking_keys = dictCreate(&keylistDictType,NULL);
        server.db[j].ready_keys = dictCreate(&setDictType,NULL);
        server.db[j].watched_keys = dictCreate(&keylistDictType,NULL);
        server.db[j].eviction_pool = evictionPoolAlloc();
        server.db[j].id = j;
        server.db[j].avg_ttl = 0;
    }
    ...

    /* Create the serverCron() time event, that's our main way to process
     * background operations. 創(chuàng)建定時(shí)事件 */
    if(aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
        serverPanic("Can't create the serverCron time event.");
        exit(1);
    }

    /* Create an event handler for accepting new connections in TCP and Unix
     * domain sockets. */
    for (j = 0; j < server.ipfd_count; j++) {
        if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,
            acceptTcpHandler,NULL) == AE_ERR)
            {
                serverPanic(
                    "Unrecoverable error creating server.ipfd file event.");
            }
    }
    // 將事件加入到事件機(jī)制中询兴,調(diào)用鏈為 aeCreateFileEvent/aeApiAddEvent/epoll_ctl
    if (server.sofd > 0 && aeCreateFileEvent(server.el,server.sofd,AE_READABLE,
        acceptUnixHandler,NULL) == AE_ERR) serverPanic("Unrecoverable error creating server.sofd file event.");

    /* Open the AOF file if needed. */
    if (server.aof_state == AOF_ON) {
        server.aof_fd = open(server.aof_filename,
                               O_WRONLY|O_APPEND|O_CREAT,0644);
        if (server.aof_fd == -1) {
            serverLog(LL_WARNING, "Can't open the append-only file: %s",
                strerror(errno));
            exit(1);
        }
    }

    ...
}

事件處理流程
事件處理函數(shù)鏈:aeMain / aeProcessEvents / aeApiPoll / epoll_wait乃沙。

常見(jiàn)的事件機(jī)制處理流程是:調(diào)用epoll_wait等待事件來(lái)臨,然后遍歷每一個(gè)epoll_event诗舰,提取epoll_event中的events和data域警儒,data域常用來(lái)存儲(chǔ)fd或者指針,不過(guò)一般的做法是提取出events和data.fd始衅,然后根據(jù)fd找到對(duì)應(yīng)的回調(diào)函數(shù)冷蚂,fd與對(duì)應(yīng)回調(diào)函數(shù)之間的映射關(guān)系可以存儲(chǔ)在特定的數(shù)據(jù)結(jié)構(gòu)中缭保,比如數(shù)組或者哈希表,然后調(diào)用事件回調(diào)函數(shù)來(lái)處理蝙茶。
Redis中用了一個(gè)數(shù)組來(lái)保存fd與回調(diào)函數(shù)的映射關(guān)系艺骂,使用數(shù)組的優(yōu)點(diǎn)就是簡(jiǎn)單高效,但是數(shù)組一般使用在建立的連接不太多情況隆夯,而Redis正好符合這個(gè)情況钳恕,一般Redis的文件事件大都是客戶(hù)端建立的連接,而客戶(hù)端的連接個(gè)數(shù)是一定的蹄衷,該數(shù)量通過(guò)配置項(xiàng)maxclients來(lái)指定忧额。

static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
    aeApiState *state = eventLoop->apidata;
    int retval, numevents = 0;

    retval = epoll_wait(state->epfd,state->events,eventLoop->setsize,
            tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);
    if (retval > 0) {
        int j;

        numevents = retval;
        for (j = 0; j < numevents; j++) {
            int mask = 0;
            struct epoll_event *e = state->events+j;

            if (e->events & EPOLLIN) mask |= AE_READABLE;
            if (e->events & EPOLLOUT) mask |= AE_WRITABLE;
            if (e->events & EPOLLERR) mask |= AE_WRITABLE;
            if (e->events & EPOLLHUP) mask |= AE_WRITABLE;
            eventLoop->fired[j].fd = e->data.fd;
            eventLoop->fired[j].mask = mask;
        }
    }
    return numevents;
}

int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
    numevents = aeApiPoll(eventLoop, tvp);
    for (j = 0; j < numevents; j++) {
        // 從eventLoop->events數(shù)組中查找對(duì)應(yīng)的回調(diào)函數(shù)
        aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];
        int mask = eventLoop->fired[j].mask;
        int fd = eventLoop->fired[j].fd;
        int rfired = 0;

        /* note the fe->mask & mask & ... code: maybe an already processed
         * event removed an element that fired and we still didn't
         * processed, so we check if the event is still valid. */
        if (fe->mask & mask & AE_READABLE) {
            rfired = 1;
            fe->rfileProc(eventLoop,fd,fe->clientData,mask);
        }
        if (fe->mask & mask & AE_WRITABLE) {
            if (!rfired || fe->wfileProc != fe->rfileProc)
                fe->wfileProc(eventLoop,fd,fe->clientData,mask);
        }
        processed++;
    }
    ...
}

文件事件的監(jiān)聽(tīng)

Redis監(jiān)聽(tīng)端口的事件回調(diào)函數(shù)鏈?zhǔn)牵?code>acceptTcpHandler / acceptCommonHandler / createClient / aeCreateFileEvent / aeApiAddEvent / epoll_ctl。
在Reids監(jiān)聽(tīng)事件處理流程中愧口,會(huì)將客戶(hù)端的連接fd添加到事件機(jī)制中睦番,并設(shè)置其回調(diào)函數(shù)為readQueryFromClient,該函數(shù)負(fù)責(zé)處理客戶(hù)端的命令請(qǐng)求耍属。

命令處理流程

命令處理流程鏈?zhǔn)牵?code>readQueryFromClient / processInputBuffer / processCommand / call / 對(duì)應(yīng)命令的回調(diào)函數(shù)(c->cmd->proc)托嚣,比如get key命令的處理回調(diào)函數(shù)為getCommandgetCommand的執(zhí)行流程是先到client對(duì)應(yīng)的數(shù)據(jù)庫(kù)字典中根據(jù)key來(lái)查找數(shù)據(jù)厚骗,然后根據(jù)響應(yīng)消息格式將查詢(xún)結(jié)果填充到響應(yīng)消息中示启。

如何添加自定義命令

如何在Redis中添加自定的命令呢?其中只需要改動(dòng)以下幾個(gè)地方就行了领舰,比如自定義命令hello xxx夫嗓,然后返回redis: xxx,因?yàn)?code>hello xxx和get key類(lèi)似冲秽,所以就依葫蘆畫(huà)瓢舍咖。

首先在redisCommandTable數(shù)組中添加自定義的命令,redisCommandTable數(shù)組定義在server.c中劳跃。

struct redisCommand redisCommandTable[] = {
    {"get",getCommand,2,"rF",0,NULL,1,1,1,0,0},
    ...
    {"hello",helloCommand,2,"rF",0,NULL,1,1,1,0,0},
};

然后在getCommand定義處后面添加``helloCommand的定義谎仲,getCommand定義在t_string.c`中。

void getCommand(client *c) {
    getGenericCommand(c);
}

void helloCommand(client *c)
{
    char buff[512] = {0};
    robj *o = NULL;

    sprintf(buff, "%s %s", "redis: ", c->argv[1]);
    o = createObject(OBJ_STRING, sdsnewlen(buff, strlen(buff)));

    addReplyBulk(c, o);
    sdsfree(o);
}

最后在server.h中添加helloCommand的聲明刨仑。

void getCommand(client *c);
void helloCommand(client *c);

至此為止郑诺,代碼已經(jīng)編寫(xiě)完畢,編譯redis然后運(yùn)行redis-server杉武。

hello命令運(yùn)行結(jié)果

參考資料:

  1. redis源碼分析(1)內(nèi)存管理
  2. redis源碼
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末辙诞,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子轻抱,更是在濱河造成了極大的恐慌飞涂,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異较店,居然都是意外死亡士八,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門(mén)梁呈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)婚度,“玉大人,你說(shuō)我怎么就攤上這事官卡』茸拢” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵寻咒,是天一觀的道長(zhǎng)哮翘。 經(jīng)常有香客問(wèn)我,道長(zhǎng)毛秘,這世上最難降的妖魔是什么饭寺? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮叫挟,結(jié)果婚禮上佩研,老公的妹妹穿的比我還像新娘。我一直安慰自己霞揉,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布晰骑。 她就那樣靜靜地躺著适秩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪硕舆。 梳的紋絲不亂的頭發(fā)上秽荞,一...
    開(kāi)封第一講書(shū)人閱讀 51,763評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音抚官,去河邊找鬼扬跋。 笑死,一個(gè)胖子當(dāng)著我的面吹牛凌节,可吹牛的內(nèi)容都是我干的钦听。 我是一名探鬼主播,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼倍奢,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼朴上!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起椭符,我...
    開(kāi)封第一講書(shū)人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤捺檬,失蹤者是張志新(化名)和其女友劉穎弱匪,沒(méi)想到半個(gè)月后套像,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體衣撬,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡乖订,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了具练。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乍构。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖靠粪,靈堂內(nèi)的尸體忽然破棺而出蜡吧,到底是詐尸還是另有隱情,我是刑警寧澤占键,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布昔善,位于F島的核電站,受9級(jí)特大地震影響畔乙,放射性物質(zhì)發(fā)生泄漏君仆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一牲距、第九天 我趴在偏房一處隱蔽的房頂上張望返咱。 院中可真熱鬧,春花似錦牍鞠、人聲如沸咖摹。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)萤晴。三九已至,卻和暖如春胁后,著一層夾襖步出監(jiān)牢的瞬間店读,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工攀芯, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留屯断,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓侣诺,卻偏偏與公主長(zhǎng)得像殖演,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子紧武,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • epoll概述 epoll是linux中IO多路復(fù)用的一種機(jī)制剃氧,I/O多路復(fù)用就是通過(guò)一種機(jī)制,一個(gè)進(jìn)程可以監(jiān)視多...
    發(fā)仔很忙閱讀 10,893評(píng)論 4 35
  • 最近在看 UNIX 網(wǎng)絡(luò)編程并研究了一下 Redis 的實(shí)現(xiàn)阻星,感覺(jué) Redis 的源代碼十分適合閱讀和分析朋鞍,其中 ...
    Draveness閱讀 10,727評(píng)論 8 56
  • 本文的討論已添,暫時(shí)忽略redis數(shù)據(jù)結(jié)構(gòu)和算法層面的東西。 目錄 redis如此之快的原因 redis server...
    tafeng閱讀 10,390評(píng)論 0 6
  • 花間詞滥酥,人在花間更舞,極寫(xiě)男女歡情之好。 花間派領(lǐng)袖坎吻,以我之愚見(jiàn)缆蝉,唯有柳永,韋莊瘦真,溫庭筠三人而已刊头。 情色,客觀...
    秦吉_閱讀 399評(píng)論 0 2
  • 【內(nèi)楗第三】 原文: (3.1)君臣上下之事诸尽,有遠(yuǎn)有親原杂,近而疏,就之不用您机,去之反求穿肄。日進(jìn)前而不御,遙聞聲而相思际看。事...
    海納百川vs王者之風(fēng)閱讀 308評(píng)論 0 0