redis6.0.5之t_hash阅读笔记--哈希
********************************************************************************************************************* /* 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. */ 检查对象的数量,看看是否需要转化zplist为真正的哈希键值对。注意我们这里值检查字符串编码的对象, 因为字符串长度可以在常量时间内查询 void hashTypeTryConversion(robj *o, robj **argv, int start, int end) { int i; if (o->encoding != OBJ_ENCODING_ZIPLIST) return; 非ziplist编码,直接返回 -- #define sdsEncodedObject(objptr) (objptr->encoding == OBJ_ENCODING_RAW || objptr->encoding == OBJ_ENCODING_EMBSTR) -- createSizeTConfig("hash-max-ziplist-value", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.hash_max_ziplist_value, 64, MEMORY_CONFIG, NULL, NULL), for (i = start; i <= end; i++) { if (sdsEncodedObject(argv[i]) && sdslen(argv[i]->ptr) > server.hash_max_ziplist_value) 当前环境初始值是64个 { hashTypeConvert(o, OBJ_ENCODING_HT); 转化为hash表形式编码 break; } } } ********************************************************************************************************************* /* Get the value from a ziplist encoded hash, identified by field. * Returns -1 when the field cannot be found. */ 从ziplist编码的hash中获取值,通过域标识,当域不能找到时,返回-1 int hashTypeGetFromZiplist(robj *o, sds field, unsigned char **vstr, unsigned int *vlen, long long *vll) { unsigned char *zl, *fptr = NULL, *vptr = NULL; int ret; serverAssert(o->encoding == OBJ_ENCODING_ZIPLIST); zl = o->ptr; fptr = ziplistIndex(zl, ZIPLIST_HEAD); if (fptr != NULL) { fptr = ziplistFind(fptr, (unsigned char*)field, sdslen(field), 1); 查找是否存在键为传入参数field的值 if (fptr != NULL) { 找到了 /* Grab pointer to the value (fptr points to the field) */ 获取指向值的指针(fptr指向域) vptr = ziplistNext(zl, fptr);获取指向值的指针 serverAssert(vptr != NULL); 确认不为空 } } if (vptr != NULL) { ret = ziplistGet(vptr, vstr, vlen, vll); 获取具体的值 serverAssert(ret); return 0; } return -1; } ********************************************************************************************************************* /* Get the value from a hash table encoded hash, identified by field. * Returns NULL when the field cannot be found, otherwise the SDS value * is returned. */ 从hash表中获取值,通过域标识,当域不能找到时,返回null,找到就返回一个sds字符串的值 sds hashTypeGetFromHashTable(robj *o, sds field) { dictEntry *de; serverAssert(o->encoding == OBJ_ENCODING_HT); de = dictFind(o->ptr, field); 查找 if (de == NULL) return NULL; return dictGetVal(de); 获取值 } ********************************************************************************************************************* /* Higher level function of hashTypeGet*() that returns the hash value * associated with the specified field. If the field is found C_OK * is returned, otherwise C_ERR. The returned object is returned by * reference in either *vstr and *vlen if it's returned in string form, * or stored in *vll if it's returned as a number. * * If *vll is populated *vstr is set to NULL, so the caller * can always check the function return by checking the return value * for C_OK and checking if vll (or vstr) is NULL. */ 关于hashTypeGet相关的高层次的函数 ,通过关联的确定域返回hash值。如果域被找到了,那么返回C_OK. 否则返回C_ERR.返回的对象通过引用在*vstr和*vlen中,如果返回是是字符串格式的,或者保存在*vll中,如果返回的是数字 int hashTypeGetValue(robj *o, sds field, unsigned char **vstr, unsigned int *vlen, long long *vll) { if (o->encoding == OBJ_ENCODING_ZIPLIST) { ziplist编码 *vstr = NULL; if (hashTypeGetFromZiplist(o, field, vstr, vlen, vll) == 0) 从ziplist获取值 return C_OK; } else if (o->encoding == OBJ_ENCODING_HT) { sds value; if ((value = hashTypeGetFromHashTable(o, field)) != NULL) { 从hash表中获取值 *vstr = (unsigned char*) value; 赋值给vstr,同ziplist保持一致 *vlen = sdslen(value); return C_OK; } } else { serverPanic("Unknown hash encoding"); } return C_ERR; } ********************************************************************************************************************* /* Like hashTypeGetValue() but returns a Redis object, which is useful for * interaction with the hash type outside t_hash.c. * The function returns NULL if the field is not found in the hash. Otherwise * a newly allocated string object with the value is returned. */ 类似函数hashTypeGetValue,但是返回一个redis对象,被用来做和外部t_hash.c交互的hash类型. 函数返回null,如果域不能够在hash表被找到,否则一个新分配的带着值的字符串对象被返回 robj *hashTypeGetValueObject(robj *o, sds field) { unsigned char *vstr; unsigned int vlen; long long vll; if (hashTypeGetValue(o,field,&vstr,&vlen,&vll) == C_ERR) return NULL; 获取值 if (vstr) return createStringObject((char*)vstr,vlen); 如果是字符串,创建字符串对象 else return createStringObjectFromLongLong(vll); 如果是数值,也创建字符串对象 } ********************************************************************************************************************* /* Higher level function using hashTypeGet*() to return the length of the * object associated with the requested field, or 0 if the field does not * exist. */ 使用hashTypeGet*组装的高层次函数,返回请求域所联系的对象的长度,或者返回0如果域不存在 size_t hashTypeGetValueLength(robj *o, sds field) { size_t len = 0; if (o->encoding == OBJ_ENCODING_ZIPLIST) { ziplist编码情况下 unsigned char *vstr = NULL; unsigned int vlen = UINT_MAX; long long vll = LLONG_MAX; if (hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll) == 0) len = vstr ? vlen : sdigits10(vll); 非空获取长度 } else if (o->encoding == OBJ_ENCODING_HT) { hash表编码情况下 sds aux; if ((aux = hashTypeGetFromHashTable(o, field)) != NULL) len = sdslen(aux); 获取字符串长度 } else { serverPanic("Unknown hash encoding"); } return len; } ********************************************************************************************************************* /* Test if the specified field exists in the given hash. Returns 1 if the field * exists, and 0 when it doesn't. */ 测试特定域是否在给定的hash中,返回1如果域存在,不存在就返回0 int hashTypeExists(robj *o, sds field) { if (o->encoding == OBJ_ENCODING_ZIPLIST) { 在ziplist编码的情况下 unsigned char *vstr = NULL; unsigned int vlen = UINT_MAX; long long vll = LLONG_MAX; if (hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll) == 0) return 1; 存在返回1 } else if (o->encoding == OBJ_ENCODING_HT) { 在hash表情况下 if (hashTypeGetFromHashTable(o, field) != NULL) return 1; 存在返回1 } else { serverPanic("Unknown hash encoding"); } return 0; 不存在的情况下返回0 } ********************************************************************************************************************* /* Add a new field, overwrite the old with the new value if it already exists. * Return 0 on insert and 1 on update. 增加一个新的域,如果已经存在就用新值覆盖存在的旧值。当插入新值是返回0, 如果是更新返回1 * By default, the key and value SDS strings are copied if needed, so the * caller retains ownership of the strings passed. However this behavior * can be effected by passing appropriate flags (possibly bitwise OR-ed): 默认情况下,如果需要,键和值的sds字符串被拷贝。这样调用者就可以保留传入字符串的所有权。 然而这个现象能够被通过传入适当的标志影响(可以能按照位异或操作) * HASH_SET_TAKE_FIELD -- The SDS field ownership passes to the function. sds域的所有权传入函数中 * HASH_SET_TAKE_VALUE -- The SDS value ownership passes to the function. sds值的所有权传入函数中 * * When the flags are used the caller does not need to release the passed * SDS string(s). It's up to the function to use the string to create a new * entry or to free the SDS string before returning to the caller. 当传入的标志被使用,调用者不需要去释放传入的sds字符串。函数可以使用这个字符串去创建一个新的实体 或者在函数返回调用者之前释放sds字符串 * HASH_SET_COPY corresponds to no flags passed, and means the default * semantics of copying the values if needed. HASH_SET_COPY对应没有传入任何标志,这个种情况意味着默认的需要复制的值 */ #define HASH_SET_TAKE_FIELD (1<<0) 1 #define HASH_SET_TAKE_VALUE (1<<1) 2 #define HASH_SET_COPY 0 int hashTypeSet(robj *o, sds field, sds value, int flags) { int update = 0; if (o->encoding == OBJ_ENCODING_ZIPLIST) { unsigned char *zl, *fptr, *vptr; zl = o->ptr; fptr = ziplistIndex(zl, ZIPLIST_HEAD); 指向ziplist头部 if (fptr != NULL) { fptr = ziplistFind(fptr, (unsigned char*)field, sdslen(field), 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, (unsigned char*)value, sdslen(value)); } } if (!update) { 如果不是更新,那么说明原来的ziplist中不存在 /* Push new field/value pair onto the tail of the ziplist */ 添加新的域/值对到原理ziplist的末尾 zl = ziplistPush(zl, (unsigned char*)field, sdslen(field), ZIPLIST_TAIL); 添加域(即键) zl = ziplistPush(zl, (unsigned char*)value, sdslen(value), ZIPLIST_TAIL); 添加值 } o->ptr = zl; 重新指向新的ziplist /* Check if the ziplist needs to be converted to a hash table */ 测试新的ziplist是否需要转化为一个hash表 if (hashTypeLength(o) > server.hash_max_ziplist_entries) hashTypeConvert(o, OBJ_ENCODING_HT); } else if (o->encoding == OBJ_ENCODING_HT) { 如果是hash表的情况 dictEntry *de = dictFind(o->ptr,field); 先查找 if (de) { 存在的情况下 sdsfree(dictGetVal(de)); 释放原先的旧值 if (flags & HASH_SET_TAKE_VALUE) { dictGetVal(de) = value; 更新成新值 value = NULL; 值释放 } else { dictGetVal(de) = sdsdup(value); 复制创建一个新的字符串,传入的值不动 } update = 1; 更新 } else { sds f,v; if (flags & HASH_SET_TAKE_FIELD) { 释放传入的域 f = field; field = NULL; } else { f = sdsdup(field); 不释放的情况,需要创建一个新的键 } if (flags & HASH_SET_TAKE_VALUE) { 释放传入的值 v = value; value = NULL; } else { v = sdsdup(value); } dictAdd(o->ptr,f,v); 增加新的键值对 } } else { serverPanic("Unknown hash encoding"); } /* Free SDS strings we did not referenced elsewhere if the flags * want this function to be responsible. */ 如果标志想要这个函数内部处理传入的非空参数,那么这里需要释放这些传入的sds参数 if (flags & HASH_SET_TAKE_FIELD && field) sdsfree(field); if (flags & HASH_SET_TAKE_VALUE && value) sdsfree(value); return update; 返回更新标志 } ********************************************************************************************************************* /* Delete an element from a hash. * Return 1 on deleted and 0 on not found. */ 从hash中删除一个元素。返回1如果删除,返回0如果找不到 int hashTypeDelete(robj *o, sds field) { int deleted = 0; if (o->encoding == OBJ_ENCODING_ZIPLIST) { ziplist编码 unsigned char *zl, *fptr; zl = o->ptr; fptr = ziplistIndex(zl, ZIPLIST_HEAD); 定位到头部 if (fptr != NULL) { fptr = ziplistFind(fptr, (unsigned char*)field, sdslen(field), 1); 查找传入的域(键) if (fptr != NULL) { 找到就删除 zl = ziplistDelete(zl,&fptr); /* Delete the key. */ 删除键 zl = ziplistDelete(zl,&fptr); /* Delete the value. */ 删除值 o->ptr = zl; deleted = 1; 删除标志设置为1 } } } else if (o->encoding == OBJ_ENCODING_HT) { hash表的情况下 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; } ********************************************************************************************************************* /* Return the number of elements in a hash. */ 返回hash表中元素的数量 unsigned long hashTypeLength(const robj *o) { unsigned long length = ULONG_MAX; if (o->encoding == OBJ_ENCODING_ZIPLIST) { length = ziplistLen(o->ptr) / 2; 总长度除以1就是对数 } else if (o->encoding == OBJ_ENCODING_HT) { length = dictSize((const dict*)o->ptr); 这里直接返回对数 } else { serverPanic("Unknown hash encoding"); } return length; } ********************************************************************************************************************* 初始化hash迭代器 hashTypeIterator *hashTypeInitIterator(robj *subject) { hashTypeIterator *hi = zmalloc(sizeof(hashTypeIterator)); 先分配所需空间 hi->subject = subject; hi->encoding = subject->encoding; if (hi->encoding == OBJ_ENCODING_ZIPLIST) { ziplist编码 hi->fptr = NULL; hi->vptr = NULL; } else if (hi->encoding == OBJ_ENCODING_HT) { hash表 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. */ 移动到下一个hash中的实体。如果下个实体可以找到那么返回C_OK,如果迭代到达结尾返回C_ERR int hashTypeNext(hashTypeIterator *hi) { 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) { 如果迭代器是空,指向头部 /* Initialize cursor */ 初始化游标 serverAssert(vptr == NULL); fptr = ziplistIndex(zl, 0); } else { /* Advance cursor */ 向前的光标 serverAssert(vptr != NULL); fptr = ziplistNext(zl, vptr); 指向下一个 } if (fptr == NULL) return C_ERR; /* Grab pointer to the value (fptr points to the field) */ 从指针获取值 vptr = ziplistNext(zl, fptr); serverAssert(vptr != NULL); /* fptr, vptr now point to the first or next pair */ fptr ,vptr 现在指向第一个或者下一个键值对 hi->fptr = fptr; hi->vptr = vptr; } else if (hi->encoding == OBJ_ENCODING_HT) { hash表的情况 if ((hi->de = dictNext(hi->di)) == NULL) return C_ERR; } else { serverPanic("Unknown hash encoding"); } return C_OK; } ********************************************************************************************************************* /* Get the field or value at iterator cursor, for an iterator on a hash value * encoded as a ziplist. Prototype is similar to `hashTypeGetFromZiplist`. */ 获取迭代游标所在的键和值,对一个用ziplist编码的hash值上的迭代器。 原型类似于函数hashTypeGetFromZiplist void hashTypeCurrentFromZiplist(hashTypeIterator *hi, int what, unsigned char **vstr, unsigned int *vlen, long long *vll) { int ret; serverAssert(hi->encoding == OBJ_ENCODING_ZIPLIST); 确认是ziplist编码 if (what & OBJ_HASH_KEY) { 获取键 ret = ziplistGet(hi->fptr, vstr, vlen, vll); serverAssert(ret); } else { 获取值 ret = ziplistGet(hi->vptr, vstr, vlen, vll); serverAssert(ret); } } ********************************************************************************************************************* /* Get the field or value at iterator cursor, for an iterator on a hash value * encoded as a hash table. Prototype is similar to * `hashTypeGetFromHashTable`. */ 在迭代器的游标中获取键或值,对于一个hash表编码的在hash值上的迭代器,原型类似于函数hashTypeGetFromHashTable sds hashTypeCurrentFromHashTable(hashTypeIterator *hi, int what) { serverAssert(hi->encoding == OBJ_ENCODING_HT); if (what & OBJ_HASH_KEY) { return dictGetKey(hi->de); 返回键 } else { return dictGetVal(hi->de); 返回值 } } ********************************************************************************************************************* /* Higher level function of hashTypeCurrent*() that returns the hash value * at current iterator position. hashTypeCurrent这类函数的高层次的表现,返回当前迭代器位置的hash值 * The returned element is returned by reference in either *vstr and *vlen if * it's returned in string form, or stored in *vll if it's returned as * a number. 返回的元素,如果是字符串形式,通过 *vstr 或者 *vlen的引用返回,如果是数值,通过保存在*vll中返回 * If *vll is populated *vstr is set to NULL, so the caller * can always check the function return by checking the return value * type checking if vstr == NULL. 如果*vll被使用了,那么*vstr就会被设置为null,这样调用者总是可以通过检查条件vstr == NULL,来判断返回的值是数值还是字符串 */ void hashTypeCurrentObject(hashTypeIterator *hi, int what, unsigned char **vstr, unsigned int *vlen, long long *vll) { if (hi->encoding == OBJ_ENCODING_ZIPLIST) {ziplist编码 *vstr = NULL; hashTypeCurrentFromZiplist(hi, what, vstr, vlen, vll); 返回当前指向的值 } else if (hi->encoding == OBJ_ENCODING_HT) { sds ele = hashTypeCurrentFromHashTable(hi, what); 当前指向的元素 *vstr = (unsigned char*) ele; 值 *vlen = sdslen(ele); 长度 } else { serverPanic("Unknown hash encoding"); } } ********************************************************************************************************************* /* Return the key or value at the current iterator position as a new * SDS string. */ 用一个新sds字符串返回当前迭代器位置的键或值 sds hashTypeCurrentObjectNewSds(hashTypeIterator *hi, int what) { unsigned char *vstr; unsigned int vlen; long long vll; hashTypeCurrentObject(hi,what,&vstr,&vlen,&vll); 返回当前迭代器位置的元素 if (vstr) return sdsnewlen(vstr,vlen); 字符串 return sdsfromlonglong(vll);数值 } ********************************************************************************************************************* robj *hashTypeLookupWriteOrCreate(client *c, robj *key) { robj *o = lookupKeyWrite(c->db,key); 库中查找键 if (o == NULL) { 找不到 o = createHashObject(); 创建新对象 dbAdd(c->db,key,o); 增加新对象 } else { if (o->type != OBJ_HASH) { 类型不是hash,提示错误 addReply(c,shared.wrongtypeerr); return NULL; } } return o; } ********************************************************************************************************************* 从ziplist转为hashtype void hashTypeConvertZiplist(robj *o, int enc) { serverAssert(o->encoding == OBJ_ENCODING_ZIPLIST); 先确认是ziplist编码 if (enc == OBJ_ENCODING_ZIPLIST) { 如果要编码的目标也是ziplist,无需转化 /* Nothing to do... */ } else if (enc == OBJ_ENCODING_HT) { 如果编码目标是hash hashTypeIterator *hi; dict *dict; int ret; hi = hashTypeInitIterator(o); 创建ziplist的迭代器 dict = dictCreate(&hashDictType, NULL); 创建新的hash while (hashTypeNext(hi) != C_ERR) { 寻找下一个元素 sds key, value; key = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_KEY); 返回字符串键 value = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_VALUE); 返回字符串键对应的值 ret = dictAdd(dict, key, value);加入到hash列表中 if (ret != DICT_OK) { 添加不成功,说明解析ziplist出现问题 serverLogHexDump(LL_WARNING,"ziplist with dup elements dump", o->ptr,ziplistBlobLen(o->ptr)); serverPanic("Ziplist corruption detected"); } } hashTypeReleaseIterator(hi); 释放迭代器 zfree(o->ptr); 释放原先指向的内容 o->encoding = OBJ_ENCODING_HT; 更新编码类型 o->ptr = dict;指向新的hash } else { serverPanic("Unknown hash encoding"); } } ********************************************************************************************************************* 转化为hash类型 void hashTypeConvert(robj *o, int enc) { if (o->encoding == OBJ_ENCODING_ZIPLIST) { 只有当原编码是ziplist时,才有可以转hash hashTypeConvertZiplist(o, enc); } else if (o->encoding == OBJ_ENCODING_HT) { hash转ziplist没有实现 serverPanic("Not implemented"); } else { serverPanic("Unknown hash encoding"); } } ********************************************************************************************************************* /*----------------------------------------------------------------------------- * Hash type commands hash类型的命令 *----------------------------------------------------------------------------*/ Redis Hsetnx 命令用于为哈希表中不存在的的字段赋值 如果哈希表不存在,一个新的哈希表被创建并进行 HSET 操作。 如果字段已经存在于哈希表中,操作无效。 如果 key 不存在,一个新哈希表被创建并执行 HSETNX 命令 hsetnx myhash f1 test1 void hsetnxCommand(client *c) { robj *o; if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; 如果库中不存在该键,那么新建hash表,并且添加新的键值对 hashTypeTryConversion(o,c->argv,2,3); 存在的情况,是否需要转化编码类型 if (hashTypeExists(o, c->argv[2]->ptr)) { 键值已经在hash表中存在,直接返回0 addReply(c, shared.czero); } else { hashTypeSet(o,c->argv[2]->ptr,c->argv[3]->ptr,HASH_SET_COPY); 在hash表中添加新的键值对 addReply(c, shared.cone); 返回1 signalModifiedKey(c,c->db,c->argv[1]); 发出键值被修改的消息 notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id); 通知订阅的客户端hset消息 server.dirty++; 变化的键加1 } } ********************************************************************************************************************* Redis Hset 命令用于为哈希表中的字段赋值 。 如果哈希表不存在,一个新的哈希表被创建并进行 HSET 操作。 如果字段已经存在于哈希表中,旧值将被覆盖 hset myhash f1 test2 void hsetCommand(client *c) { int i, created = 0; robj *o; if ((c->argc % 2) == 1) { 如果传入的参数不是成对的,那么传入参数有问题 addReplyError(c,"wrong number of arguments for HMSET"); return; } if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; 如果库中不存在该键,那么新建hash表,并且添加新的键值对 hashTypeTryConversion(o,c->argv,2,c->argc-1); 存在的情况,是否需要转化编码类型 for (i = 2; i < c->argc; i += 2) 对每一对键值对进行检查 created += !hashTypeSet(o,c->argv[i]->ptr,c->argv[i+1]->ptr,HASH_SET_COPY); /* HMSET (deprecated) and HSET return value is different. */ HMSET(已弃用)和HSET返回值不同 char *cmdname = c->argv[0]->ptr; if (cmdname[1] == 's' || cmdname[1] == 'S') { 通过字母判断是否是多键值设置 /* HSET */hset命令 addReplyLongLong(c, created); } else { /* HMSET */hmset命令 addReply(c, shared.ok); } signalModifiedKey(c,c->db,c->argv[1]); notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id); server.dirty++; } ********************************************************************************************************************* Redis Hincrby 命令用于为哈希表中的字段值加上指定增量值。 增量也可以为负数,相当于对指定字段进行减法操作。 如果哈希表的 key 不存在,一个新的哈希表被创建并执行 HINCRBY 命令。 如果指定的字段不存在,那么在执行命令前,字段的值被初始化为 0 。 对一个储存字符串值的字段执行 HINCRBY 命令将造成一个错误。 本操作的值被限制在 64 位(bit)有符号数字表示之内 HSET myhash f2 5 HINCRBY myhash f2 2 void hincrbyCommand(client *c) { long long value, incr, oldvalue; robj *o; sds new; unsigned char *vstr; unsigned int vlen; if (getLongLongFromObjectOrReply(c,c->argv[3],&incr,NULL) != C_OK) return; 传入要增加的非整型值,返回错误 if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; 如果库中不存在该键,那么新建hash表,并且添加新的键值对 if (hashTypeGetValue(o,c->argv[2]->ptr,&vstr,&vlen,&value) == C_OK) { 获取对应键的值 if (vstr) { 如果是字符串 if (string2ll((char*)vstr,vlen,&value) == 0) { 将字符串转化为数字 addReplyError(c,"hash value is not an integer"); 不成功,那就是不是一个整型 return; } } /* Else hashTypeGetValue() already stored it into &value */ 非字符串的情况下,数值已经存储到value中 } else { value = 0; 不在库中,那么初始化为0 } oldvalue = value; 保存旧值 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; 正常情况,将传入值进行计算 new = sdsfromlonglong(value); 转化为字符串输出 hashTypeSet(o,c->argv[2]->ptr,new,HASH_SET_TAKE_VALUE); 将新值设置到hash中,如果不存在就新建键值对(需要处理传入的字符串) addReplyLongLong(c,value); 回复客户端 signalModifiedKey(c,c->db,c->argv[1]); notifyKeyspaceEvent(NOTIFY_HASH,"hincrby",c->argv[1],c->db->id); server.dirty++; } ********************************************************************************************************************* Redis Hincrbyfloat 命令用于为哈希表中的字段值加上指定浮点数增量值。 如果指定的字段不存在,那么在执行命令前,字段的值被初始化为 0 // HSET mykey f3 10.50 HINCRBYFLOAT mykey f3 0.1 void hincrbyfloatCommand(client *c) { long double value, incr; long long ll; robj *o; sds new; unsigned char *vstr; unsigned int vlen; if (getLongDoubleFromObjectOrReply(c,c->argv[3],&incr,NULL) != C_OK) return; 传入参数是否是数值,不是就返回 if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; 如果库中不存在该键,那么新建hash表,并且添加新的键值对 if (hashTypeGetValue(o,c->argv[2]->ptr,&vstr,&vlen,&ll) == C_OK) { 存在获取对应键值 if (vstr) { 是字符串 if (string2ld((char*)vstr,vlen,&value) == 0) { 转浮点型数据 addReplyError(c,"hash value is not a float"); 不能转提示错误 return; } } else { value = (long double)ll; 进行类型转化 } } else { value = 0; 不存在初始化为0 } value += incr; if (isnan(value) || isinf(value)) { 无穷大和空,不能相加 addReplyError(c,"increment would produce NaN or Infinity"); return; } /* The maximum number of characters needed to represent a long double * as a string (long double has a huge range). * This should be the size of the buffer given to ld2string */ -- #define MAX_LONG_DOUBLE_CHARS 5*1024 char buf[MAX_LONG_DOUBLE_CHARS]; int len = ld2string(buf,sizeof(buf),value,LD_STR_HUMAN); 浮点型转字符串得到的长度 new = sdsnewlen(buf,len); hashTypeSet(o,c->argv[2]->ptr,new,HASH_SET_TAKE_VALUE); 设置新的键值对,传入的参数需要内部处理 addReplyBulkCBuffer(c,buf,len); signalModifiedKey(c,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. */ 始终将命令HINCRBYFLOAT赋值成为具有最终hset值的命令。 这是为了确保不同的操作中(复制或者在AOF重启之后)浮点数精度和格式保持一致。 robj *aux, *newobj; aux = createStringObject("HSET",4); newobj = createRawStringObject(buf,len); rewriteClientCommandArgument(c,0,aux); decrRefCount(aux); rewriteClientCommandArgument(c,3,newobj); decrRefCount(newobj); } ********************************************************************************************************************* static void addHashFieldToReply(client *c, robj *o, sds field) { int ret; if (o == NULL) { 如果对象为空,返回空 addReplyNull(c); return; } if (o->encoding == OBJ_ENCODING_ZIPLIST) { ziplist类型编码 unsigned char *vstr = NULL; unsigned int vlen = UINT_MAX; long long vll = LLONG_MAX; ret = hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll); 从ziplist根据传入键field获取值 if (ret < 0) { 不存在 addReplyNull(c); } else { if (vstr) { 是字符串 addReplyBulkCBuffer(c, vstr, vlen); } else { 非字符串,数值 addReplyBulkLongLong(c, vll); } } } else if (o->encoding == OBJ_ENCODING_HT) { hash类型的表 sds value = hashTypeGetFromHashTable(o, field); 从hash表中获取值 if (value == NULL) addReplyNull(c); else addReplyBulkCBuffer(c, value, sdslen(value)); } else { serverPanic("Unknown hash encoding"); } } ********************************************************************************************************************* Redis Hget 命令用于返回哈希表中指定字段的值 void hgetCommand(client *c) { robj *o; if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp])) == NULL || 如果库中不存在 checkType(c,o,OBJ_HASH)) return; 或者类型不是hash,那么直接返回即可 addHashFieldToReply(c, o, c->argv[2]->ptr); 否则返回查找的结果 } ********************************************************************************************************************* Redis Hmget 命令用于返回哈希表中,一个或多个给定字段的值。 如果指定的字段不存在于哈希表,那么返回一个 nil 值 HMGET myhash f1 f2 f3 void hmgetCommand(client *c) { robj *o; int i; /* Don't abort when the key cannot be found. Non-existing keys are empty * hashes, where HMGET should respond with a series of null bulks. */ 当键在库中找不到时也不终止。不存在的键都是空的hash值,HMGET应当返回一系列的空值 o = lookupKeyRead(c->db, c->argv[1]); hash表是否存在库中 if (o != NULL && o->type != OBJ_HASH) { 存在hash表,但是不是hash类型 addReply(c, shared.wrongtypeerr); return; } addReplyArrayLen(c, c->argc-2); 增加回复客户端的数组长度 for (i = 2; i < c->argc; i++) { 对每个键遍历 addHashFieldToReply(c, o, c->argv[i]->ptr); 将回复添加到数组对应的位置上 } } ********************************************************************************************************************* Redis Hdel 命令用于删除哈希表 key 中的一个或多个指定字段,不存在的字段将被忽略 HDEL myhash f1 void hdelCommand(client *c) { robj *o; int j, deleted = 0, keyremoved = 0; if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || 如果库中不存在 checkType(c,o,OBJ_HASH)) return; 或者类型不是hash,直接返回 for (j = 2; j < c->argc; j++) { if (hashTypeDelete(o,c->argv[j]->ptr)) { 从hash表中删除一个键 deleted++; if (hashTypeLength(o) == 0) { 如果hash已空,那么从库中删除这个hash表 dbDelete(c->db,c->argv[1]); keyremoved = 1; break; } } } if (deleted) { 如果有删除 signalModifiedKey(c,c->db,c->argv[1]); 通知键修改的信息 notifyKeyspaceEvent(NOTIFY_HASH,"hdel",c->argv[1],c->db->id); 通知订阅的客户端hdel消息 if (keyremoved) 如果hash表被清除 notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1], 通知客户端del消息 c->db->id); server.dirty += deleted; 删除键的个数 } addReplyLongLong(c,deleted); 回复客户端删除的个数 } ********************************************************************************************************************* Redis Hlen 命令用于获取哈希表中字段的数量 hlen myhash void hlenCommand(client *c) { robj *o; if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || 如果库中不存在 checkType(c,o,OBJ_HASH)) return; 或者类型不是hash,直接返回 addReplyLongLong(c,hashTypeLength(o)); 返回长度 } ********************************************************************************************************************* Redis HSTRLEN 命令用于获取哈希表中字段的数量 HSTRLEN myhash f1 void hstrlenCommand(client *c) { robj *o; if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,o,OBJ_HASH)) return; addReplyLongLong(c,hashTypeGetValueLength(o,c->argv[2]->ptr)); 返回键对应值的长度 } ********************************************************************************************************************* 获取当前迭代器位置的值返回给客户端 static void addHashIteratorCursorToReply(client *c, hashTypeIterator *hi, int what) { if (hi->encoding == OBJ_ENCODING_ZIPLIST) { ziplist编码 unsigned char *vstr = NULL; unsigned int vlen = UINT_MAX; long long vll = LLONG_MAX; hashTypeCurrentFromZiplist(hi, what, &vstr, &vlen, &vll); 获取当前位置的键和值 if (vstr) 字符串 addReplyBulkCBuffer(c, vstr, vlen); else 数值 addReplyBulkLongLong(c, vll); } else if (hi->encoding == OBJ_ENCODING_HT) { hash编码 sds value = hashTypeCurrentFromHashTable(hi, what); 当前位置的值 addReplyBulkCBuffer(c, value, sdslen(value)); } else { serverPanic("Unknown hash encoding"); } } ********************************************************************************************************************* void genericHgetallCommand(client *c, int flags) { robj *o; hashTypeIterator *hi; int length, count = 0; if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymap[c->resp])) 如果库中不存在 == NULL || checkType(c,o,OBJ_HASH)) return; 或者类型不是hash,直接返回 /* We return a map if the user requested keys and values, like in the * HGETALL case. Otherwise to use a flat array makes more sense. */ 如果用户请求键和值,我们将返回一个map,就像HGETALL的情况一样。否则使用一维数组更有明了 length = hashTypeLength(o); hash中总的个数 if (flags & OBJ_HASH_KEY && flags & OBJ_HASH_VALUE) { 如果键和值都需要,使用马屁形式返回 addReplyMapLen(c, length); } else { 否则使用一维数组 addReplyArrayLen(c, length); } 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); 释放迭代器 /* Make sure we returned the right number of elements. */ 确保我们返回正确的元素个数 if (flags & OBJ_HASH_KEY && flags & OBJ_HASH_VALUE) count /= 2; serverAssert(count == length); } ********************************************************************************************************************* Redis Hkeys 命令用于获取哈希表中的所有域(field) hkeys myhash void hkeysCommand(client *c) { genericHgetallCommand(c,OBJ_HASH_KEY); } ********************************************************************************************************************* Redis Hvals 命令返回哈希表所有的值 HVALS myhash void hvalsCommand(client *c) { genericHgetallCommand(c,OBJ_HASH_VALUE); } ********************************************************************************************************************* Redis Hgetall 命令用于返回哈希表中,所有的字段和值。 在返回值里,紧跟每个字段名(field name)之后是字段的值(value),所以返回值的长度是哈希表大小的两倍。 hgetall myhash void hgetallCommand(client *c) { genericHgetallCommand(c,OBJ_HASH_KEY|OBJ_HASH_VALUE); } ********************************************************************************************************************* Redis Hexists 命令用于查看哈希表的指定字段是否存在 HEXISTS myhash f1 void hexistsCommand(client *c) { robj *o; if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,o,OBJ_HASH)) return; addReply(c, hashTypeExists(o,c->argv[2]->ptr) ? shared.cone : shared.czero); hash中是否存在键 } ********************************************************************************************************************* Redis HSCAN 命令用于迭代哈希表中的键的匹配 HSCAN key cursor [MATCH pattern] [COUNT count] cursor - 游标。 pattern - 匹配的模式。 count - 指定从数据集里返回多少元素,默认值为 10 HSCAN sites 0 match "f*" void hscanCommand(client *c) { robj *o; unsigned long cursor; if (parseScanCursorOrReply(c,c->argv[2],&cursor) == C_ERR) return; 检验游标是否正常 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptyscan)) == NULL || checkType(c,o,OBJ_HASH)) return; scanGenericCommand(c,o,cursor); 进行检索 } *********************************************************************************************************************