《redis设计与实现》-8 hash对象

一 序

   昨天偷懒没有看代码,看了部电影《伸冤人2》,与中国的大侠不同,老外也有雷锋啊。明明是退役特工,却在生活中收敛锋芒,变成了滴滴快车的师傅,打戏占比不是全程从头打到尾的,却也干净利落。大段的镜头描写平静的美式中年生活。有一种大隐于市的感觉,喜欢主人公那种读书的感觉。

   好吧,扯完闲篇开始 今天的读书生活。关于hash对象书上只是简单列了下编码格式及转换。后面整理命令的实现需要结合代码来看。

二 t_hash

   2.1 hash对象的编码

在之前的object对象的编码提到,一个哈希类型的对象的编码有两种,分别是OBJ_ENCODING_ZIPLIST和OBJ_ENCODING_HT。参见字典ziplist:  源码obect.c可以看到:

robj *createHashObject(void) {
    unsigned char *zl = ziplistNew();
    robj *o = createObject(OBJ_HASH, zl);
    o->encoding = OBJ_ENCODING_ZIPLIST;
    return o;
}
void freeHashObject(robj *o) {
    switch (o->encoding) {
    case OBJ_ENCODING_HT:
        dictRelease((dict*) o->ptr);
        break;
    case OBJ_ENCODING_ZIPLIST:
        zfree(o->ptr);
        break;
    default:
        serverPanic("Unknown hash encoding type");
        break;
    }
}
编码—encoding对象—ptr
OBJ_ENCODING_ZIPLIST压缩列表实现的哈希对象
OBJ_ENCODING_HT字典实现的哈希对象

为啥创建的哈希类型的对象编码为OBJ_ENCODING_ZIPLIST,而释放的时候多了OBJ_ENCODING_HT?这是因为OBJ_ENCODING_HT类型编码是通过达到配置的阈值条件后,进行转换得到的。

阈值条件为:

/* redis.conf文件中的阈值 */
hash-max-ziplist-value 64 // ziplist中最大能存放的值长度
hash-max-ziplist-entries 512 // ziplist中最多能存放的entry节点数量
书上这里分别举例。执行以下 HSET 命令, 那么服务器将创建一个列表对象作为 profile 键的值:
redis> HSET profile name "Tom"
(integer) 1

redis> HSET profile age 25
(integer) 1

redis> HSET profile career "Programmer"
(integer) 1

如果 profile 键的值对象使用的是 ziplist 编码, 那么这个值对象将会是图 8-9 所示的样子, 其中对象所使用的压缩列表如图 8-10 所示。

另一方面, hashtable 编码的哈希对象使用字典作为底层实现, 哈希对象中的每个键值对都使用一个字典键值对来保存:

  • 字典的每个键都是一个字符串对象, 对象中保存了键值对的键;
  • 字典的每个值都是一个字符串对象, 对象中保存了键值对的值。

举个例子, 如果前面 profile 键创建的不是 ziplist 编码的哈希对象, 而是 hashtable 编码的哈希对象, 那么这个哈希对象应该会是图 8-11 所示的样子。

和列表数据类型一样,哈希数据类型基于ziplist和hash table进行封装,实现了哈希数据类型的接口,源码在server.h


/* Hash data type */
void hashTypeConvert(robj *o, int enc);// 转换一个哈希对象的编码类型,enc指定新的编码类型
void hashTypeTryConversion(robj *subject, robj **argv, int start, int end);// 转换一个哈希对象的编码类型,enc指定新的编码类型
void hashTypeTryObjectEncoding(robj *subject, robj **o1, robj **o2);// 对键和值的对象尝试进行优化编码以节约内存
robj *hashTypeGetObject(robj *o, robj *key);// 从一个哈希对象中返回field对应的值对象
int hashTypeExists(robj *o, robj *key);// 判断field对象是否存在在o对象中
int hashTypeSet(robj *o, robj *key, robj *value);//将field-value添加到哈希对象中,返回1,如果field存在更新新的值,返回0
int hashTypeDelete(robj *o, robj *key);// 从一个哈希对象中删除field,成功返回1,没找到field返回0
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);
//从字典类型的迭代器中获取对应的field 或者value                                
void hashTypeCurrentFromHashTable(hashTypeIterator *hi, int what, robj **dst);
// 从哈希类型的迭代器中获取键或值
robj *hashTypeCurrentObject(hashTypeIterator *hi, int what);
// 以写操作在数据库中查找对应key的哈希对象,如果不存在则创建
robj *hashTypeLookupWriteOrCreate(client *c, robj *key);

2.2 hash的迭代器

和列表类型一样,哈希数据类型也实现自己的迭代器,而且也是基于ziplist和字典结构的迭代器封装而成。源码在server.h

/* Structure to hold hash iteration abstraction. Note that iteration over
 * hashes involves both fields and values. Because it is possible that
 * not both are required, store pointers in the iterator to avoid
 * unnecessary memory allocation for fields/values. */
typedef struct {
    robj *subject;// 被迭代的哈希对象
    int encoding; // 哈希对象的编码
	  // 域指针和值指针
    // 在迭代 ZIPLIST 编码的哈希对象时使用
    unsigned char *fptr, *vptr;
    //用于字典编码
    dictIterator *di; // 迭代HT类型的哈希对象时的字典迭代器
    dictEntry *de;    // 指向当前的哈希表节点
} hashTypeIterator;
#define OBJ_HASH_KEY 1
#define OBJ_HASH_VALUE 2

创建一个迭代器,源码在t_hash.c

/* 返回一个初始化的哈希类型的迭代器*/
hashTypeIterator *hashTypeInitIterator(robj *subject) {
    hashTypeIterator *hi = zmalloc(sizeof(hashTypeIterator));// 分配空间初始化成员
    hi->subject = subject; // 指向对象
    hi->encoding = subject->encoding;    // 记录编码
    // 以 ziplist 的方式初始化迭代器
    if (hi->encoding == OBJ_ENCODING_ZIPLIST) {
        hi->fptr = NULL;
        hi->vptr = NULL;
          // 以字典的方式初始化迭代器
    } else if (hi->encoding == OBJ_ENCODING_HT) {
        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;

        if (fptr == NULL) { // 第一次执行时,初始化指针,field的指针为空,则指向第一个entry
            /* Initialize cursor */
            serverAssert(vptr == NULL);
            fptr = ziplistIndex(zl, 0);
        } else {
            /* Advance cursor */// 获取下一个迭代节点,也就是下一个filed地址
            serverAssert(vptr != NULL);
            fptr = ziplistNext(zl, vptr);
        }
        if (fptr == NULL) return C_ERR;  // 迭代完毕或者 ziplist 为空 返回C_ERR

        /* Grab pointer to the value (fptr points to the field) */
        vptr = ziplistNext(zl, fptr); // // 记录值的指针,就是下一个value的地址
        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;
}

看代码主要是ziplist的迭代多一些,字典的直接调用dictnext,所以看下ziplist的查找过程,源码在ziplist.c

/* Returns an offset to use for iterating with ziplistNext. When the given
 * index is negative, the list is traversed back to front. When the list
 * doesn't contain an element at the provided index, NULL is returned. */
unsigned char *ziplistIndex(unsigned char *zl, int index) {
    unsigned char *p;
    unsigned int prevlensize, prevlen = 0;
    if (index < 0) {   // 处理负数索引,从表尾开始
        index = (-index)-1;  // 将索引转换为正数
        p = ZIPLIST_ENTRY_TAIL(zl);// 定位到表尾节点
        if (p[0] != ZIP_END) {//列表不为空
            ZIP_DECODE_PREVLEN(p, prevlensize, prevlen);//获取前一个节点的长度
            while (prevlen > 0 && index--) {
                p -= prevlen; // 前移指针
                ZIP_DECODE_PREVLEN(p, prevlensize, prevlen);
            }
        }
    } else {// 处理正数索引,从表头开始
        p = ZIPLIST_ENTRY_HEAD(zl);
        while (p[0] != ZIP_END && index--) {
            p += zipRawEntryLength(p); // 后移指针
        }
    }
    return (p[0] == ZIP_END || index > 0) ? NULL : p;
}


/* Return pointer to next entry in ziplist.
 *
 * zl is the pointer to the ziplist
 * p is the pointer to the current element
 *
 * The element after 'p' is returned, otherwise NULL if we are at the end. */
unsigned char *ziplistNext(unsigned char *zl, unsigned char *p) {
    ((void) zl);

    /* "p" could be equal to ZIP_END, caused by ziplistDelete,
     * and we should return NULL. Otherwise, we should return NULL
     * when the *next* element is ZIP_END (there is no next entry). */
    if (p[0] == ZIP_END) {  // p 已经指向列表末端
        return NULL;
    }

    p += zipRawEntryLength(p); // 指向后一节点
    if (p[0] == ZIP_END) {//看到这是第二次判断为空了,
        return NULL;
    }

    return p;
}

为啥ziplist的ziplistNext 里面会有两次判断为空,为啥呢?我觉得是为了遍历时被删除的情况。

三 hash对象命令实现


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]: 迭代哈希表中的键值对。

3.1 HKEYS、HVALS、HGETALL

void hkeysCommand(client *c) {
    genericHgetallCommand(c,OBJ_HASH_KEY);
}

void hvalsCommand(client *c) {
    genericHgetallCommand(c,OBJ_HASH_VALUE);
}

void hgetallCommand(client *c) {
    genericHgetallCommand(c,OBJ_HASH_KEY|OBJ_HASH_VALUE);
}

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;
     // 计算一对键值对要返回的个数
     /*这里运用了位运算,第一次没看明白,要结合入参来看,OBJ_HASH_KEY=01,OBJ_HASH_VALUE=2(10),
      * 全部查询hgetallCommand传入了或就是3(11)。
     */
    if (flags & OBJ_HASH_KEY) multiplier++;//key
    if (flags & OBJ_HASH_VALUE) multiplier++;//value

    length = hashTypeLength(o) * multiplier; // 计算整个哈希对象中的所有键值对要返回的个数,注意是指针不是乘法。
    addReplyMultiBulkLen(c, length); //发get到的个数给client
		
		 // 迭代节点,并取出元素
    hi = hashTypeInitIterator(o); // 创建一个哈希类型的迭代器并初始化
    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);
}

这里我看了好一会才明白,因为自己第一次犯晕,把指针看成乘法,围绕着length没看懂。网上搜一下很多都是copy了 men_wen 的文章,可他不能每行都注释啊。回过头来在看入参的OBJ_HASH_KEY,OBJ_HASH_VALUE才明白。同样的额一件事,可能大神扫眼就知道了,菜鸟需要反复的看才明白。

HSTRLEN 命令实现


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,为啥从第2个开始,命令格式:hdel key filed1,filed2....
    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]);  // 发送键修改信号
        notifyKeyspaceEvent(NOTIFY_HASH,"hdel",c->argv[1],c->db->id); // 发送事件通知
        if (keyremoved) // 如果哈希对象被删除,发送del通知
            notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1],
                                c->db->id);
        server.dirty += deleted;  // 更新脏键
    }
    addReplyLongLong(c,deleted); //发送删除的个数给client
}

/* Delete an element from a hash.
 * Return 1 on deleted and 0 on not found. */
int hashTypeDelete(robj *o, robj *field) {
    int deleted = 0;

    if (o->encoding == OBJ_ENCODING_ZIPLIST) { // 从 ziplist 中删除
        unsigned char *zl, *fptr;

        field = getDecodedObject(field);//获取真正的filed

        zl = o->ptr; //z1指向实际数据
        fptr = ziplistIndex(zl, ZIPLIST_HEAD);
        if (fptr != NULL) {// 定位到域
            fptr = ziplistFind(fptr, field->ptr, sdslen(field->ptr), 1);
            if (fptr != NULL) { // 删除域和值
                zl = ziplistDelete(zl,&fptr);
                zl = ziplistDelete(zl,&fptr);
                o->ptr = zl;
                deleted = 1;  //找到并且删除成功
            }
        }

        decrRefCount(field);

    } else if (o->encoding == OBJ_ENCODING_HT) { // 从字典中删除
        if (dictDelete((dict*)o->ptr, field) == C_OK) {
            deleted = 1;

            /* Always check if the dictionary needs a resize after a delete. */
            if (htNeedsResize(o->ptr)) dictResize(o->ptr); // 删除成功时,看字典是否需要收缩
        }

    } else {
        serverPanic("Unknown hash encoding");
    }

    return deleted;
}

hincrbyfloatCommand 

void hincrbyfloatCommand(client *c) {
    double long value, incr;
    robj *o, *current, *new, *aux;
         // 取出 incr 参数(一个long double类型的增量increment)
    if (getLongDoubleFromObjectOrReply(c,c->argv[3],&incr,NULL) != C_OK) return;
    if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;  // 以写方式取出哈希对象,失败则直接返回
    if ((current = hashTypeGetObject(o,c->argv[2])) != NULL) { // 取出值对象
        // 从值对象中取出浮点值
        if (getLongDoubleFromObjectOrReply(c,current,&value,
            "hash value is not a valid float") != C_OK) {
            decrRefCount(current);  //取值不成功,释放临时的value对象空间,直接返回
            return;
        }
        decrRefCount(current);//取值成功,也要释放空间
    } else {// 值对象不存在,默认值为 0
        value = 0;
    }
    //下面是计算结果
    value += incr;//备份原先的值
     // 为计算结果创建值对象
    new = createStringObjectFromLongDouble(value,1);
    hashTypeTryObjectEncoding(o,&c->argv[2],NULL); // 编码值对象(以节省空间,是以embstr或raw或整型存储)
    hashTypeSet(o,c->argv[2],new); // 关联键和新的值对象,如果已经有对象存在,那么用新对象替换它
    addReplyBulk(c,new);// 返回新的值对象作为回复
    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. */
    // 在传播 INCRBYFLOAT 命令时,总是用 SET 命令来替换 INCRBYFLOAT 命令
    // 从而防止因为不同的浮点精度和格式化造成 AOF 重启时的数据不一致
    aux = createStringObject("HSET",4); // 创建HSET字符串对象
    rewriteClientCommandArgument(c,0,aux);// 修改HINCRBYFLOAT命令为HSET对象
    decrRefCount(aux); // 释放空间
    rewriteClientCommandArgument(c,3,new);  // 修改increment为新的值对象new
    decrRefCount(new);  //  释放空间
}

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

这个命令是数据库的命令,比较复杂。待单独整理。

good night.

****************补充一下hset*******************

void hsetCommand(client *c) {
    int update;
    robj *o;
		 // 取出或新创建哈希对象
    if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
    hashTypeTryConversion(o,c->argv,2,3);// 如果需要的话,转换哈希对象的编码
    hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]);// 编码 field 和 value 对象以节约空间
    update = hashTypeSet(o,c->argv[2],c->argv[3]);// 设置 field 和 value 到 hash
    addReply(c, update ? shared.czero : shared.cone);// 返回状态:显示 field-value 对是新添加还是更新
    signalModifiedKey(c->db,c->argv[1]); // 发送键修改信号
    notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id);// 发送事件通知hset
    server.dirty++;//将服务器设为脏
}

这里主要实现的方法是hashTypeSet.进入之前先进性编码的判断。就是一开始说的set两种编码转换限制之一。

/* Check the length of a number of objects to see if we need to convert a
 * ziplist to a real hash. Note that we only check string encoded objects
 * as their string length can be queried in constant time. */
void hashTypeTryConversion(robj *o, robj **argv, int start, int end) {
    int i;

    if (o->encoding != OBJ_ENCODING_ZIPLIST) return;//已经是字典了,就不用再转了,直接返回

    for (i = start; i <= end; i++) { // 检查所有输入对象,看它们的字符串值是否超过了指定长度
        if (sdsEncodedObject(argv[i]) &&
            sdslen(argv[i]->ptr) > server.hash_max_ziplist_value)//判断是否超过长度阈值
        {
            hashTypeConvert(o, OBJ_ENCODING_HT);// 将对象的编码转换成 REDIS_ENCODING_HT
            break;
        }
    }
}

/* Encode given objects in-place when the hash uses a dict. 
 * 当 subject 的编码为 REDIS_ENCODING_HT 时,
 * 尝试对对象 o1 和 o2 进行编码,
 * 以节省更多内存。
*/
void hashTypeTryObjectEncoding(robj *subject, robj **o1, robj **o2) {
    if (subject->encoding == OBJ_ENCODING_HT) {
        if (o1) *o1 = tryObjectEncoding(*o1);
        if (o2) *o2 = tryObjectEncoding(*o2);
    }
}

再看看hashTypeSet

/* Add an element, discard the old if the key already exists.
 * Return 0 on insert and 1 on update.
  * 将给定的 field-value 对添加到 hash 中,
 * 如果 field 已经存在,那么删除旧的值,并关联新值。
 *
 * This function will take care of incrementing the reference count of the
 * retained fields and value objects. */
int hashTypeSet(robj *o, robj *field, robj *value) {
    int update = 0;

    if (o->encoding == OBJ_ENCODING_ZIPLIST) {// 添加到 ziplist
        unsigned char *zl, *fptr, *vptr;
	      // 解码成字符串或者数字
        field = getDecodedObject(field);
        value = getDecodedObject(value);
	      // 遍历整个 ziplist ,尝试查找并更新 field (如果它已经存在的话)
        zl = o->ptr;
        fptr = ziplistIndex(zl, ZIPLIST_HEAD);
        if (fptr != NULL) {// 定位到域 field
            fptr = ziplistFind(fptr, field->ptr, sdslen(field->ptr), 1);
            if (fptr != NULL) {
                /* Grab pointer to the value (fptr points to the field) */
                vptr = ziplistNext(zl, fptr);// 定位到域的值
                serverAssert(vptr != NULL);
                update = 1;

                /* Delete value */
                zl = ziplistDelete(zl, &vptr); // 删除旧的键值对

                /* Insert new value */ // 添加新的键值对
                zl = ziplistInsert(zl, vptr, value->ptr, sdslen(value->ptr));
            }
        }

        if (!update) {// 如果这不是更新操作,那么这就是一个添加操作
            /* Push new field/value pair onto the tail of the ziplist */
              // 将新的 field-value 对推入到 ziplist 的末尾
            zl = ziplistPush(zl, field->ptr, sdslen(field->ptr), ZIPLIST_TAIL);
            zl = ziplistPush(zl, value->ptr, sdslen(value->ptr), ZIPLIST_TAIL);
        }
        o->ptr = zl; // 更新对象指针
        decrRefCount(field);  // 释放临时对象
        decrRefCount(value);

        /* Check if the ziplist needs to be converted to a hash table */
          // 检查在添加操作完成之后,是否需要将 ZIPLIST 编码转换成 HT 编码
        if (hashTypeLength(o) > server.hash_max_ziplist_entries)
            hashTypeConvert(o, OBJ_ENCODING_HT);
    } else if (o->encoding == OBJ_ENCODING_HT) { // 添加到字典
    	  // 添加或替换键值对到字典
        // 添加返回 1 ,替换返回 0
        if (dictReplace(o->ptr, field, value)) { /* Insert */
            incrRefCount(field);
        } else { /* Update */
            update = 1;
        }
        incrRefCount(value);
    } else {
        serverPanic("Unknown hash encoding");
    }
    return update;// 更新/添加指示变量
}

这里有牵扯到server.hash_max_ziplist_entries,插入之后判断是否需要转变为OBJ_ENCODING_HT。

 

参考:

https://blog.csdn.net/men_wen/article/details/70850618

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值