redis之單線程命令處理

一、簡單介紹

衆所周知,redis非常快,而且是單線程的,這裏說的單線程是指單線程處理命令,而實際上redis是多線程的,很多異步操作都是給後臺線程進行操作的。
高效的原因:

  1. 內存級,讀寫速度快,不受磁盤IO限制
  2. 數據結構設計簡單以及高效,很多結構操作都是O(1)
  3. 單線程
    (1)沒有競爭,不需要鎖
    (2)沒有線程切換,不需要上下文切換
    (3)串行執行,保證每個操作都是原子性的


  4. IO多路複用架構,非阻塞IO

二、整體流程圖

redis之單線程命令處理

三、整體代碼邏輯

3.1 首先建立監聽套接字

main()
    ...
    initServer() ->
        ...
        //創建I/O多路複用器
        aeCreateEventLoop() ->
            aeApiCreate()
        ...
        //創建監聽socket
        listenToPort() ->
            ...
             anetTcp6Server()
            ...
            anetTcpServer()
            ...

3.2 註冊監聽事件處理回調

initServer()
    ...
    //註冊接收鏈接事件處理回調
     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.");
            }
    }
    ...
    //註冊定時任務定時器事件處理回調
    aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL)
    ...

3.3 主循環

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

aeProcessEvents()
    ...
    numevents = aeApiPoll(eventLoop, tvp);
    ...
     for (j = 0; j < numevents; j++) {
          ...
            if (mask & AE_READABLE) {
                fe->rfileProc(eventLoop,fd,fe->clientData,mask);
            }
            ...
            if (mask & AE_WRITABLE) {
                fe->wfileProc(eventLoop,fd,fe->clientData,mask);
            }
           ...
        }
    }

3.4 回調
3.4.1 接受鏈接,並註冊read處理函數

 fe->rfileProc(eventLoop,fd,fe->clientData,mask);
 acceptTcpHandler() ->
     acceptCommonHandler() ->
         createClient() ->
             connSetReadHandler(conn, readQueryFromClient);
                ...
                conn->type->set_read_handler(conn, func);
                ...

其中type是在創建conn的時候使用的全局變量CT_Socket
connCreateAcceptedSocket() ->
    ...
    connCreateSocket()
        ...
        conn->type = &CT_Socket;
        ...

ConnectionType CT_Socket = {
    .ae_handler = connSocketEventHandler,
    .close = connSocketClose,
    .write = connSocketWrite,
    .read = connSocketRead,
    .accept = connSocketAccept,
    .connect = connSocketConnect,
    .set_write_handler = connSocketSetWriteHandler,
    .set_read_handler = connSocketSetReadHandler,
    .get_last_error = connSocketGetLastError,
    .blocking_connect = connSocketBlockingConnect,
    .sync_write = connSocketSyncWrite,
    .sync_read = connSocketSyncRead,
    .sync_readline = connSocketSyncReadLine
};

static int connSocketSetReadHandler(connection *conn, ConnectionCallbackFunc func) {
    if (func == conn->read_handler) return C_OK;

    conn->read_handler = func;
    if (!conn->read_handler)
        aeDeleteFileEvent(server.el,conn->fd,AE_READABLE);
    else
        if (aeCreateFileEvent(server.el,conn->fd,
                    AE_READABLE,conn->type->ae_handler,conn) == AE_ERR) return C_ERR;
    return C_OK;
}

3.4.2 回調read 進行數據讀取

readQueryFromClient() ->
    ...
    connRead()
    ...

3.4.3 解析命令

readQueryFromClient() ->
    ...
    processInputBuffer()
        ...
         while(c->qb_pos < sdslen(c->querybuf)){
          ...
            if (c->reqtype == PROTO_REQ_INLINE) {
                if (processInlineBuffer(c) != C_OK) break;
            } else if (c->reqtype == PROTO_REQ_MULTIBULK) {
                if (processMultibulkBuffer(c) != C_OK) break;
            } else {
                serverPanic("Unknown request type");
            }
        ...
        }
    }
        ...

3.4.4 執行命令

readQueryFromClient() ->
    ...
     processInputBuffer() ->
         ...
         processCommandAndResetClient() ->
             ...
             processCommand()
             ...
         ...
     ...

3.4.5 將響應寫入隊列中

processCommand() ->
    ...
    call()
        ...
        //根據不同的請求命令,回調不同的處理函數,這裏使用get命令做示範
        c->cmd->proc(c);
        ...
    ...

//t_string.c
getCommand() ->
    getGenericCommand() 
        ...
        // 1.獲取對應key的值
        lookupKeyReadOrReply()
        // 處理響應數據
        addReply() ->
            ...
            // 2.將此客戶端連接加入到 鏈表server.clients_pending_write 中
            prepareClientToWrite() ->
                ...
                clientInstallWriteHandler()
                    ...
                    listAddNodeHead(server.clients_pending_write,c); //一個客戶端只會添加一次
                    ...
             // 3.將結果寫入到待發送區        
           _addReplyToBuffer()         

3.4.6 將數據發送給客戶端
//在select之前將數據發送出去

beforeSleep() ->
    ...
    handleClientsWithPendingWritesUsingThreads() ->
        ...
        handleClientsWithPendingWrites()
            ...
            // 遍歷有待發送數據的客戶端隊列
            listRewind(server.clients_pending_write,&li);
            while((ln = listNext(&li))) {
                client *c = listNodeValue(ln);
                c->flags &= ~CLIENT_PENDING_WRITE;
                listDelNode(server.clients_pending_write,ln);
                ...
                /* Try to write buffers to the client socket. */
                if (writeToClient(c,0) == C_ERR) continue;
                ...
              }  
            ...
        ...
    ...
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章