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

Redis 命令執行過程(下)

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

set 和 get 命令具體實現

前文講到 processCommand 方法會從輸入緩衝區中解析出對應的 redisCommand,然後呼叫 call 方法執行解析出來的 redisCommand的 proc 方法。不同命令的的 proc 方法是不同的,比如說名為 set 的 redisCommand 的 proc 是 setCommand 方法,而 get 的則是 getCommand 方法。通過這種形式,實際上實現在Java 中特別常見的多型策略。

void call(client *c, int flags) {
    ....
    c->cmd->proc(c);
    ....
}
// redisCommand結構體
struct redisCommand {
    char *name;
    // 對應方法的函式正規化
    redisCommandProc *proc;
    .... // 其他定義
};
// 使用 typedef 定義的別名
typedef void redisCommandProc(client *c);
// 不同的命令,呼叫不同的方法。
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 命令都有
}

setCommand 會判斷set命令是否攜帶了nx、xx、ex或者px等可選引數,然後呼叫setGenericCommand命令。我們直接來看 setGenericCommand 方法。

setGenericCommand 方法的處理邏輯如下所示:

  • 首先判斷 set 的型別是 set_nx 還是 set_xx,如果是 nx 並且 key 已經存在則直接返回;如果是 xx 並且 key 不存在則直接返回。
  • 呼叫 setKey 方法將鍵值新增到對應的 Redis 資料庫中。
  • 如果有過期時間,則呼叫 setExpire 將設定過期時間
  • 進行鍵空間通知
  • 返回對應的值給客戶端。
// t_string.c 
void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) {
    long long milliseconds = 0; 
    /**
     * 設定了過期時間;expire是robj型別,獲取整數值
     */
    if (expire) {
        if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) != C_OK)
            return;
        if (milliseconds <= 0) {
            addReplyErrorFormat(c,"invalid expire time in %s",c->cmd->name);
            return;
        }
        if (unit == UNIT_SECONDS) milliseconds *= 1000;
    }
    /**
     * NX,key存在時直接返回;XX,key不存在時直接返回
     * lookupKeyWrite 是在對應的資料庫中尋找鍵值是否存在
     */
    if ((flags & OBJ_SET_NX && lookupKeyWrite(c->db,key) != NULL) ||
        (flags & OBJ_SET_XX && lookupKeyWrite(c->db,key) == NULL))
    {
        addReply(c, abort_reply ? abort_reply : shared.nullbulk);
        return;
    }
    /**
     * 新增到資料字典
     */
    setKey(c->db,key,val);
    server.dirty++;
    /**
     * 過期時間新增到過期字典
     */
    if (expire) setExpire(c,c->db,key,mstime()+milliseconds);
    /**
     * 鍵空間通知
     */
    notifyKeyspaceEvent(NOTIFY_STRING,"set",key,c->db->id);
    if (expire) notifyKeyspaceEvent(NOTIFY_GENERIC,
        "expire",key,c->db->id);
    /**
     * 返回值,addReply 在 get 命令時再具體講解
     */
    addReply(c, ok_reply ? ok_reply : shared.ok);
}

具體 setKey 和 setExpire 的方法實現我們這裡就不細講,其實就是將鍵值新增到db的 dict 資料雜湊表中,將鍵和過期時間新增到 expires 雜湊表中,如下圖所示。

接下來看 getCommand 的具體實現,同樣的,它底層會呼叫 getGenericCommand 方法。

getGenericCommand 方法會呼叫 lookupKeyReadOrReply 來從 dict 資料雜湊表中查詢對應的 key值。如果找不到,則直接返回 C_OK;如果找到了,則根據值的型別,呼叫 addReply 或者 addReplyBulk 方法將值新增到輸出緩衝區中。

int getGenericCommand(client *c) {
    robj *o;
    // 呼叫 lookupKeyReadOrReply 從資料字典中查詢對應的鍵
    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL)
        return C_OK;
    // 如果是string型別,呼叫 addReply 單行返回。如果是其他物件型別,則呼叫 addReplyBulk
    if (o->type != OBJ_STRING) {
        addReply(c,shared.wrongtypeerr);
        return C_ERR;
    } else {
        addReplyBulk(c,o);
        return C_OK;
    }
}

lookupKeyReadWithFlags 會從 redisDb 中查詢對應的鍵值對,它首先會呼叫 expireIfNeeded判斷鍵是否過期並且需要刪除,如果為過期,則呼叫 lookupKey 方法從 dict 雜湊表中查詢並返回。具體解釋可以看程式碼中的詳細註釋

/*
 * 查詢key的讀操作,如果key找不到或者已經邏輯上過期返回 NULL,有一些副作用
 *   1 如果key到達過期時間,它會被裝置為過期,並且刪除
 *   2 更新key的最近訪問時間
 *   3 更新全域性快取擊中概率
 * flags 有兩個值: LOOKUP_NONE 一般都是這個;LOOKUP_NOTOUCH 不修改最近訪問時間
 */
robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) { // db.c
    robj *val;
    // 檢查鍵是否過期
    if (expireIfNeeded(db,key) == 1) {
        .... // master和 slave 對這種情況的特殊處理
    }
    // 查詢鍵值字典
    val = lookupKey(db,key,flags);
    // 更新全域性快取命中率
    if (val == NULL)
        server.stat_keyspace_misses++;
    else
        server.stat_keyspace_hits++;
    return val;
}

Redis 在呼叫查詢鍵值系列方法前都會先呼叫 expireIfNeeded 來判斷鍵是否過期,然後根據 Redis 是否配置了懶刪除來進行同步刪除或者非同步刪除。關於鍵刪除的細節可以檢視《詳解 Redis 記憶體管理機制和實現》一文。

在判斷鍵釋放過期的邏輯中有兩個特殊情況:

  • 如果當前 Redis 是主從結構中的從例項,則只判斷鍵是否過期,不直接對鍵進行刪除,而是要等待主例項傳送過來的刪除命令後再進行刪除。如果當前 Redis 是主例項,則呼叫 propagateExpire 來傳播過期指令。
  • 如果當前正在進行 Lua 指令碼執行,因為其原子性和事務性,整個執行過期中時間都按照其開始執行的那一刻計算,也就是說lua執行時未過期的鍵,在它整個執行過程中也都不會過期。

/*
 * 在呼叫 lookupKey*系列方法前呼叫該方法。
 * 如果是slave:
 *  slave 並不主動過期刪除key,但是返回值仍然會返回鍵已經被刪除。
 *  master 如果key過期了,會主動刪除過期鍵,並且觸發 AOF 和同步操作。
 * 返回值為0表示鍵仍然有效,否則返回1
 */
int expireIfNeeded(redisDb *db, robj *key) { // db.c
    // 獲取鍵的過期時間
    mstime_t when = getExpire(db,key);
    mstime_t now;

    if (when < 0) return 0;

    /*
     * 如果當前是在執行lua指令碼,根據其原子性,整個執行過期中時間都按照其開始執行的那一刻計算
     * 也就是說lua執行時未過期的鍵,在它整個執行過程中也都不會過期。
     */ 
    now = server.lua_caller ? server.lua_time_start : mstime();

    // slave 直接返回鍵是否過期
    if (server.masterhost != NULL) return now > when;
    // master時,鍵未過期直接返回
    if (now <= when) return 0;

    // 鍵過期,刪除鍵
    server.stat_expiredkeys++;
    // 觸發命令傳播
    propagateExpire(db,key,server.lazyfree_lazy_expire);
    // 和鍵空間事件
    notifyKeyspaceEvent(NOTIFY_EXPIRED,
        "expired",key,db->id);
    // 根據是否懶刪除,呼叫不同的函式 
    return server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
                                         dbSyncDelete(db,key);
}

lookupKey 方法則是通過 dictFind 方法從 redisDb 的 dict 雜湊表中查詢鍵值,如果能找到,則根據 redis 的 maxmemory_policy 策略來判斷是更新 lru 的最近訪問時間,還是呼叫 updateFU 方法更新其他指標,這些指標可以在後續記憶體不足時對鍵值進行回收。

robj *lookupKey(redisDb *db, robj *key, int flags) {
    // dictFind 根據 key 獲取字典的entry
    dictEntry *de = dictFind(db->dict,key->ptr);
    if (de) {
        // 獲取 value
        robj *val = dictGetVal(de);
        // 當處於 rdb aof 子程序複製階段或者 flags 不是 LOOKUP_NOTOUCH
        if (server.rdb_child_pid == -1 &&
            server.aof_child_pid == -1 &&
            !(flags & LOOKUP_NOTOUCH))
        {
            // 如果是 MAXMEMORY_FLAG_LFU 則進行相應操作
            if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
                updateLFU(val);
            } else {
                // 更新最近訪問時間
                val->lru = LRU_CLOCK();
            }
        }
        return val;
    } else {
        return NULL;
    }
}

將命令結果寫入輸出緩衝區

在所有的 redisCommand 執行的最後,一般都會呼叫 addReply 方法進行結果返回,我們的分析也來到了 Redis 命令執行的返回資料階段。

addReply 方法做了兩件事情:

  • prepareClientToWrite 判斷是否需要返回資料,並且將當前 client 新增到等待寫返回資料佇列中。
  • 呼叫 _addReplyToBuffer 和 _addReplyObjectToList 方法將返回值寫入到輸出緩衝區中,等待寫入 socekt。
void addReply(client *c, robj *obj) {
    if (prepareClientToWrite(c) != C_OK) return;
    if (sdsEncodedObject(obj)) {
        // 需要將響應內容新增到output buffer中。總體思路是,先嚐試向固定buffer新增,新增失敗的話,在嘗試新增到響應連結串列
        if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != C_OK)
            _addReplyObjectToList(c,obj);
    } else if (obj->encoding == OBJ_ENCODING_INT) {
        .... // 特殊情況的優化
    } else {
        serverPanic("Wrong obj->encoding in addReply()");
    }
}

prepareClientToWrite 首先判斷了當前 client是否需要返回資料:

  • Lua 指令碼執行的 client 則需要返回值;
  • 如果客戶端傳送來 REPLY OFF 或者 SKIP 命令,則不需要返回值;
  • 如果是主從複製時的主例項 client,則不需要返回值;
  • 當前是在 AOF loading 狀態的假 client,則不需要返回值。

接著如果這個 client 還未處於延遲等待寫入 (CLIENT_PENDING_WRITE)的狀態,則將其設定為該狀態,並將其加入到 Redis 的等待寫入返回值客戶端佇列中,也就是 clients_pending_write佇列。

int prepareClientToWrite(client *c) {
    // 如果是 lua client 則直接OK
    if (c->flags & (CLIENT_LUA|CLIENT_MODULE)) return C_OK;
    // 客戶端發來過 REPLY OFF 或者 SKIP 命令,不需要傳送返回值
    if (c->flags & (CLIENT_REPLY_OFF|CLIENT_REPLY_SKIP)) return C_ERR;

    // master 作為client 向 slave 傳送命令,不需要接收返回值
    if ((c->flags & CLIENT_MASTER) &&
        !(c->flags & CLIENT_MASTER_FORCE_REPLY)) return C_ERR;
    // AOF loading 時的假client 不需要返回值
    if (c->fd <= 0) return C_ERR; 

    // 將client加入到等待寫入返回值佇列中,下次事件週期會進行返回值寫入。
    if (!clientHasPendingReplies(c) &&
        !(c->flags & CLIENT_PENDING_WRITE) &&
        (c->replstate == REPL_STATE_NONE ||
         (c->replstate == SLAVE_STATE_ONLINE && !c->repl_put_online_on_ack)))
    {
        // 設定標誌位並且將client加入到 clients_pending_write 佇列中
        c->flags |= CLIENT_PENDING_WRITE;
        listAddNodeHead(server.clients_pending_write,c);
    }
    // 表示已經在排隊,進行返回資料
    return C_OK;
}

Redis 將儲存等待返回的響應資料的空間,也就是輸出緩衝區分成兩部分,一個固定大小的 buffer 和一個響應內容資料的連結串列。在連結串列為空並且 buffer 有足夠空間時,則將響應新增到 buffer 中。如果 buffer 滿了則建立一個節點追加到連結串列上。_addReplyToBuffer 和 _addReplyObjectToList 就是分別向這兩個空間寫資料的方法。

固定buffer和響應連結串列,整體上構成了一個佇列。這麼組織的好處是,既可以節省記憶體,不需一開始預先分配大塊記憶體,並且可以避免頻繁分配、回收記憶體。

上面就是響應內容寫入輸出緩衝區的過程,下面看一下將資料從輸出緩衝區寫入 socket 的過程。

prepareClientToWrite 函式,將客戶端加入到了Redis 的等待寫入返回值客戶端佇列中,也就是 clients_pending_write 佇列。請求處理的事件處理邏輯就結束了,等待 Redis 下一次事件迴圈處理時,將響應從輸出緩衝區寫入到 socket 中。

將命令返回值從輸出緩衝區寫入 socket

在 《Redis 事件機制詳解》
一文中我們知道,Redis 在兩次事件迴圈之間會呼叫 beforeSleep 方法處理一些事情,而對 clients_pending_write 列表的處理就在其中。

下面的 aeMain 方法就是 Redis 事件迴圈的主邏輯,可以看到每次迴圈時都會呼叫 beforesleep 方法。

void aeMain(aeEventLoop *eventLoop) { // ae.c
    eventLoop->stop = 0;
    while (!eventLoop->stop) {
        /* 如果有需要在事件處理前執行的函式,那麼執行它 */
        if (eventLoop->beforesleep != NULL)
            eventLoop->beforesleep(eventLoop);
        /* 開始處理事件*/
        aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);
    }
}

beforeSleep 函式會呼叫 handleClientsWithPendingWrites 函式來處理 clients_pending_write 列表。

handleClientsWithPendingWrites 方法會遍歷 clients_pending_write 列表,對於每個 client 都會先呼叫 writeToClient 方法來嘗試將返回資料從輸出快取區寫入到 socekt中,如果還未寫完,則只能呼叫 aeCreateFileEvent 方法來註冊一個寫資料事件處理器 sendReplyToClient,等待 Redis 事件機制的再次呼叫。

這樣的好處是對於返回資料較少的客戶端,不需要麻煩的註冊寫資料事件,等待事件觸發再寫資料到 socket,而是在下一次事件迴圈週期就直接將資料寫到 socket中,加快了資料返回的響應速度。

但是從這裡也會發現,如果 clients_pending_write 佇列過長,則處理時間也會很久,阻塞正常的事件響應處理,導致 Redis 後續命令延時增加。

// 直接將返回值寫到client的輸出緩衝區中,不需要進行系統呼叫,也不需要註冊寫事件處理器
int handleClientsWithPendingWrites(void) {
    listIter li;
    listNode *ln;
    // 獲取系統延遲寫佇列的長度
    int processed = listLength(server.clients_pending_write);

    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);

        // 將緩衝值寫入client的socket中,如果寫完,則跳過之後的操作。
        if (writeToClient(c->fd,c,0) == C_ERR) continue;

        // 還有資料未寫入,只能註冊寫事件處理器了
        if (clientHasPendingReplies(c)) {
            int ae_flags = AE_WRITABLE;
            if (server.aof_state == AOF_ON &&
                server.aof_fsync == AOF_FSYNC_ALWAYS)
            {
                ae_flags |= AE_BARRIER;
            }
            // 註冊寫事件處理器 sendReplyToClient,等待執行
            if (aeCreateFileEvent(server.el, c->fd, ae_flags,
                sendReplyToClient, c) == AE_ERR)
            {
                    freeClientAsync(c);
            }
        }
    }
    return processed;
}

sendReplyToClient 方法其實也會呼叫 writeToClient 方法,該方法就是將輸出緩衝區中的 buf 和 reply 列表中的資料都儘可能多的寫入到對應的 socket中。

// 將輸出緩衝區中的資料寫入socket,如果還有資料未處理則返回C_OK
int writeToClient(int fd, client *c, int handler_installed) {
    ssize_t nwritten = 0, totwritten = 0;
    size_t objlen;
    sds o;
    // 仍然有資料未寫入
    while(clientHasPendingReplies(c)) {
        // 如果緩衝區有資料
        if (c->bufpos > 0) {
            // 寫入到 fd 代表的 socket 中
            nwritten = write(fd,c->buf+c->sentlen,c->bufpos-c->sentlen);
            if (nwritten <= 0) break;
            c->sentlen += nwritten;
            // 統計本次一共輸出了多少子節
            totwritten += nwritten;

            // buffer中的資料已經發送,則重置標誌位,讓響應的後續資料寫入buffer
            if ((int)c->sentlen == c->bufpos) {
                c->bufpos = 0;
                c->sentlen = 0;
            }
        } else {
            // 緩衝區沒有資料,從reply佇列中拿
            o = listNodeValue(listFirst(c->reply));
            objlen = sdslen(o);

            if (objlen == 0) {
                listDelNode(c->reply,listFirst(c->reply));
                continue;
            }
            // 將佇列中的資料寫入 socket
            nwritten = write(fd, o + c->sentlen, objlen - c->sentlen);
            if (nwritten <= 0) break;
            c->sentlen += nwritten;
            totwritten += nwritten;
            // 如果寫入成功,則刪除佇列
            if (c->sentlen == objlen) {
                listDelNode(c->reply,listFirst(c->reply));
                c->sentlen = 0;
                c->reply_bytes -= objlen;
                if (listLength(c->reply) == 0)
                    serverAssert(c->reply_bytes == 0);
            }
        }
        // 如果輸出的位元組數量已經超過NET_MAX_WRITES_PER_EVENT限制,break
        if (totwritten > NET_MAX_WRITES_PER_EVENT &&
            (server.maxmemory == 0 ||
             zmalloc_used_memory() < server.maxmemory) &&
            !(c->flags & CLIENT_SLAVE)) break;
    }
    server.stat_net_output_bytes += totwritten;
    if (nwritten == -1) {
        if (errno == EAGAIN) {
            nwritten = 0;
        } else {
            serverLog(LL_VERBOSE,
                "Error writing to client: %s", strerror(errno));
            freeClient(c);
            return C_ERR;
        }
    }
    if (!clientHasPendingReplies(c)) {
        c->sentlen = 0;
        //如果內容已經全部輸出,刪除事件處理器
        if (handler_installed) aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);
        // 資料全部返回,則關閉client和連線
        if (c->flags & CLIENT_CLOSE_AFTER_REPLY) {
            freeClient(c);
            return C_ERR;
        }
    }
    return C_OK;
}

個人部落格地址,歡迎檢視

相關推薦

Redis 命令執行過程()

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

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

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

Redis 命令執行過程(上)

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

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

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

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

Linux+Qt tar 命令在GUI執行過程過程和結果資訊捕捉

#!/bin/sh ls -Ral | grep -E "^(\-|d).*$" | grep -vE ".* (\.\.|\.)$" | wc -l 需求:Linux下使用GUI(qt)執行 tar 命令。需要顯示其過程和執行結果 設計:最初思路是想通過類似pv這樣的命令

第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)如果能找到命令,

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

liunx以java命令執行java專案(匯出jar或者相關class類的方式)

                                          &

Linux在命令列模式執行命令

Linux系統登入環境 在Linux預設的登入的模式中,主要分為兩種,一種是純文字介面的登入環境,另一種則是圖形介面的登入環境。 ●Linux預設提供6個終端介面來讓使用者登入,切換的方式為:[Ctrl]+[Alt]+[F1]~[F6]。系統會按[F1]~[

npm init命令執行到version 無法執行一步

環境:win7 64位 在使用命令npm init命令時,執行到填寫version資訊後,就不能填寫資訊,也不能ctrl+c退出,也不能使用enter進行下一步。 通過node -v命令檢視我本地安裝的node版本為:8.1.0 在各方查詢資料後,網友給出的建議是版本回退或者升級。

redis解除安裝安裝和啟動命令(window

1、安裝redis服務 redis-install.bat 1 echo install redis-server 2 3 D:\redis\redis-server.exe --service-install D:\redis\redis.windows.conf --

dos執行pybot 命令執行指令碼

執行robot framework 的測試用例 命令列pybot使用方式 1、執行整個專案下的所有用例: pybot 專案路徑。例如: pybot C:\Users\lenovo\Desktop\te

linux命令執行,配合crontab使用

在crontab定時任務中,一般都是多個語句組成一個shell script,定時執行,這就存在了語句執行邏輯問題 有時候單純是按一定順序執行,有時候需要按一定邏輯執行(通過回傳值來完成) 按順序依次執行(通過`;`分隔) cmd1; cmd2; cmd3; 按邏輯與執行(&am