redis源碼學習之工做流程初探

背景

redis是當下比較流行的KV數據庫之一,是抵禦高併發的一把利器,本着知其然還要知其因此然的目的,我決定花一點時間來研究其源碼,但願最後能向本身解釋清楚「redis爲何這麼快」這個疑惑,第一篇主要介紹環境搭建和redis工做流程初探,後期會陸續獻上其餘有意思的章節。html

環境準備

我本身的電腦是win10系統,因此我會準備一套適合windows系統的環境來供本身學習,這樣方便調試分析。linux

下載redis源碼

redis自己是不支持windows系統的,可是微軟的工程師針對windows平臺作了支持,源碼放在了github上,有須要的能夠本身去下載,我這裏下載的是v2.8.9這個tag的源碼,下載地址https://github.com/microsoftarchive/redis。這裏扯個題外話,學習一個開源軟件的時候不要一上來就下載最新版本的源碼看,通過的迭代太多代碼量就上來了,對於新手來講容易暈,先下一個早期的穩定版本瞭解其體系結構和工做流程,等待熟悉了之後再按部就班。git

下載Visual Studio

其餘軟件我沒嘗試過,這個是官方推薦的ide,必定必定必定下載 Visual Studio 2013 update5這個版本的,不然編譯的時候各類報錯,下載地址
http://download.microsoft.com/download/9/3/E/93EA27FF-DB02-4822-8771-DCA0238957E9/vs2013.5_ult_chs.iso。這裏再扯個題外話,我剛開始下載的是最新版本的Visual Studio,結果編譯的時候各類報錯,而後就去網絡上一頓查一頓試,折騰半天仍是沒好,最後下載了 Visual Studio 2013 update5這個版本,結果一把就成功,有些牛角尖必定得鑽,可是有些牛角尖不值得鑽。github

Visual Studio打開redis源碼

按照下圖方式打開下載的redis源碼


redis

c程序的入口是main方法,redis main方法的位置在redis.c文件中,下面咱們經過main方法來逐步瞭解redis的工做流程。數據庫

啓動過程分析

跟着main方法順序看下去,大概有如下幾個關鍵步驟(略過了sentinel相關邏輯):
1.設置隨機數種子、獲取當前時間等;
2.初始化服務配置信息,設置默認值(initServerConfig);
3.解析配置文件(loadServerConfig);
4.初始化server對象(initServer);
 4.1建立eventLoop對象;
 4.2建立serverSocket,監聽端口;
 4.3添加定時事件到eventLoop對象中;
 4.4將serverSocket文件描述符添加到監視集中,這裏藉助IO多路複用框架的能力(windows平臺使用IOCP,其餘平臺使用select、epoll、evport等);
5.從磁盤加載數據到內存中(loadDataFromDisk);
6.執行事件循環邏輯(aeMain),這是redis真正揮灑汗水的地方,下一節會單獨講述這塊內容。windows

調用關係圖

事件循環分析

咱們都知道redis是單線程執行客戶端命令的,那到底是怎樣一種設計才能支持高併發的讀寫呢。網絡

工做模型

1.server啓動,建立serverSocket監聽端口,將serverSocket對應的FD(文件描述符)簡稱爲FD-Server添加到IO多路複用框架的監視集當中,註冊AE_READABLE事件(可讀),關聯的事件處理器是acceptTcpHandler;
2.client鏈接server;
3.事件循環開始輪詢IO多路複用框架接口aeApiPoll,會獲得就緒的FD,執行對應的事件處理器;
4.由第3步事件循環觸發FD-Server AE_READABLE事件對應的事件處理器acceptTcpHandler;
 4.1調用accept得到clientSocket對應的FD簡稱爲FD-Client;
 4.2將FD-Client添加到IO多路複用框架的監視集當中,註冊AE_READABLE事件(可讀),關聯的事件處理器是readQueryFromClient;
5.client發送redis命令;
6.由第3步事件循環觸發FD-Clien AE_READABLE事件對應的事件處理器readQueryFromClient;
 6.1解析客戶端發來的redis命令,找到命令對應的redisCommandProc(命令對應的處理函數);
 6.2執行redisCommandProc;
 6.3prepareClientToWrite準備回寫響應信息,爲FD-Client註冊AE_WRITEABLE事件(可寫),關聯的事件處理器是sendReplyToClient;
7.執行redis中的定時任務;
8.由第3步事件循環觸發FD-Clien AE_WRITEABLE事件對應的事件處理器sendReplyToClient,發送響應內容給client;
併發

代碼分析

server啓動,建立serverSocket並註冊AE_READABLE事件,設置事件處理器爲acceptTcpHandler框架

void initServer() {
      //省略部分代碼

      //初始化eventLoop對象,eventLoop對象裏面存儲了全部的事件
      server.el = aeCreateEventLoop(server.maxclients+REDIS_EVENTLOOP_FDSET_INCR);

      //建立serverSocket,監聽端口
      if (server.port != 0 &&
        listenToPort(server.port,server.ipfd,&server.ipfd_count) == REDIS_ERR)
        exit(1);
      
      //添加定時任務到eventLoop中 
      if(aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
      }



      //將serverSocket對應的文件描述符添加到監視集中,關聯的事件處理器是acceptTcpHandler
      for (j = 0; j < server.ipfd_count; j++) {
        if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,
            acceptTcpHandler,NULL) == AE_ERR)
           
       }
}

acceptTcpHandler當有鏈接過來的時候被觸發,調用accept獲得client socket對應的FD,並將FD添加到監視集中,關聯的事件處理器是readQueryFromClient

void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
    int cport, cfd;
    //調用accept得到clientSocket對應的FD
    cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
    
    //將clientSocket對應的FD添加到監視集中 
    acceptCommonHandler(cfd,0);
}

static void acceptCommonHandler(int fd, int flags) {
    redisClient *c;
     
    //調用createClient添加
    if ((c = createClient(fd)) == NULL) {
    }  
}

redisClient *createClient(int fd) {
    redisClient *c = zmalloc(sizeof(redisClient));

    if (fd != -1) {
        anetNonBlock(NULL,fd);
        anetEnableTcpNoDelay(NULL,fd);
        if (server.tcpkeepalive)
            anetKeepAlive(NULL,fd,server.tcpkeepalive);

	//將fd添加到監視集中,關聯的事件處理器是readQueryFromClient
        if (aeCreateFileEvent(server.el,fd,AE_READABLE,
            readQueryFromClient, c) == AE_ERR)
        {
        }
    }
}

aeMain就是跑一個循環,一直去調用aeProcessEvents

void aeMain(aeEventLoop *eventLoop) {
    eventLoop->stop = 0;
    while (!eventLoop->stop) {
        if (eventLoop->beforesleep != NULL)
            eventLoop->beforesleep(eventLoop);
        aeProcessEvents(eventLoop, AE_ALL_EVENTS);
    }
}

aeProcessEvents會調用aeApiPoll方法來得到就緒的文件描述符,而後執行文件描述符關聯的的事件處理器

int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
    int processed = 0, numevents;

#ifdef _WIN32	
	if (ServiceStopIssued() == TRUE)
		aeStop(eventLoop);
#endif

    /* Nothing to do? return ASAP */
    if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0;

    /* Note that we want call select() even if there are no
     * file events to process as long as we want to process time
     * events, in order to sleep until the next time event is ready
     * to fire. */
    if (eventLoop->maxfd != -1 ||
        ((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) {
        int j;
        aeTimeEvent *shortest = NULL;
        struct timeval tv, *tvp;

        if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))
            shortest = aeSearchNearestTimer(eventLoop);
        if (shortest) {
            long now_sec, now_ms;

            /* Calculate the time missing for the nearest
             * timer to fire. */
            aeGetTime(&now_sec, &now_ms);
            tvp = &tv;
            tvp->tv_sec = shortest->when_sec - now_sec;
            if (shortest->when_ms < now_ms) {
                tvp->tv_usec = ((shortest->when_ms+1000) - now_ms)*1000;
                tvp->tv_sec --;
            } else {
                tvp->tv_usec = (shortest->when_ms - now_ms)*1000;
            }
            if (tvp->tv_sec < 0) tvp->tv_sec = 0;
            if (tvp->tv_usec < 0) tvp->tv_usec = 0;
        } else {
            /* If we have to check for events but need to return
             * ASAP because of AE_DONT_WAIT we need to set the timeout
             * to zero */
            if (flags & AE_DONT_WAIT) {
                tv.tv_sec = tv.tv_usec = 0;
                tvp = &tv;
            } else {
                /* Otherwise we can block */
                tvp = NULL; /* wait forever */
            }
        }

        numevents = aeApiPoll(eventLoop, tvp);
        for (j = 0; j < numevents; j++) {
            aeFileEvent *fe;
            int mask = eventLoop->fired[j].mask;
            int fd = eventLoop->fired[j].fd;
            int rfired = 0;

            fe = &eventLoop->events[eventLoop->fired[j].fd];

	    /* 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++;
        }
    }
    /* Check time events */
    if (flags & AE_TIME_EVENTS)
        processed += processTimeEvents(eventLoop);//處理延遲任務

    return processed; /* return the number of processed file/time events */
}

動畫演示

作了一個動畫幫助理解工做過程(redis啓動以後使用命令行telnet到6379端口,而後執行keys *命令,最終拿到結果)

網絡模塊

IO多路複用

這部份內容網絡上精彩的內容太多,這裏把我認爲比較經典的一些內容貼出來供你們品讀(建議從上往下順序閱讀)
The C10K problem
socket阻塞非阻塞等頭疼問題解釋
LINUX – IO MULTIPLEXING – SELECT VS POLL VS EPOLL
poll vs select vs event-based
redis事件驅動

 
 

   來個人公衆號與我交流
相關文章
相關標籤/搜索