Redis源碼剖析和注釋(十一)--- 雜湊鍵命令的實現(t_hash)

來源:互聯網
上載者:User
Redis 雜湊鍵命令實現(t_hash) 1. 雜湊命令介紹

Redis 所有雜湊命令如下表所示:Redis 雜湊命令詳解

序號 命令及描述
1 HDEL key field2 [field2]:刪除一個或多個雜湊表欄位
2 HEXISTS key field:查看雜湊表 key 中,指定的欄位是否存在。
3 HGET key field:擷取儲存在雜湊表中指定欄位的值。
4 HGETALL key:擷取在雜湊表中指定 key 的所有欄位和值
5 HINCRBY key field increment:為雜湊表 key 中的指定欄位的整數值加上增量 increment 。
6 HINCRBYFLOAT key field increment:為雜湊表 key 中的指定欄位的浮點數值加上增量 increment 。
7 HKEYS key:擷取所有雜湊表中的欄位
8 HLEN key:擷取雜湊表中欄位的數量
9 HMGET key field1 [field2]:擷取所有給定欄位的值
10 HMSET key field1 value1 [field2 value2 ]:同時將多個 field-value (域-值)對設定到雜湊表 key 中。
11 HSET key field value:將雜湊表 key 中的欄位 field 的值設為 value 。
12 HSETNX key field value:只有在欄位 field 不存在時,設定雜湊表欄位的值。
13 HVALS key:擷取雜湊表中所有值
14 HSCAN key cursor [MATCH pattern][COUNT count]: 迭代雜湊表中的索引值對。
2. 雜湊類型的實現

之前在redis對象系統源碼剖析和注釋中提到,一個雜湊類型的對象的編碼有兩種,分別是OBJ_ENCODING_ZIPLIST和OBJ_ENCODING_HT。

redis 壓縮列表源碼剖析和注釋

redis 字典結構源碼剖析和注釋

編碼—encoding 對象—ptr
OBJ_ENCODING_ZIPLIST 壓縮列表實現的雜湊對象
OBJ_ENCODING_HT 字典實現的雜湊對象

但是預設建立的雜湊類型的對象編碼為OBJ_ENCODING_ZIPLIST,OBJ_ENCODING_HT類型編碼是通過達到配置的閾值條件後,進行轉換得到的。

閾值條件為:

/* redis.conf檔案中的閾值 */hash-max-ziplist-value 64 // ziplist中最大能存放的值長度hash-max-ziplist-entries 512 // ziplist中最多能存放的entry節點數量

一個雜湊對象的結構定義如下:

typedef struct redisObject {    //對象的資料類型,字串對象應該為 OBJ_HASH    unsigned type:4;            //對象的編碼類別型,分別為 OBJ_ENCODING_ZIPLIST 或 OBJ_ENCODING_HT    unsigned encoding:4;    //暫且不關心該成員    unsigned lru:LRU_BITS; /* lru time (relative to server.lruclock) */    //引用計數    int refcount;    //指向底層資料實現的指標,指向一個dict的字典結構    void *ptr;} robj;

例如,我們建立一個 user:info 雜湊鍵,有三個欄位,分別是name,sex,passwd。

127.0.0.1:6379> HMSET user:info name Mike sex male passwd 123456OK127.0.0.1:6379> HGETALL user:info1) "name"2) "Mike"3) "sex"4) "male"5) "passwd"6) "123456"

我們以此為例,查看redis的雜湊對象的空間結構。

根據這些資訊的大小,redis應該為其建立一個編碼為OBJ_ENCODING_ZIPLIST的雜湊對象。如下圖所示:

壓縮列表中的entry節點,兩兩組成一個索引值對。

如果這個雜湊對象所儲存的索引值對或者ziplist的長度超過配置的限制,則會轉換為字典結構,這寫閾值條件上面已經列出,而為了說明編碼為 OBJ_ENCODING_HT 類型的雜湊對象,我們仍用上面的 user:info 對象來表示一個字典結構的雜湊對象,雜湊對象中的索引值對都是字串類型的對象。如下圖:

和列表資料類型一樣,雜湊資料類型基於ziplist和hash table進行封裝,實現了雜湊資料類型的介面:

/* Hash data type */// 轉換一個雜湊對象的編碼類別型,enc指定新的編碼類別型void hashTypeConvert(robj *o, int enc);// 檢查一個數字對象的長度判斷是否需要進行類型的轉換,從ziplist轉換到ht類型void hashTypeTryConversion(robj *subject, robj **argv, int start, int end);// 對鍵和值的對象嘗試進行最佳化編碼以節約記憶體void hashTypeTryObjectEncoding(robj *subject, robj **o1, robj **o2);// 從一個雜湊對象中返回field對應的值對象robj *hashTypeGetObject(robj *o, robj *key);// 判斷field對象是否存在在o對象中int hashTypeExists(robj *o, robj *key);//  將field-value添加到雜湊對象中,返回1,如果field存在更新新的值,返回0int hashTypeSet(robj *o, robj *key, robj *value);// 從一個雜湊對象中刪除field,成功返回1,沒找到field返回0int hashTypeDelete(robj *o, robj *key);// 返回雜湊對象中的索引值對個數unsigned long hashTypeLength(robj *o);// 返回一個初始化的雜湊類型的迭代器hashTypeIterator *hashTypeInitIterator(robj *subject);// 釋放雜湊類型迭代器空間void hashTypeReleaseIterator(hashTypeIterator *hi);// 講雜湊類型迭代器指向雜湊對象中的下一個節點int hashTypeNext(hashTypeIterator *hi);// 從ziplist類型的雜湊類型迭代器中擷取對應的field或value,儲存在參數中void hashTypeCurrentFromZiplist(hashTypeIterator *hi, int what, unsigned char **vstr, unsigned int *vlen, long long *vll);// 從ziplist類型的雜湊類型迭代器中擷取對應的field或value,儲存在參數中void hashTypeCurrentFromHashTable(hashTypeIterator *hi, int what, robj **dst);// 從雜湊類型的迭代器中擷取鍵或值robj *hashTypeCurrentObject(hashTypeIterator *hi, int what);// 以寫操作在資料庫中尋找對應key的雜湊對象,如果不存在則建立robj *hashTypeLookupWriteOrCreate(client *c, robj *key);

這些函數介面的注釋請上github查看:雜湊類型函數介面的注釋 3. 雜湊類型的迭代器

和清單類型一樣,雜湊資料類型也實現自己的迭代器,而且也是基於ziplist和字典結構的迭代器封裝而成。

typedef struct {    robj *subject;              // 雜湊類型迭代器所屬的雜湊對象    int encoding;               // 雜湊對象的編碼類別型    // 用ziplist編碼    unsigned char *fptr, *vptr; // 指向當前的key和value節點的地址,ziplist類型編碼時使用    // 用於字典編碼    dictIterator *di;           // 迭代HT類型的雜湊對象時的字典迭代器    dictEntry *de;              // 指向當前的雜湊表節點} hashTypeIterator;#define OBJ_HASH_KEY 1          // 雜湊鍵#define OBJ_HASH_VALUE 2        // 雜湊值
建立一個迭代器
// 返回一個初始化的雜湊類型的迭代器hashTypeIterator *hashTypeInitIterator(robj *subject) {    // 分配空間初始化成員    hashTypeIterator *hi = zmalloc(sizeof(hashTypeIterator));    hi->subject = subject;    hi->encoding = subject->encoding;    // 根據不同的編碼設定不同的成員    if (hi->encoding == OBJ_ENCODING_ZIPLIST) {        hi->fptr = NULL;        hi->vptr = NULL;    } else if (hi->encoding == OBJ_ENCODING_HT) {        // 初始化一個字典迭代器返回給di成員        hi->di = dictGetIterator(subject->ptr);    } else {        serverPanic("Unknown hash encoding");    }    return hi;}
釋放迭代器
// 釋放雜湊類型迭代器空間void hashTypeReleaseIterator(hashTypeIterator *hi) {    // 如果是字典,則需要先釋放字典迭代器的空間    if (hi->encoding == OBJ_ENCODING_HT) {        dictReleaseIterator(hi->di);    }    zfree(hi);}
迭代
/* Move to the next entry in the hash. Return C_OK when the next entry * could be found and C_ERR when the iterator reaches the end. *///講雜湊類型迭代器指向雜湊對象中的下一個節點int hashTypeNext(hashTypeIterator *hi) {    // 迭代ziplist    if (hi->encoding == OBJ_ENCODING_ZIPLIST) {        unsigned char *zl;        unsigned char *fptr, *vptr;        // 備份迭代器的成員資訊        zl = hi->subject->ptr;        fptr = hi->fptr;        vptr = hi->vptr;        // field的指標為空白,則指向第一個entry,只在第一次執行時,初始化指標        if (fptr == NULL) {            /* Initialize cursor */            serverAssert(vptr == NULL);            fptr = ziplistIndex(zl, 0);        } else {            /* Advance cursor */            // 擷取value節點的下一個entry地址,即為下一個field的地址            serverAssert(vptr != NULL);            fptr = ziplistNext(zl, vptr);        }        // 迭代完畢或返回C_ERR        if (fptr == NULL) return C_ERR;        /* Grab pointer to the value (fptr points to the field) */        // 儲存下一個value的地址        vptr = ziplistNext(zl, fptr);        serverAssert(vptr != NULL);        /* fptr, vptr now point to the first or next pair */        // 更新迭代器的成員資訊        hi->fptr = fptr;        hi->vptr = vptr;    // 如果是迭代字典    } else if (hi->encoding == OBJ_ENCODING_HT) {        // 得到下一個字典節點的地址        if ((hi->de = dictNext(hi->di)) == NULL) return C_ERR;    } else {        serverPanic("Unknown hash encoding");    }    return C_OK;}
4. 雜湊命令的實現

上面都給出了雜湊類型的介面,所以雜湊類型命令實現很容易看懂,而且雜湊類型命令沒有阻塞版的。

具體所有注釋請看:雜湊類型命令的注釋 Hgetall一類命令的底層實現

HKEYS、HVALS、HGETALL

void genericHgetallCommand(client *c, int flags) {    robj *o;    hashTypeIterator *hi;    int multiplier = 0;    int length, count = 0;    // 以寫操作取出雜湊對象,若失敗,或取出的對象不是雜湊類型的對象,則發送0後直接返回    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL        || checkType(c,o,OBJ_HASH)) return;    // 計算一對索引值對要返回的個數    if (flags & OBJ_HASH_KEY) multiplier++;    if (flags & OBJ_HASH_VALUE) multiplier++;    // 計算整個雜湊對象中的所有索引值對要返回的個數    length = hashTypeLength(o) * multiplier;    addReplyMultiBulkLen(c, length);        //發get到的個數給client    // 建立一個雜湊類型的迭代器並初始化    hi = hashTypeInitIterator(o);    // 迭代所有的entry節點    while (hashTypeNext(hi) != C_ERR) {        // 如果取雜湊鍵        if (flags & OBJ_HASH_KEY) {            // 儲存當前迭代器指向的鍵            addHashIteratorCursorToReply(c, hi, OBJ_HASH_KEY);            count++;    //更新計數器        }        // 如果取雜湊值        if (flags & OBJ_HASH_VALUE) {            // 儲存當前迭代器指向的值            addHashIteratorCursorToReply(c, hi, OBJ_HASH_VALUE);            count++;    //更新計數器        }    }    //釋放迭代器    hashTypeReleaseIterator(hi);    serverAssert(count == length);}
HSTRLEN 命令實現

Redis 3.2版本以上新加入的

void hstrlenCommand(client *c) {    robj *o;    // 以寫操作取出雜湊對象,若失敗,或取出的對象不是雜湊類型的對象,則發送0後直接返回    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||        checkType(c,o,OBJ_HASH)) return;    // 發送field對象的值的長度給client    addReplyLongLong(c,hashTypeGetValueLength(o,c->argv[2]));}
HDEL命令實現
void hdelCommand(client *c) {    robj *o;    int j, deleted = 0, keyremoved = 0;    // 以寫操作取出雜湊對象,若失敗,或取出的對象不是雜湊類型的對象,則發送0後直接返回    if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||        checkType(c,o,OBJ_HASH)) return;    // 遍曆所有的欄位field    for (j = 2; j < c->argc; j++) {        // 從雜湊對象中刪除當前欄位        if (hashTypeDelete(o,c->argv[j])) {            deleted++;  //更新刪除的個數            // 如果雜湊對象為空白,則刪除該對象            if (hashTypeLength(o) == 0) {                dbDelete(c->db,c->argv[1]);                keyremoved = 1; //設定刪除標誌                break;            }        }    }    // 只要刪除了欄位    if (deleted) {        // 發送訊號表示鍵被改變        signalModifiedKey(c->db,c->argv[1]);        // 發送"hdel"事件通知        notifyKeyspaceEvent(NOTIFY_HASH,"hdel",c->argv[1],c->db->id);        // 如果雜湊對象被刪除        if (keyremoved)            // 發送"hdel"事件通知            notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1],                                c->db->id);        server.dirty += deleted;    // 更新髒鍵    }    addReplyLongLong(c,deleted);    //發送刪除的個數給client}
HINCRBYFLOAT 命令的實現
void hincrbyfloatCommand(client *c) {    double long value, incr;    robj *o, *current, *new, *aux;    // 得到一個long double類型的增量increment    if (getLongDoubleFromObjectOrReply(c,c->argv[3],&incr,NULL) != C_OK) return;    // 以寫方式取出雜湊對象,失敗則直接返回    if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;    // 返回field在雜湊對象o中的值對象    if ((current = hashTypeGetObject(o,c->argv[2])) != NULL) {        //從值對象中得到一個long double類型的value,如果不是浮點數的值,則發送"hash value is not a valid float"資訊給client        if (getLongDoubleFromObjectOrReply(c,current,&value,            "hash value is not a valid float") != C_OK) {            decrRefCount(current);  //取值成功,釋放臨時的value對象空間,直接返回            return;        }        decrRefCount(current);  //取值失敗也要釋放空間    } else {        value = 0;  //如果沒有值,則設定為預設的0    }    value += incr;  //備份原先的值    // 將value轉換為字串類型的對象    new = createStringObjectFromLongDouble(value,1);    //將鍵和值對象的編碼進行最佳化,以節省空間的,是以embstr或raw或整型儲存    hashTypeTryObjectEncoding(o,&c->argv[2],NULL);    // 設定原來的key為新的值對象    hashTypeSet(o,c->argv[2],new);    // 講新的值對象發送給client    addReplyBulk(c,new);     // 修改資料庫的鍵則發送訊號,發送"hincrbyfloat"事件通知,更新髒鍵    signalModifiedKey(c->db,c->argv[1]);    notifyKeyspaceEvent(NOTIFY_HASH,"hincrbyfloat",c->argv[1],c->db->id);    server.dirty++;    /* Always replicate HINCRBYFLOAT as an HSET command with the final value     * in order to make sure that differences in float pricision or formatting     * will not create differences in replicas or after an AOF restart. */    // 用HSET命令代替HINCRBYFLOAT,以防不同的浮點精度造成的誤差    // 建立HSET字串對象    aux = createStringObject("HSET",4);    // 修改HINCRBYFLOAT命令為HSET對象    rewriteClientCommandArgument(c,0,aux);    // 釋放空間    decrRefCount(aux);    // 修改increment為新的值對象new    rewriteClientCommandArgument(c,3,new);    // 釋放空間    decrRefCount(new);}
HSCAN 命令實現
// HSCAN key cursor [MATCH pattern] [COUNT count]// HSCAN 命令實現void hscanCommand(client *c) {    robj *o;    unsigned long cursor;    // 擷取scan命令的遊標cursor    if (parseScanCursorOrReply(c,c->argv[2],&cursor) == C_ERR) return;    // 以寫操作取出雜湊對象,若失敗,或取出的對象不是雜湊類型的對象,則發送0後直接返回    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptyscan)) == NULL ||        checkType(c,o,OBJ_HASH)) return;    // 調用底層實現    scanGenericCommand(c,o,cursor);}

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.