Redis源碼剖析和註釋(九)--- 字符串命令的實現(t_string)

Redis 字符串鍵的實現(t_string)

1. 字符串命令介紹

redis中的所有字符串命令如下:字符串類型命令詳解

序號 命令及描述
1 SET key value:設置指定 key 的值
2 GET key: 獲取指定 key 的值。
3 GETRANGE key start end: 返回 key 中字符串值的子字符
4 GETSET key value:將給定 key 的值設爲 value ,並返回 key 的舊值(old value)。
5 GETBIT key offset:對 key 所儲存的字符串值,獲取指定偏移量上的位(bit)。
6 MGET key1 [key2..]:獲取所有(一個或多個)給定 key 的值。
7 SETBIT key offset value:對 key 所儲存的字符串值,設置或清除指定偏移量上的位(bit)。
8 SETEX key seconds value:將值 value 關聯到 key ,並將 key 的過期時間設爲 seconds (以秒爲單位)。
9 SETNX key value:只有在 key 不存在時設置 key 的值。
10 SETRANGE key offset value:用 value 參數覆寫給定 key 所儲存的字符串值,從偏移量 offset 開始。
11 STRLEN key:返回 key 所儲存的字符串值的長度。
12 MSET key value [key value …]:同時設置一個或多個 key-value 對。
13 MSETNX key value [key value …]:同時設置一個或多個 key-value 對,當且僅當所有給定 key 都不存在。
14 PSETEX key milliseconds value:這個命令和 SETEX 命令相似,但它以毫秒爲單位設置 key 的生存時間,而不是像 SETEX 命令那樣,以秒爲單位。
15 INCR key:將 key 中儲存的數字值增一。
16 INCRBY key increment將 key: 所儲存的值加上給定的增量值(increment) 。
17 INCRBYFLOAT key increment:將 key 所儲存的值加上給定的浮點增量值(increment) 。
18 DECR key:將 key 中儲存的數字值減一。
19 DECRBY key decrementkey: 所儲存的值減去給定的減量值(decrement) 。
20 APPEND key value:如果 key 已經存在並且是一個字符串, APPEND 命令將 value 追加到 key 原來的值的末尾。

2. 字符串命令的實現

字符串命令底層數據結構爲 簡單動態字符串SDS對於字符串命令,無論是命令本身還是參數,都是作爲成一個對象對待的。關於redis的對象系統,請參考文章:redis對象系統源碼剖析和註釋

在redis的對象系統中,字符串對象的底層實現類型有如下三種:

編碼—encoding 對象—ptr
OBJ_ENCODING_RAW 簡單動態字符串實現的字符串對象
OBJ_ENCODING_INT 整數值實現的字符串對象
OBJ_ENCODING_EMBSTR embstr編碼的簡單動態字符串實現的字符串對象

因此,一個字符串對象的結構定義如下:

typedef struct redisObject {
    //對象的數據類型,字符串對象應該爲 OBJ_STRING
    unsigned type:4;        
    //對象的編碼類型,分別爲OBJ_STRING、OBJ_ENCODING_INT或OBJ_ENCODING_EMBSTR
    unsigned encoding:4;
    //暫且不關心該成員
    unsigned lru:LRU_BITS; /* lru time (relative to server.lruclock) */
    //引用計數
    int refcount;
    //指向底層數據實現的指針
    void *ptr;
} robj;

我們假設一個key的值爲”Hello World” ,因此它的空間結構如圖所示:

這裏寫圖片描述

3. 字符串命令源碼註釋

所有命令詳細實現可以上github下載:t_string.c註釋。這裏列出重要的命令。

3.1 SET 一類命令的最底層實現

#define OBJ_SET_NO_FLAGS 0
#define OBJ_SET_NX (1<<0)     /* Set if key not exists. */          //在key不存在的情況下才會設置
#define OBJ_SET_XX (1<<1)     /* Set if key exists. */              //在key存在的情況下才會設置
#define OBJ_SET_EX (1<<2)     /* Set if time in seconds is given */ //以秒(s)爲單位設置鍵的key過期時間
#define OBJ_SET_PX (1<<3)     /* Set if time in ms in given */      //以毫秒(ms)爲單位設置鍵的key過期時間

//setGenericCommand()函數是以下命令: SET, SETEX, PSETEX, SETNX.的最底層實現
//flags 可以是NX或XX,由上面的宏提供
//expire 定義key的過期時間,格式由unit指定
//ok_reply和abort_reply保存着回覆client的內容,NX和XX也會改變回復
//如果ok_reply爲空,則使用 "+OK"
//如果abort_reply爲空,則使用 "$-1"
void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) {
    long long milliseconds = 0; /* initialized to avoid any harmness warning */ //初始化,避免錯誤

    //如果定義了key的過期時間
    if (expire) {
        //從expire對象中取出值,保存在milliseconds中,如果出錯發送默認的信息給client
        if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) != C_OK)
            return;
        // 如果過期時間小於等於0,則發送錯誤信息給client
        if (milliseconds <= 0) {
            addReplyErrorFormat(c,"invalid expire time in %s",c->cmd->name);
            return;
        }
        //如果unit的單位是秒,則需要轉換爲毫秒保存
        if (unit == UNIT_SECONDS) milliseconds *= 1000;
    }

    //lookupKeyWrite函數是爲執行寫操作而取出key的值對象
    //如果設置了NX(不存在),並且在數據庫中 找到 該key,或者
    //設置了XX(存在),並且在數據庫中 沒有找到 該key
    //回覆abort_reply給client
    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;
    }
    //在當前db設置鍵爲key的值爲val
    setKey(c->db,key,val);

    //設置數據庫爲髒(dirty),服務器每次修改一個key後,都會對髒鍵(dirty)增1
    server.dirty++;

    //設置key的過期時間
    //mstime()返回毫秒爲單位的格林威治時間
    if (expire) setExpire(c->db,key,mstime()+milliseconds);

    //發送"set"事件的通知,用於發佈訂閱模式,通知客戶端接受發生的事件
    notifyKeyspaceEvent(NOTIFY_STRING,"set",key,c->db->id);

    //發送"expire"事件通知
    if (expire) notifyKeyspaceEvent(NOTIFY_GENERIC,
        "expire",key,c->db->id);

    //設置成功,則向客戶端發送ok_reply
    addReply(c, ok_reply ? ok_reply : shared.ok);
}

3.2 GET 一類命令的最底層實現

//GET 命令的底層實現
int getGenericCommand(client *c) {
    robj *o;

    //lookupKeyReadOrReply函數是爲執行讀操作而返回key的值對象,找到返回該對象,找不到會發送信息給client
    //如果key不存在直接,返回0表示GET命令執行成功
    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL)
        return C_OK;

    //如果key的值的編碼類型不是字符串對象
    if (o->type != OBJ_STRING) {
        addReply(c,shared.wrongtypeerr);    //返回類型錯誤的信息給client,返回-1表示GET命令執行失敗
        return C_ERR;
    } else {
        addReplyBulk(c,o);  //返回之前找到的對象作爲回覆給client,返回0表示GET命令執行成功
        return C_OK;
    }
}

3.3 DECR 和 INCR 底層實現

// DECR key
// INCR key
//INCR和DECR命令的底層實現
void incrDecrCommand(client *c, long long incr) {
    long long value, oldvalue;
    robj *o, *new;

    o = lookupKeyWrite(c->db,c->argv[1]);   //以寫操作獲取key的value對象

    //找到了value對象但是value對象不是字符串類型,直接返回
    if (o != NULL && checkType(c,o,OBJ_STRING)) return;

    //將字符串類型的value轉換爲longlong類型保存在value中
    if (getLongLongFromObjectOrReply(c,o,&value,NULL) != C_OK) return;

    oldvalue = value;   //備份舊的value

    //如果incr超出longlong類型所能表示的範圍,發送錯誤信息
    if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) ||
        (incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) {
        addReplyError(c,"increment or decrement would overflow");
        return;
    }
    value += incr;  //計算新的value值

    //value對象目前非共享,編碼爲整型類型,且新value值不在共享範圍,且value處於long類型所表示的範圍內
    if (o && o->refcount == 1 && o->encoding == OBJ_ENCODING_INT &&
        (value < 0 || value >= OBJ_SHARED_INTEGERS) &&
        value >= LONG_MIN && value <= LONG_MAX)
    {
        new = o;
        o->ptr = (void*)((long)value);  //設置vlaue對象的值
    } else {
        //當不滿足以上任意條件,則新創建一個字符串對象
        new = createStringObjectFromLongLong(value);

        //如果之前的value對象存在
        if (o) {
            dbOverwrite(c->db,c->argv[1],new);  //用new對象去重寫key的值
        } else {
            dbAdd(c->db,c->argv[1],new);        //如果之前的value不存在,將key和new組成新的key-value對
        }
    }
    signalModifiedKey(c->db,c->argv[1]);    //當數據庫的鍵被改動,則會調用該函數發送信號
    //發送"incrby"事件通知
    notifyKeyspaceEvent(NOTIFY_STRING,"incrby",c->argv[1],c->db->id);
    //設置髒鍵
    server.dirty++;

    //回覆信息給client
    addReply(c,shared.colon);
    addReply(c,new);
    addReply(c,shared.crlf);
}

3.4 APPEND 實現

// APPEND key value
// APPEND命令的實現
void appendCommand(client *c) {
    size_t totlen;
    robj *o, *append;

    o = lookupKeyWrite(c->db,c->argv[1]);   //以寫操作獲取key的value對象

    //如果沒有獲取到vlaue,則要創建一個
    if (o == NULL) {
        /* Create the key */
        c->argv[2] = tryObjectEncoding(c->argv[2]); //對參數value進行優化編碼
        dbAdd(c->db,c->argv[1],c->argv[2]); //將key和value組成新的key-value對
        incrRefCount(c->argv[2]);           //增加value的引用計數
        totlen = stringObjectLen(c->argv[2]);   //返回vlaue的長度
    } else {    //獲取到value
        /* Key exists, check type */
        if (checkType(c,o,OBJ_STRING))  //如果value不是字符串類型的對象直接返回
            return;

        /* "append" is an argument, so always an sds */
        //獲得追加的值對象
        append = c->argv[2];
        //計算追加後的長度
        totlen = stringObjectLen(o)+sdslen(append->ptr);
        //如果追加後的長度超出範圍,則返回
        if (checkStringLength(c,totlen) != C_OK)
            return;

        /* Append the value */
        //因爲要根據value修改key的值,因此如果key原來的值是共享的,需要解除共享,新創建一個值對象與key組對
        o = dbUnshareStringValue(c->db,c->argv[1],o);
        //將vlaue對象的值後面追加上append的值
        o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr));
        //計算出追加後值的長度
        totlen = sdslen(o->ptr);
    }
    signalModifiedKey(c->db,c->argv[1]);//當數據庫的鍵被改動,則會調用該函數發送信號
    //發送"append"事件通知
    notifyKeyspaceEvent(NOTIFY_STRING,"append",c->argv[1],c->db->id);
    //設置髒鍵
    server.dirty++;
    //發送追加後value的長度給client
    addReplyLongLong(c,totlen);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章