redis0.1源碼解析之基本原理

本文分析redis的基礎原理,暫不做深入分析,後續再詳細分析。我們從main函數開始。


int main(int argc, char **argv) {
    initServerConfig();
    initServer();
    aeCreateFileEvent(server.el, server.fd, AE_READABLE, acceptHandler, NULL, NULL)
    aeMain(server.el);
    return 0;
}

下面就四個函數進行分析。

1 initServerConfig

static void initServerConfig() {
    server.dbnum = REDIS_DEFAULT_DBNUM;
    server.port = REDIS_SERVERPORT;
    server.verbosity = REDIS_DEBUG;
    server.maxidletime = REDIS_MAXIDLETIME;
    server.saveparams = NULL;
    server.logfile = NULL; /* NULL = log on standard output */
    server.bindaddr = NULL;
    server.glueoutputbuf = 1;
    server.daemonize = 0;
    server.pidfile = "/var/run/redis.pid";
    server.dbfilename = "dump.rdb";
    server.requirepass = NULL;
    server.shareobjects = 0;
    server.maxclients = 0;
    ResetServerSaveParams();

    appendServerSaveParams(60*60,1);  /* save after 1 hour and 1 change */
    appendServerSaveParams(300,100);  /* save after 5 minutes and 100 changes */
    appendServerSaveParams(60,10000); /* save after 1 minute and 10000 changes */
    /* Replication related */
    server.isslave = 0;
    server.masterhost = NULL;
    server.masterport = 6379;
    server.master = NULL;
    server.replstate = REDIS_REPL_NONE;
}

該函數是對redis核心結構體server(struct redisServer server)進行初始化。

2 initServer

static void initServer() {
    int j;
	// 修改這兩個信號的處理函數爲忽略,即不處理,如果不設置但又收到這個信號,會導致進程退出
    signal(SIGHUP, SIG_IGN);
    signal(SIGPIPE, SIG_IGN);
	// clients列表,每次收到請求的時候會初始化一個client
    server.clients = listCreate();
    server.slaves = listCreate();
    server.monitors = listCreate();
    server.objfreelist = listCreate();
    createSharedObjects();
    // 創建一個事件循環的結構體
    server.el = aeCreateEventLoop();
    server.db = zmalloc(sizeof(redisDb)*server.dbnum);
    server.sharingpool = dictCreate(&setDictType,NULL);
    server.sharingpoolsize = 1024;
 	// 啓動服務器
    server.fd = anetTcpServer(server.neterr, server.port, server.bindaddr);
   
    for (j = 0; j < server.dbnum; j++) {
        server.db[j].dict = dictCreate(&hashDictType,NULL);
        server.db[j].expires = dictCreate(&setDictType,NULL);
        server.db[j].id = j;
    }
    server.cronloops = 0;
    server.bgsaveinprogress = 0;
    server.lastsave = time(NULL);
    server.dirty = 0;
    server.usedmemory = 0;
    server.stat_numcommands = 0;
    server.stat_numconnections = 0;
    server.stat_starttime = time(NULL);
    // 增加一個定時器
    aeCreateTimeEvent(server.el, 1000, serverCron, NULL, NULL);
}

我們主要看initServer函數中的anetTcpServer這個函數。

int anetTcpServer(char *err, int port, char *bindaddr)
{
    int s, on = 1;
    struct sockaddr_in sa;
    
    if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        anetSetError(err, "socket: %s\n", strerror(errno));
        return ANET_ERR;
    }
    if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
        anetSetError(err, "setsockopt SO_REUSEADDR: %s\n", strerror(errno));
        close(s);
        return ANET_ERR;
    }
    memset(&sa,0,sizeof(sa));
    sa.sin_family = AF_INET;
    sa.sin_port = htons(port);
    sa.sin_addr.s_addr = htonl(INADDR_ANY);
    if (bindaddr) {
        if (inet_aton(bindaddr, &sa.sin_addr) == 0) {
            anetSetError(err, "Invalid bind address\n");
            close(s);
            return ANET_ERR;
        }
    }
    if (bind(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
        anetSetError(err, "bind: %s\n", strerror(errno));
        close(s);
        return ANET_ERR;
    }
    if (listen(s, 64) == -1) {
        anetSetError(err, "listen: %s\n", strerror(errno));
        close(s);
        return ANET_ERR;
    }
    return s;
}

這個函數的邏輯就是經典的網絡socket編程流程。最後通過listen函數,啓動服務器。。

3 aeCreateFileEvent(server.el, server.fd, AE_READABLE, acceptHandler, NULL, NULL)

int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
        aeFileProc *proc, void *clientData,
        aeEventFinalizerProc *finalizerProc)
{
    aeFileEvent *fe;

    fe = zmalloc(sizeof(*fe));
    if (fe == NULL) return AE_ERR;
    fe->fd = fd;
    fe->mask = mask;
    fe->fileProc = proc;
    fe->finalizerProc = finalizerProc;
    fe->clientData = clientData;
    fe->next = eventLoop->fileEventHead;
    eventLoop->fileEventHead = fe;
    return AE_OK;
}

aeCreateFileEvent函數在事件驅動那篇文章分析過,他就封裝一個結構體,然後在事件循環的時候註冊到事件驅動模塊(本版本是使用select函數)。這裏是註冊可讀事件,回調函數是acceptHandler。

4 aeMain

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

該函數在事件驅動文章也分析過,就是註冊事件到事件驅動模塊,然後開始等待事件發生,接着處理回調,根據3的分析,當有連接到來,會執行acceptHandler。

static void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
    int cport, cfd;
    char cip[128];
    redisClient *c;
    // 調用accept摘下一個請求
    cfd = anetAccept(server.neterr, fd, cip, &cport);
    // 創建一個client
    if ((c = createClient(cfd)) == NULL) {
        //
    }
}

該函數摘下一個請求的節點,然後創建一個client處理該請求。


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

    anetNonBlock(NULL,fd);
    anetTcpNoDelay(NULL,fd);
    if (!c) return NULL;
    selectDb(c,0);
    c->fd = fd;
    c->querybuf = sdsempty();
    c->argc = 0;
    c->argv = NULL;
    c->bulklen = -1;
    c->sentlen = 0;
    c->flags = 0;
    c->lastinteraction = time(NULL);
    c->authenticated = 0;
    c->replstate = REDIS_REPL_NONE;
    if ((c->reply = listCreate()) == NULL) oom("listCreate");
    listSetFreeMethod(c->reply,decrRefCount);
    listSetDupMethod(c->reply,dupClientReplyValue);
    if (aeCreateFileEvent(server.el, c->fd, AE_READABLE,
        readQueryFromClient, c, NULL) == AE_ERR) {
        freeClient(c);
        return NULL;
    }
    if (!listAddNodeTail(server.clients,c)) oom("listAddNodeTail");
    return c;
}

我們接着看aeCreateFileEvent(server.el, c->fd, AE_READABLE,readQueryFromClient, c, NULL)這一句,在accept的fd(和客戶端通信的fd)上註冊可讀事件(即等待客戶端發送數據過來),然後執行readQueryFromClient。readQueryFromClient函數主要是讀取客戶端發送過來的數據,然後調processCommand。

static int processCommand(redisClient *c) {
    struct redisCommand *cmd;
    long long dirty;
	// 退出命令
    if (!strcasecmp(c->argv[0]->ptr,"quit")) {
        freeClient(c);
        return 0;
    }
    // 根據客戶端的命令找到對應的命令配置
    cmd = lookupCommand(c->argv[0]->ptr);
    // ...
    // 執行對應的處理函數
    cmd->proc(c);
    return 1;
}

上面函數根據用戶發送的命令執行對應的函數。redis的命令配置表爲。

static struct redisCommand cmdTable[] = {
    {"get",getCommand,2,REDIS_CMD_INLINE},
    ...
};

假設客戶端發送的是get命令,則執行的函數爲getCommand。


static void getCommand(redisClient *c) {
	// 讀取客戶端發送的數據
    robj *o = lookupKeyRead(c->db,c->argv[1]);
	addReply(c,o);
}

static void addReply(redisClient *c, robj *obj) {
    aeCreateFileEvent(server.el, c->fd, AE_WRITABLE, sendReplyToClient, c, NULL)
}

給客戶端fd註冊可寫事件,準備給客戶端回覆信息。事件觸發時執行的函數是sendReplyToClient。

static void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
    redisClient *c = privdata;
    //...
    nwritten = write(fd, ((char*)o->ptr)+c->sentlen, objlen - c->sentlen);
}

發送回包給客戶端。
這就是redis啓動到處理客戶端信息的整體流程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章