1. 程式人生 > >Redis 命令執行過程(上)

Redis 命令執行過程(上)

今天我們來了解一下 Redis 命令執行的過程。在之前的文章中《當 Redis 發生高延遲時,到底發生了什麼》我們曾簡單的描述了一條命令的執行過程,本篇文章展示深入說明一下,加深讀者對 Redis 的瞭解。

如下圖所示,一條命令執行完成並且返回資料一共涉及三部分,第一步是建立連線階段,響應了socket的建立,並且建立了client物件;第二步是處理階段,從socket讀取資料到輸入緩衝區,然後解析並獲得命令,執行命令並將返回值儲存到輸出緩衝區中;第三步是資料返回階段,將返回值從輸出緩衝區寫到socket中,返回給客戶端,最後關閉client。

這三個階段之間是通過事件機制串聯了,在 Redis 啟動階段首先要註冊socket連線建立事件處理器:

  • 當客戶端發來建立socket的連線的請求時,對應的處理器方法會被執行,建立連線階段的相關處理就會進行,然後註冊socket讀取事件處理器
  • 當客戶端發來命令時,讀取事件處理器方法會被執行,對應處理階段的相關邏輯都會被執行,然後註冊socket寫事件處理器
  • 當寫事件處理器被執行時,就是將返回值寫回到socket中。

接下來,我們分別來看一下各個步驟的具體原理和程式碼實現。

啟動時監聽socket

Redis 伺服器啟動時,會呼叫 initServer 方法,首先會建立 Redis 自己的事件機制 eventLoop,然後在其上註冊週期時間事件處理器,最後在所監聽的 socket 上
建立檔案事件處理器,監聽 socket 建立連線的事件,其處理函式為 acceptTcpHandler。

void initServer(void) { // server.c
    ....
    /**
     * 建立eventLoop
     */
    server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
    /* Open the TCP listening socket for the user commands. */

    if (server.port != 0 &&
        listenToPort(server.port,server.ipfd,&server.ipfd_count) == C_ERR)
        exit(1);

    /**
     * 註冊週期時間事件,處理後臺操作,比如說客戶端操作、過期鍵等
     */
    if (aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
        serverPanic("Can't create event loop timers.");
        exit(1);
    }
    /**
     * 為所有監聽的socket建立檔案事件,監聽可讀事件;事件處理函式為acceptTcpHandler
     * 
     */
    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.");
            }
    }
    ....
}

在《Redis 事件機制詳解》一文中,我們曾詳細介紹過 Redis 的事件機制,可以說,Redis 命令執行過程中都是由事件機制協調管理的,也就是 initServer 方法中生成的 aeEventLoop。當socket發生對應的事件時,aeEventLoop 對呼叫已經註冊的對應的事件處理器。

建立連線和Client

當客戶端向 Redis 建立 socket時,aeEventLoop 會呼叫 acceptTcpHandler 處理函式,伺服器會為每個連結建立一個 Client 物件,並建立相應檔案事件來監聽socket的可讀事件,並指定事件處理函式。

acceptTcpHandler 函式會首先呼叫 anetTcpAccept方法,它底層會呼叫 socket 的 accept 方法,也就是接受客戶端來的建立連線請求,然後呼叫 acceptCommonHandler方法,繼續後續的邏輯處理。

// 當客戶端建立連結時進行的eventloop處理函式  networking.c
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
    ....
    // 層層呼叫,最後在anet.c 中 anetGenericAccept 方法中呼叫 socket 的 accept 方法
    cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
    if (cfd == ANET_ERR) {
        if (errno != EWOULDBLOCK)
            serverLog(LL_WARNING,
                "Accepting client connection: %s", server.neterr);
        return;
    }
    serverLog(LL_VERBOSE,"Accepted %s:%d", cip, cport);
    /**
     * 進行socket 建立連線後的處理
     */
    acceptCommonHandler(cfd,0,cip);
}

acceptCommonHandler 則首先呼叫 createClient 建立 client,接著判斷當前 client 的數量是否超出了配置的 maxclients,如果超過,則給客戶端傳送錯誤資訊,並且釋放 client。

static void acceptCommonHandler(int fd, int flags, char *ip) { //networking.c
    client *c;
    // 建立redisClient
    c = createClient(fd)
    // 當 maxClient 屬性被設定,並且client數量已經超出時,給client傳送error,然後釋放連線
    if (listLength(server.clients) > server.maxclients) {
        char *err = "-ERR max number of clients reached\r\n";
        if (write(c->fd,err,strlen(err)) == -1) {
        }
        server.stat_rejected_conn++;
        freeClient(c);
        return;
    }
    .... // 處理為設定密碼時預設保護狀態的客戶端連線
    // 統計連線數
    server.stat_numconnections++;
    c->flags |= flags;
}

createClient 方法用於建立 client,它代表著連線到 Redis 客戶端,每個客戶端都有各自的輸入緩衝區和輸出緩衝區,輸入緩衝區儲存客戶端通過 socket 傳送過來的資料,輸出緩衝區則儲存著 Redis 對客戶端的響應資料。client一共有三種類型,不同型別的對應緩衝區的大小都不同。

  • 普通客戶端是除了複製和訂閱的客戶端之外的所有連線
  • 從客戶端用於主從複製,主節點會為每個從節點單獨建立一條連線用於命令複製
  • 訂閱客戶端用於釋出訂閱功能

createClient 方法除了建立 client 結構體並設定其屬性值外,還會對 socket進行配置並註冊讀事件處理器

設定 socket 為 非阻塞 socket、設定 NO_DELAY 和 SO_KEEPALIVE標誌位來關閉 Nagle 演算法並且啟動 socket 存活檢查機制。

設定讀事件處理器,當客戶端通過 socket 傳送來資料後,Redis 會呼叫 readQueryFromClient 方法。

client *createClient(int fd) {
    client *c = zmalloc(sizeof(client));
    // fd 為 -1,表示其他特殊情況建立的client,redis在進行比如lua指令碼執行之類的情況下也會建立client
    if (fd != -1) {
        // 配置socket為非阻塞、NO_DELAY不開啟Nagle演算法和SO_KEEPALIVE
        anetNonBlock(NULL,fd);
        anetEnableTcpNoDelay(NULL,fd);
        if (server.tcpkeepalive)
            anetKeepAlive(NULL,fd,server.tcpkeepalive);
        /**
         * 向 eventLoop 中註冊了 readQueryFromClient。
         * readQueryFromClient 的作用就是從client中讀取客戶端的查詢緩衝區內容。
         * 繫結讀事件到事件 loop (開始接收命令請求)
         */
        if (aeCreateFileEvent(server.el,fd,AE_READABLE,
            readQueryFromClient, c) == AE_ERR)
        {
            close(fd);
            zfree(c);
            return NULL;
        }
    }
    // 預設選擇資料庫
    selectDb(c,0);
    uint64_t client_id;
    atomicGetIncr(server.next_client_id,client_id,1);
    c->id = client_id;
    c->fd = fd;
    .... // 設定client的屬性
    return c;
}

client 的屬性中有很多屬性,比如後邊會看到的輸入緩衝區 querybuf 和輸出緩衝區 buf,這裡因為程式碼過長做了省略,感興趣的同學可以自行閱讀原始碼。

讀取socket資料到輸入緩衝區

readQueryFromClient 方法會呼叫 read 方法從 socket 中讀取資料到輸入緩衝區中,然後判斷其大小是否大於系統設定的 client_max_querybuf_len,如果大於,則向 Redis返回錯誤資訊,並關閉 client。

將資料讀取到輸入緩衝區後,readQueryFromClient 方法會根據 client 的型別來做不同的處理,如果是普通型別,則直接呼叫 processInputBuffer 來處理;如果是主從客戶端,還需要將命令同步到自己的從伺服器中。也就是說,Redis例項將主例項傳來的命令執行後,繼續將命令同步給自己的從例項。

// 處理從client中讀取客戶端的輸入緩衝區內容。
void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
    client *c = (client*) privdata;
    ....
    if (c->querybuf_peak < qblen) c->querybuf_peak = qblen;
    c->querybuf = sdsMakeRoomFor(c->querybuf, readlen);
    // 從 fd 對應的socket中讀取到 client 中的 querybuf 輸入緩衝區
    nread = read(fd, c->querybuf+qblen, readlen);
    if (nread == -1) {
        .... // 出錯釋放 client
    } else if (nread == 0) {
        // 客戶端主動關閉 connection
        serverLog(LL_VERBOSE, "Client closed connection");
        freeClient(c);
        return;
    } else if (c->flags & CLIENT_MASTER) { 
        /*
         * 當這個client代表主從的master節點時,將query buffer和 pending_querybuf結合
         * 用於主從複製中的命令傳播????
         */
        c->pending_querybuf = sdscatlen(c->pending_querybuf,
                                        c->querybuf+qblen,nread);
    }
    // 增加已經讀取的位元組數
    sdsIncrLen(c->querybuf,nread);
    c->lastinteraction = server.unixtime;
    if (c->flags & CLIENT_MASTER) c->read_reploff += nread;
    server.stat_net_input_bytes += nread;
    // 如果大於系統配置的最大客戶端快取區大小,也就是配置檔案中的client-query-buffer-limit
    if (sdslen(c->querybuf) > server.client_max_querybuf_len) {
        sds ci = catClientInfoString(sdsempty(),c), bytes = sdsempty();
        // 返回錯誤資訊,並且關閉client
        bytes = sdscatrepr(bytes,c->querybuf,64);
        serverLog(LL_WARNING,"Closing client that reached max query buffer length: %s (qbuf initial bytes: %s)", ci, bytes);
        sdsfree(ci);
        sdsfree(bytes);
        freeClient(c);
        return;
    }

    
    if (!(c->flags & CLIENT_MASTER)) {
        // processInputBuffer 處理輸入緩衝區
        processInputBuffer(c);
    } else {
        // 如果client是master的連線
        size_t prev_offset = c->reploff;
        processInputBuffer(c);
        // 判斷是否同步偏移量發生變化,則通知到後續的slave
        size_t applied = c->reploff - prev_offset;

        if (applied) {
            replicationFeedSlavesFromMasterStream(server.slaves,
                    c->pending_querybuf, applied);
            sdsrange(c->pending_querybuf,applied,-1);
        }
    }
}

解析獲取命令

processInputBuffer 主要是將輸入緩衝區中的資料解析成對應的命令,根據命令型別是 PROTO_REQ_MULTIBULK 還是 PROTO_REQ_INLINE,來分別呼叫 processInlineBuffer 和 processMultibulkBuffer 方法來解析命令。

然後呼叫 processCommand 方法來執行命令。執行成功後,如果是主從客戶端,還需要更新同步偏移量 reploff 屬性,然後重置 client,讓client可以接收一條命令。

void processInputBuffer(client *c) { // networking.c
    server.current_client = c;
    /* 當緩衝區中還有資料時就一直處理 */
    while(sdslen(c->querybuf)) {
        .... // 處理 client 的各種狀態
        /* 判斷命令請求型別 telnet傳送的命令和redis-cli傳送的命令請求格式不同 */
        if (!c->reqtype) {
            if (c->querybuf[0] == '*') {
                c->reqtype = PROTO_REQ_MULTIBULK;
            } else {
                c->reqtype = PROTO_REQ_INLINE;
            }
        }
        /**
         * 從緩衝區解析命令
         */
        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");
        }

        /* 引數個數為0時重置client,可以接受下一個命令 */
        if (c->argc == 0) {
            resetClient(c);
        } else {
            // 執行命令
            if (processCommand(c) == C_OK) {
                if (c->flags & CLIENT_MASTER && !(c->flags & CLIENT_MULTI)) {
                    // 如果是master的client發來的命令,則 更新 reploff
                    c->reploff = c->read_reploff - sdslen(c->querybuf);
                }

                // 如果不是阻塞狀態,則重置client,可以接受下一個命令
                if (!(c->flags & CLIENT_BLOCKED) || c->btype != BLOCKED_MODULE)
                    resetClient(c);
            }
        }
    }
    server.current_client = NULL;
}

解析命令暫時不看,就是將 redis 命令文字資訊,記錄到client的argv/argc屬性中

執行命令

processCommand 方法會處理很多邏輯,不過大致可以分為三個部分:首先是呼叫 lookupCommand 方法獲得對應的 redisCommand;接著是檢測當前 Redis 是否可以執行該命令;最後是呼叫 call 方法真正執行命令。

processCommand會做如下邏輯處理:

  • 1 如果命令名稱為 quit,則直接返回,並且設定客戶端標誌位。
  • 2 根據 argv[0] 查詢對應的 redisCommand,所有的命令都儲存在命令字典 redisCommandTable 中,根據命令名稱可以獲取對應的命令。
  • 3 進行使用者許可權校驗。
  • 4 如果是叢集模式,處理叢集重定向。當命令傳送者是 master 或者 命令沒有任何 key 的引數時可以不重定向。
  • 5 預防 maxmemory 情況,先嚐試回收一下,如果不行,則返回異常。
  • 6 當此伺服器是 master 時:aof 持久化失敗時,或上一次 bgsave 執行錯誤,且配置 bgsave 引數和 stop_writes_on_bgsave_err;禁止執行寫命令。
  • 7 當此伺服器時master時:如果配置了 repl_min_slaves_to_write,當slave數目小於時,禁止執行寫命令。
  • 8 當時只讀slave時,除了 master 的不接受其他寫命令。
  • 9 當客戶端正在訂閱頻道時,只會執行部分命令。
  • 10 伺服器為slave,但是沒有連線 master 時,只會執行帶有 CMD_STALE 標誌的命令,如 info 等
  • 11 正在載入資料庫時,只會執行帶有 CMD_LOADING 標誌的命令,其餘都會被拒絕。
  • 12 當伺服器因為執行lua指令碼阻塞時,只會執行部分命令,其餘都會拒絕
  • 13 如果是事務命令,則開啟事務,命令進入等待佇列;否則直接執行命令。
int processCommand(client *c) {
    // 1 處理 quit 命令
    if (!strcasecmp(c->argv[0]->ptr,"quit")) {
        addReply(c,shared.ok);
        c->flags |= CLIENT_CLOSE_AFTER_REPLY;
        return C_ERR;
    }

    /**
     * 根據 argv[0] 查詢對應的 command
     * 2 命令字典查詢指定命令;所有的命令都儲存在命令字典中 struct redisCommand redisCommandTable[]={}
     */
    c->cmd = c->lastcmd = lookupCommand(c->argv[0]->ptr);
    if (!c->cmd) {
        // 處理未知命令
    } else if ((c->cmd->arity > 0 && c->cmd->arity != c->argc) ||
               (c->argc < -c->cmd->arity)) {
        // 處理引數錯誤
    }
    // 3 檢查使用者驗證
    if (server.requirepass && !c->authenticated && c->cmd->proc != authCommand)
    {
        flagTransaction(c);
        addReply(c,shared.noautherr);
        return C_OK;
    }

    /**
     * 4 如果是叢集模式,處理叢集重定向。當命令傳送者是master或者 命令沒有任何key的引數時可以不重定向
     */
    if (server.cluster_enabled &&
        !(c->flags & CLIENT_MASTER) &&
        !(c->flags & CLIENT_LUA &&
          server.lua_caller->flags & CLIENT_MASTER) &&
        !(c->cmd->getkeys_proc == NULL && c->cmd->firstkey == 0 &&
          c->cmd->proc != execCommand))
    {
        int hashslot;
        int error_code;
        // 查詢可以執行的node資訊
        clusterNode *n = getNodeByQuery(c,c->cmd,c->argv,c->argc,
                                        &hashslot,&error_code);
        if (n == NULL || n != server.cluster->myself) {
            if (c->cmd->proc == execCommand) {
                discardTransaction(c);
            } else {
                flagTransaction(c);
            }
            clusterRedirectClient(c,n,hashslot,error_code);
            return C_OK;
        }
    }

    // 5 處理maxmemory請求,先嚐試回收一下,如果不行,則返回異常
    if (server.maxmemory) {
        int retval = freeMemoryIfNeeded();
        ....
    }

    /**
     * 6 當此伺服器是master時:aof持久化失敗時,或上一次bgsave執行錯誤,
     * 且配置bgsave引數和stop_writes_on_bgsave_err;禁止執行寫命令
     */
    if (((server.stop_writes_on_bgsave_err &&
          server.saveparamslen > 0 &&
          server.lastbgsave_status == C_ERR) ||
          server.aof_last_write_status == C_ERR) &&
        server.masterhost == NULL &&
        (c->cmd->flags & CMD_WRITE ||
         c->cmd->proc == pingCommand)) { .... }

    /**
     * 7 當此伺服器時master時:如果配置了repl_min_slaves_to_write,
     * 當slave數目小於時,禁止執行寫命令
     */
    if (server.masterhost == NULL &&
        server.repl_min_slaves_to_write &&
        server.repl_min_slaves_max_lag &&
        c->cmd->flags & CMD_WRITE &&
        server.repl_good_slaves_count < server.repl_min_slaves_to_write) { .... }

    /**
     * 8 當時只讀slave時,除了master的不接受其他寫命令
     */
    if (server.masterhost && server.repl_slave_ro &&
        !(c->flags & CLIENT_MASTER) &&
        c->cmd->flags & CMD_WRITE) { .... }

    /**
     * 9 當客戶端正在訂閱頻道時,只會執行以下命令
     */
    if (c->flags & CLIENT_PUBSUB &&
        c->cmd->proc != pingCommand &&
        c->cmd->proc != subscribeCommand &&
        c->cmd->proc != unsubscribeCommand &&
        c->cmd->proc != psubscribeCommand &&
        c->cmd->proc != punsubscribeCommand) { .... }
    /**
     * 10 伺服器為slave,但沒有正確連線master時,只會執行帶有CMD_STALE標誌的命令,如info等
     */
    if (server.masterhost && server.repl_state != REPL_STATE_CONNECTED &&
        server.repl_serve_stale_data == 0 &&
        !(c->cmd->flags & CMD_STALE)) {...}
    /**
     * 11 正在載入資料庫時,只會執行帶有CMD_LOADING標誌的命令,其餘都會被拒絕
     */
    if (server.loading && !(c->cmd->flags & CMD_LOADING)) { .... }
    /**
     * 12 當伺服器因為執行lua指令碼阻塞時,只會執行以下幾個命令,其餘都會拒絕
     */
    if (server.lua_timedout &&
          c->cmd->proc != authCommand &&
          c->cmd->proc != replconfCommand &&
        !(c->cmd->proc == shutdownCommand &&
          c->argc == 2 &&
          tolower(((char*)c->argv[1]->ptr)[0]) == 'n') &&
        !(c->cmd->proc == scriptCommand &&
          c->argc == 2 &&
          tolower(((char*)c->argv[1]->ptr)[0]) == 'k')) {....}

    /**
     * 13 開始執行命令
     */
    if (c->flags & CLIENT_MULTI &&
        c->cmd->proc != execCommand && c->cmd->proc != discardCommand &&
        c->cmd->proc != multiCommand && c->cmd->proc != watchCommand)
    {
        /**
         * 開啟了事務,命令只會入佇列
         */
        queueMultiCommand(c);
        addReply(c,shared.queued);
    } else {
        /**
         * 直接執行命令
         */
        call(c,CMD_CALL_FULL);
        c->woff = server.master_repl_offset;
        if (listLength(server.ready_keys))
            handleClientsBlockedOnLists();
    }
    return C_OK;
}


struct redisCommand redisCommandTable[] = {
    {"get",getCommand,2,"rF",0,NULL,1,1,1,0,0},
    {"set",setCommand,-3,"wm",0,NULL,1,1,1,0,0},
    {"hmset",hsetCommand,-4,"wmF",0,NULL,1,1,1,0,0},
    .... // 所有的 redis 命令都有
}

call 方法是 Redis 中執行命令的通用方法,它會處理通用的執行命令的前置和後續操作。

  • 如果有監視器 monitor,則需要將命令傳送給監視器。
  • 呼叫 redisCommand 的proc 方法,執行對應具體的命令邏輯。
  • 如果開啟了 CMD_CALL_SLOWLOG,則需要記錄慢查詢日誌
  • 如果開啟了 CMD_CALL_STATS,則需要記錄一些統計資訊
  • 如果開啟了 CMD_CALL_PROPAGATE,則當 dirty大於0時,需要呼叫 propagate 方法來進行命令傳播。

命令傳播就是將命令寫入 repl-backlog-buffer 緩衝中,併發送給各個從伺服器中。

// 執行client中持有的 redisCommand 命令
void call(client *c, int flags) {
    /**
     * dirty記錄資料庫修改次數;start記錄命令開始執行時間us;duration記錄命令執行花費時間
     */
    long long dirty, start, duration;
    int client_old_flags = c->flags;

    /**
     * 有監視器的話,需要將不是從AOF獲取的命令會發送給監視器。當然,這裡會消耗時間
     */
    if (listLength(server.monitors) &&
        !server.loading &&
        !(c->cmd->flags & (CMD_SKIP_MONITOR|CMD_ADMIN)))
    {
        replicationFeedMonitors(c,server.monitors,c->db->id,c->argv,c->argc);
    }
    ....
    /* Call the command. */
    dirty = server.dirty;
    start = ustime();
    // 處理命令,呼叫命令處理函式
    c->cmd->proc(c);
    duration = ustime()-start;
    dirty = server.dirty-dirty;
    if (dirty < 0) dirty = 0;

    .... // Lua 指令碼的一些特殊處理

    /**
     * CMD_CALL_SLOWLOG 表示要記錄慢查詢日誌
     */
    if (flags & CMD_CALL_SLOWLOG && c->cmd->proc != execCommand) {
        char *latency_event = (c->cmd->flags & CMD_FAST) ?
                              "fast-command" : "command";
        latencyAddSampleIfNeeded(latency_event,duration/1000);
        slowlogPushEntryIfNeeded(c,c->argv,c->argc,duration);
    }
    /**
     * CMD_CALL_STATS 表示要統計
     */
    if (flags & CMD_CALL_STATS) {
        c->lastcmd->microseconds += duration;
        c->lastcmd->calls++;
    }
    /**
     * CMD_CALL_PROPAGATE表示要進行廣播命令
     */
    if (flags & CMD_CALL_PROPAGATE &&
        (c->flags & CLIENT_PREVENT_PROP) != CLIENT_PREVENT_PROP)
    {
        int propagate_flags = PROPAGATE_NONE;
        /**
         * dirty大於0時,需要廣播命令給slave和aof
         */
        if (dirty) propagate_flags |= (PROPAGATE_AOF|PROPAGATE_REPL);
        .... 
        /**
         * 廣播命令,寫如aof,傳送命令到slave
         * 也就是傳說中的傳播命令
         */
        if (propagate_flags != PROPAGATE_NONE && !(c->cmd->flags & CMD_MODULE))
            propagate(c->cmd,c->db->id,c->argv,c->argc,propagate_flags);
    }
    ....
}

由於文章篇幅問題,本篇文章就先講到這裡,後半部分在接下來的文章中進行講解,歡迎大家繼續關注。

個人部落格,歡迎來玩

相關推薦

Redis 命令執行過程()

今天我們來了解一下 Redis 命令執行的過程。在之前的文章中《當 Redis 發生高延遲時,到底發生了什麼》我們曾簡單的描述了一條命令的執行過程,本篇文章展示深入說明一下,加深讀者對 Redis 的瞭解。 如下圖所示,一條命令執行完成並且返回資料一共涉及三部分,第一步是建立連線階段,響應了socket的建立

Redis學習筆記】2018-06-21 redis命令執行過程 SET

作者:順風車運營研發團隊 李樂1.命令執行過程 1.1命令請求格式 當用戶在客戶端鍵入一條命令請求時,客戶端會將其按照特定協議轉換為字串,傳送給伺服器;伺服器解析字串,獲取命令請求; 例如,當用戶執行 set key value 時,轉換後的字串為 *3rn3rnset

Redis 命令執行過程(下)

在上一篇文章中《Redis 命令執行過程(上)》中,我們首先了解 Redis 命令執行的整體流程,然後細緻分析了從 Redis 啟動到建立 socket 連線,再到讀取 socket 資料到輸入緩衝區,解析命令,執行命令等過程的原理和實現細節。接下來,我們來具體看一下 set 和 get 命令的實現細節和如何

MySQL命令執行過程原始碼相關模組

參照  http://rrdba.cn/?p=35   畫了如下的圖,對MySQL sql命令的執行進行解讀。 初步瞭解,肯定有很多錯誤。 1.使用者在發出sql命令之後,mysql的執行緒管理器會建立或者重用一個執行緒,建立client到mysql伺服器的連線。m

ping命令執行過程及返回資訊分析

Ping命令幕後過程及其返回資訊分析 “Ping”的幕後過程 我們以下面一個網路為例:有A、B、C、D四臺機子,一臺路由RA,子網掩碼均為255.255.255.0,預設路由為192.16

mybatis原理解析---SqlSession執行過程()

sqlSession代表與資料庫的一次會話,在這次會話中可以多次執行查詢等sql操作。從前面可以看到SqlSession物件是從SqlSessionFactory物件中獲得的。sqlSession本身就定義了一系列的update select delete in

[17]_關於Ubuntu16.04.3終端下的命令列解析器bash和命令執行過程

1. 在Ubuntu16.04.3中開啟終端常用的幾種方式:1.同時按下鍵盤:Ctrl + Alt + t 這三個鍵; 2. 在介面空白處右鍵然後:Open Terminal   等等。 2. 說說在終端下的命令列解析器,大概過程是這樣的: (1)在鍵盤上每敲一個字元都會發

第8章2節《MonkeyRunner源代碼剖析》MonkeyRunner啟動執行過程-解析處理命令行參數

path 轉載 iss 命令 code rst pri bsp ack MonkeyRunnerStarter是MonkeyRunner啟動時的入口類,由於它裏面包括了main方法.它的整個啟動過程主要做了以下幾件事情:解析用戶啟動MonkeyRunner時從命令行傳輸

連接Redis執行命令錯誤 MISCONF Redis is configured to save RDB snapshots

ase disable logs stop 詳細信息 保存 info 數據集 運行 今天在redis中執行setrange name 1 chun 命令時報了如下錯誤提示: (error) MISCONF Redis is configured to save RDB sn

redis 命令的調用過程

以及 action href represent struct contents argv isp cut 參考文獻: Redis 是如何處理命令的(客戶端) 我是如何通過添加一條命令學習redis源碼的 從零開始寫redis客戶端(deerlet-redis-cli

命令執行過程

如果能 mark proc 命令執行 com roc 執行 存在 開始 1)用戶在命令行輸入命令,敲下回車2)系統判斷輸入的這個命令是否存在別名3)存在別名,解析別名,按照別名裏面的實際命令來進行下述操作4)從用戶的PATH變量裏面設置的路徑來查找命令。5)如果能找到命令,

命令執行robotframework 遇到 AutoItLibrary . Run執行exe傳指令碼失敗記錄

我在公司做自動化指令碼選擇的是robotframework 最近公司領導要求把做的頁面自動化成果展示在jenkins上,那就需要把寫好的指令碼放到Windows環境上,從jenkins上啟動指令碼使用的是最簡單粗暴的方式:命令列執行pybot D:\robotframe\web\ 當時使用在

MacOS python shell下使用命令執行ok,但是在eclipse執行失敗

問題描述:mac 系統,使用pip 安裝了python 模組之後,在shell 下匯入模組ok,程式執行也OK。在eclipse下沒有報模組不存在的問題,但是程式執行時模組呼叫的某一部分一直報錯。 問題分析:因為安裝的python 模組依賴比較多,當時因為許可權的問題沒有安裝成功反覆嘗試

Lvs-nat模式實現負載均衡的配置命令執行過程

一、實驗環境 三臺伺服器,一臺作為 director,兩臺作為 real server,director 有一個外網網絡卡(10.0.172.190) 和一個內網ip(192.168.0.10),兩個 real server 上只有內網 ip (192.168.0.11) 和 (192.168.

使用pipeline管道執行redis命令

pipeline管道可以減少後端與redis的連線次數,從而實現了優化。 原理如下:   使用方法: 未使用pipeline前: strict_redis = get_redis_connection('sms_codes') # type:StrictRedis

idea打包spark程式在叢集執行過程(1)

第一步: 第二步: 第三步: 第四步: 第五步: spark-submit --master yarn --deploy-mode cluster --driver-memory 4G --executor-memory 5g --num

kubernetes -- helm charts 開發: 4、 容器可以執行主機命令的方法

問題:一個容器需要能夠執行主機上某些命令 解決辦法: 容器內採用hostPath掛載,掛載的patch設定為主機上該命令的路徑 大致程式碼結構如下:   ... containers: - name: mycontainer image:

Maven依賴中的scope詳解,在eclipse裡面用maven install可以程式設計成功,到伺服器命令執行報VM crash錯誤

 Maven依賴中的scope詳解 專案中用了<scope>test</scope>在eclipse裡面用maven install可以編譯成功,到伺服器上用命令執行報VM crash錯誤,原因是test程式碼提交上去了,但沒有對應的junit包導致的 解決辦法:1.伺服器上編譯的時

關於JSch的使用,執行ssh命令,檔案傳和下載以及連線方式

最近在做一個SAAS服務的專案,SAAS就是軟體即服務,具體可以去問度娘,然後底層呢需要遠端執行SSH命令來進行支援,最後就選擇了JSch來完成這個工作。 JSch是SSH2的一個純JAVA實現。它允許你連線到一個sshd伺服器,使用埠轉發,X11轉發,檔案傳輸等等。 大致

explain命令檢視SQL執行過程的結果分析

最近慢慢接觸MySQL,瞭解如何優化它也迫在眉睫了,話說工欲善其事,必先利其器。最近我就打算了解下幾個優化MySQL中經常用到的工具。今天就簡單介紹下EXPLAIN。內容導航idselect_typetabletypepossible_keyskeykey_lenrefrow