hget如何获取多个value_Redis(五):hash/hset/hget 命令源码解析

本文介绍了Redis中哈希(Hash)数据结构的相关操作,包括hset、hmset、hget等命令的用途、格式和实现原理。哈希表在Redis中用于存储对象,当字段数量或字段长度超过一定阈值时,会从ziplist转换为hashtable。hset用于设置单个字段值,hmset用于批量设置,而hget用于获取指定字段的值,这些操作的时间复杂度为O(1)。
摘要由CSDN通过智能技术生成

Redis作为nosql数据库,kv string型数据的支持是最基础的,但是如果仅有kv的操作,也不至于有redis的成功。(memcache就是个例子)

Redis除了string, 还有hash,list,set,zset。

所以,我们就来看看hash的相关操作实现吧。

首先,我们从作用上理解hash存在的意义:Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。从另一个方面来说是,hash可以聚合很多类似的属性,这是string中难以实现的。

所以,总体来说,hash的命令与string的命令差不太多。其操作手册如下:

1> hdel 命令:删除一个或多个哈希表字段

格式:HDEL key field2 [field2]

返回值:被成功删除字段的数量,不包括被忽略的字段。

2> hexists 命令:查看哈希表 key 中,指定的字段是否存在

格式:HEXISTS key field

返回值:如果哈希表含有给定字段,返回 1 。 如果哈希表不含有给定字段,或 key 不存在,返回 0 。

3> hget 命令:获取存储在哈希表中指定字段的值

格式:HGET key field

返回值:返回给定字段的值。如果给定的字段或 key 不存在时,返回 nil 。

4> hgetall 命令:获取在哈希表中指定 key 的所有字段和值

格式:HGETALL key

返回值:以列表形式返回哈希表的字段及字段值。 若 key 不存在,返回空列表。

5> hincrby 命令:为哈希表 key 中的指定字段的整数值加上增量 increment

格式:HINCRBY key field increment

返回值:执行 HINCRBY 命令之后,哈希表中字段的值。

6> hincrbyfloat 命令:为哈希表 key 中的指定字段的浮点数值加上增量 increment

格式:HINCRBYFLOAT key field increment

返回值:执行 Hincrbyfloat 命令之后,哈希表中字段的值。

7> hkeys 命令:获取所有哈希表中的字段

格式:HKEYS key

返回值:包含哈希表中所有字段的列表。 当 key 不存在时,返回一个空列表。

8> hlen 命令:获取哈希表中字段的数量

格式:HLEN key

返回值:哈希表中字段的数量。 当 key 不存在时,返回 0 。

9> hmget 命令:获取所有给定字段的值

格式:HMGET key field1 [field2]

返回值:一个包含多个给定字段关联值的表,表值的排列顺序和指定字段的请求顺序一样。

10> hmset 命令:同时将多个 field-value (域-值)对设置到哈希表 key 中

格式:HMSET key field1 value1 [field2 value2 ]

返回值:如果命令执行成功,返回 OK 。

11> hset 命令:将哈希表 key 中的字段 field 的值设为 value

格式:HSET key field value

返回值:如果字段是哈希表中的一个新建字段,并且值设置成功,返回 1 。 如果哈希表中域字段已经存在且旧值已被新值覆盖,返回 0 。

12> hsetnx 命令:只有在字段 field 不存在时,设置哈希表字段的值

格式:HSETNX key field value

返回值:设置成功,返回 1 。 如果给定字段已经存在且没有操作被执行,返回 0 。

13> hvals 命令:获取哈希表中所有值

格式:HVALS key

返回值:一个包含哈希表中所有值的表。 当 key 不存在时,返回一个空表。

14> hscan 命令:迭代哈希表中的键值对

格式:HSCAN key cursor [MATCH pattern] [COUNT count]

其中,有的是单kv操作有的是指量操作,有的是写操作有的是读操作。从实现上看,大体上很多命令是类似的:

比如: hset/hmset/hincrbyXXX 可以是一类的

比如:hget/hgetall/hexists/hkeys/hmget 可以是一类

注意:以上分法仅是为了让我们看清本质,对实际使用并无实际参考意义。

所以,我们就挑几个方法来解析下 hash 的操作实现吧。

零、hash数据结构

hash相关的命令定义如下:

{"hset",hsetCommand,4,"wmF",0,NULL,1,1,1,0,0},

{"hsetnx",hsetnxCommand,4,"wmF",0,NULL,1,1,1,0,0},

{"hget",hgetCommand,3,"rF",0,NULL,1,1,1,0,0},

{"hmset",hmsetCommand,-4,"wm",0,NULL,1,1,1,0,0},

{"hmget",hmgetCommand,-3,"r",0,NULL,1,1,1,0,0},

{"hincrby",hincrbyCommand,4,"wmF",0,NULL,1,1,1,0,0},

{"hincrbyfloat",hincrbyfloatCommand,4,"wmF",0,NULL,1,1,1,0,0},

{"hdel",hdelCommand,-3,"wF",0,NULL,1,1,1,0,0},

{"hlen",hlenCommand,2,"rF",0,NULL,1,1,1,0,0},

{"hstrlen",hstrlenCommand,3,"rF",0,NULL,1,1,1,0,0},

{"hkeys",hkeysCommand,2,"rS",0,NULL,1,1,1,0,0},

{"hvals",hvalsCommand,2,"rS",0,NULL,1,1,1,0,0},

{"hgetall",hgetallCommand,2,"r",0,NULL,1,1,1,0,0},

{"hexists",hexistsCommand,3,"rF",0,NULL,1,1,1,0,0},

{"hscan",hscanCommand,-3,"rR",0,NULL,1,1,1,0,0},

ziplist 数据结构

typedef structzlentry {

unsignedintprevrawlensize, prevrawlen;

unsignedintlensize, len;

unsignedintheadersize;

unsignedcharencoding;

unsignedchar *p;

} zlentry;#define ZIPLIST_BYTES(zl) (*((uint32_t*)(zl)))

#define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t))))

#define ZIPLIST_LENGTH(zl) (*((uint16_t*)((zl)+sizeof(uint32_t)*2)))

#define ZIPLIST_HEADER_SIZE (sizeof(uint32_t)*2+sizeof(uint16_t))

#define ZIPLIST_END_SIZE (sizeof(uint8_t))

#define ZIPLIST_ENTRY_HEAD(zl) ((zl)+ZIPLIST_HEADER_SIZE)

#define ZIPLIST_ENTRY_TAIL(zl) ((zl)+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)))

#define ZIPLIST_ENTRY_END(zl) ((zl)+intrev32ifbe(ZIPLIST_BYTES(zl))-1)

hashtable 数据结构:

typedef structdict {

dictType*type;void *privdata;

dictht ht[2];long rehashidx; /*rehashing not in progress if rehashidx == -1*/unsignedlong iterators; /*number of iterators currently running*/} dict;

typedefstructdictht {

dictEntry**table;

unsignedlongsize;

unsignedlongsizemask;

unsignedlongused;

} dictht;

typedefstructdictEntry {void *key;void *val;struct dictEntry *next;

} dictEntry;

一、hset 设置单个 field -> value

“增删改查”中的“增改” 就是它了。

//t_hash.c, set key field value

void hsetCommand(client *c) {intupdate;

robj*o;//1. 查找hash的key是否存在,不存在则新建一个,然后在其上进行数据操作

if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;//2. 检查2-3个参数是否需要将简单版(ziplist)hash表转换为复杂的hash表,转换后的表通过 o->ptr 体现

hashTypeTryConversion(o,c->argv,2,3);//3. 添加kv到 o 的hash表中

update = hashTypeSet(o,c->argv[2]->ptr,c->argv[3]->ptr,HASH_SET_COPY);

addReply(c, update?shared.czero : shared.cone);//变更命令传播

signalModifiedKey(c->db,c->argv[1]);

notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id);

server.dirty++;

}//1. 获取db外部的key, 即整体hash数据实例//t_hash.c

robj *hashTypeLookupWriteOrCreate(client *c, robj *key) {

robj*o = lookupKeyWrite(c->db,key);if (o ==NULL) {//此处创建的hashObject是以 ziplist 形式的

o =createHashObject();

dbAdd(c->db,key,o);

}else{//不是hash类型的键已存在,不可覆盖,返回错误

if (o->type !=OBJ_HASH) {

addReply(c,shared.wrongtypeerr);returnNULL;

}

}returno;

}//object.c, 创建hashObject, 以 ziplist 形式创建

robj *createHashObject(void) {

unsignedchar *zl =ziplistNew();

robj*o =createObject(OBJ_HASH, zl);

o->encoding =OBJ_ENCODING_ZIPLIST;returno;

}//ziplist.c

static unsigned char *createList() {

unsignedchar *zl =ziplistNew();

zl= ziplistPush(zl, (unsigned char*)"foo", 3, ZIPLIST_TAIL);

zl= ziplistPush(zl, (unsigned char*)"quux", 4, ZIPLIST_TAIL);

zl= ziplistPush(zl, (unsigned char*)"hello", 5, ZIPLIST_HEAD);

zl= ziplistPush(zl, (unsigned char*)"1024", 4, ZIPLIST_TAIL);returnzl;

}//2. 检查参数,是否需要将 ziplist 形式的hash表转换为真正的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.*/

void hashTypeTryConversion(robj *o, robj **argv, int start, intend) {inti;if (o->encoding != OBJ_ENCODING_ZIPLIST) return;for (i = start; i <= end; i++) {//参数大于设置的 hash_max_ziplist_value (默认: 64)时,会直接将 ziplist 转换为 ht//OBJ_ENCODING_RAW, OBJ_ENCODING_EMBSTR//循环检查参数,只要发生了一次转换就结束检查(没必要继续了)

if (sdsEncodedObject(argv[i]) &&sdslen(argv[i]->ptr) >server.hash_max_ziplist_value)

{//这个转换过程很有意思,我们深入看看

hashTypeConvert(o, OBJ_ENCODING_HT);break;

}

}

}//t_hash.c, 转换编码方式 (如上, ziplist -> ht)

void hashTypeConvert(robj *o, intenc) {if (o->encoding ==OBJ_ENCODING_ZIPLIST) {//此处我们只处理这种情况

hashTypeConvertZiplist(o, enc);

}else if (o->encoding ==OBJ_ENCODING_HT) {

serverPanic("Not implemented");

}else{

serverPanic("Unknown hash encoding");

}

}//t_hash.c, 转换编码 ziplist 为目标 enc (实际只能是 OBJ_ENCODING_HT)

void hashTypeConvertZiplist(robj *o, intenc) {

serverAssert(o->encoding ==OBJ_ENCODING_ZIPLIST);if (enc ==OBJ_ENCODING_ZIPLIST) {/*Nothing to do...*/}else if (enc ==OBJ_ENCODING_HT) {

hashTypeIterator*hi;

dict*dict;intret;//迭代器创建

hi =hashTypeInitIterator(o);//一个hash的数据结构就是一个 dict, 从这个级别来说, hash 与 db 是一个级别的

dict = dictCreate(&hashDictType, NULL);//依次迭代 o, 赋值到 hi->fptr, hi->vptr//依次添加到 dict 中

while (hashTypeNext(hi) !=C_ERR) {

sds key, value;//从 hi->fptr 中获取key//从 hi->vptr 中获取value

key =hashTypeCurrentObjectNewSds(hi,OBJ_HASH_KEY);

value=hashTypeCurrentObjectNewSds(hi,OBJ_HASH_VALUE);//添加到 dict 中

ret =dictAdd(dict, key, value);if (ret !=DICT_OK) {

serverLogHexDump(LL_WARNING,"ziplist with dup elements dump",

o->ptr,ziplistBlobLen(o->ptr));

serverPanic("Ziplist corruption detected");

}

}//释放迭代器

hashTypeReleaseIterator(hi);

zfree(o->ptr);//将变更反映到o对象上返回

o->encoding =OBJ_ENCODING_HT;

o->ptr =dict;

}else{

serverPanic("Unknown hash encoding");

}

}//2.1. 迭代ziplist元素//t_hash.c, 迭代器/*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) {if (hi->encoding ==OBJ_ENCODING_ZIPLIST) {

unsignedchar *zl;

unsignedchar *fptr, *vptr;//每次都是基于原始字符器进行计算偏移//迭代的是 fptr,vptr

zl = hi->subject->ptr;

fptr= hi->fptr;

vptr= hi->vptr;//第一次查找时使用index查找,后续则使用 fptr,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) returnC_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*/hi->fptr =fptr;

hi->vptr =vptr;

}else if (hi->encoding ==OBJ_ENCODING_HT) {if ((hi->de = dictNext(hi->di)) == NULL) returnC_ERR;

}else{

serverPanic("Unknown hash encoding");

}returnC_OK;

}//ziplist.c, 查找 index 的元素/*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.*/unsignedchar *ziplistIndex(unsigned char *zl, intindex) {

unsignedchar *p;

unsignedint prevlensize, prevlen = 0;if (index < 0) {//小于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);

}

}//迭代完成还没找到元素 p[0]=ZIP_END//index 超出整体ziplist大小则遍历完成后 index>0

return (p[0] == ZIP_END || index > 0) ?NULL : p;

}//ziplist.c, 由 fptr,vptr 进行迭代元素/*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.*/unsignedchar *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) {returnNULL;

}//当前指针偏移当前元素长度(根据ziplist协议),即到下一元素指针位置

p +=zipRawEntryLength(p);if (p[0] ==ZIP_END) {returnNULL;

}returnp;

}/*Return the total number of bytes used by the entry pointed to by 'p'.*/

static unsigned int zipRawEntryLength(unsigned char *p) {

unsignedintprevlensize, encoding, lensize, len;

ZIP_DECODE_PREVLENSIZE(p, prevlensize);

ZIP_DECODE_LENGTH(p+prevlensize, encoding, lensize, len);return prevlensize + lensize +len;

}//2.2. t_hash.c, 获取 hashTypeIterator 的具体值,写入 vstr, vlen 中/*Return the key or value at the current iterator position as a new

* SDS string.*/sds hashTypeCurrentObjectNewSds(hashTypeIterator*hi, intwhat) {

unsignedchar *vstr;

unsignedintvlen;long longvll;

hashTypeCurrentObject(hi,what,&vstr,&vlen,&vll);if (vstr) returnsdsnewlen(vstr,vlen);returnsdsfromlonglong(vll);

}/*Higher level function of hashTypeCurrent*() that returns the hash value

* at current iterator position.

*

* 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.

*

* 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.*/

void hashTypeCurrentObject(hashTypeIterator *hi, int what, unsigned char **vstr, unsigned int *vlen, long long *vll) {if (hi->encoding ==OBJ_ENCODING_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");

}

}//t_hash.c, 从ziplist中获取某个 hashTypeIterator 的具体值,结果定稿 vstr, vlen/*Get the field or value at iterator cursor, for an iterator on a hash value

* encoded as a ziplist. Prototype is similar to `hashTypeGetFromZiplist`.*/

void hashTypeCurrentFromZiplist(hashTypeIterator *hi, intwhat,

unsignedchar **vstr,

unsignedint *vlen,long long *vll)

{intret;

serverAssert(hi->encoding ==OBJ_ENCODING_ZIPLIST);//OBJ_HASH_KEY 从 fptr 中获取, 否则从 vptr 中获取

if (what &OBJ_HASH_KEY) {

ret= ziplistGet(hi->fptr, vstr, vlen, vll);

serverAssert(ret);

}else{

ret= ziplistGet(hi->vptr, vstr, vlen, vll);

serverAssert(ret);

}

}//ziplist.c,/*Get entry pointed to by 'p' and store in either '*sstr' or 'sval' depending

* on the encoding of the entry. '*sstr' is always set to NULL to be able

* to find out whether the string pointer or the integer value was set.

* Return 0 if 'p' points to the end of the ziplist, 1 otherwise.*/unsignedint ziplistGet(unsigned char *p, unsigned char **sstr, unsigned int *slen, long long *sval) {

zlentry entry;if (p == NULL || p[0] == ZIP_END) return 0;if (sstr) *sstr =NULL;//按照ziplist的编码协议, 获取头部信息

zipEntry(p, &entry);if(ZIP_IS_STR(entry.encoding)) {if(sstr) {*slen =entry.len;*sstr = p+entry.headersize;

}

}else{if(sval) {*sval = zipLoadInteger(p+entry.headersize,entry.encoding);

}

}return 1;

}//ziplist.c, 解析原始字符串为 zlentry/*Return a struct with all information about an entry.*/

static void zipEntry(unsigned char *p, zlentry *e) {//按照ziplist的编码协议,依次读取 prevrawlensize, prevrawlen

ZIP_DECODE_PREVLEN(p, e->prevrawlensize, e->prevrawlen);//指向下一位置偏移,按照ziplist的编码协议,依次读取 encoding, lensize, len

ZIP_DECODE_LENGTH(p + e->prevrawlensize, e->encoding, e->lensize, e->len);//除去header得到 body偏移

e->headersize = e->prevrawlensize + e->lensize;

e->p =p;

}

具体header解析如下, 有兴趣的点开瞅瞅:

//ziplist.c/*Decode the length of the previous element, from the perspective of the entry

* pointed to by 'ptr'.*/

#define ZIP_DECODE_PREVLEN(ptr, prevlensize, prevlen) do { \

//解析第1个字符为 prevlensize

ZIP_DECODE_PREVLENSIZE(ptr, prevlensize); \if ((prevlensize) == 1) { \

(prevlen)= (ptr)[0]; \

}else if ((prevlensize) == 5) { \

assert(sizeof((prevlensize)) == 4); \//当ptr[0]>254时,代表内容有点大,需要使用 5个字符保存上一字符长度

memcpy(&(prevlen), ((char*)(ptr)) + 1, 4); \

memrev32ifbe(&prevlen); \

} \

}while(0);/*Decode the number of bytes required to store the length of the previous

* element, from the perspective of the entry pointed to by 'ptr'.*/

#define ZIP_DECODE_PREVLENSIZE(ptr, prevlensize) do { \

if ((ptr)[0]

(prevlensize)= 1; \

}else{ \

(prevlensize)= 5; \

} \

}while(0);/*Decode the length encoded in 'ptr'. The 'encoding' variable will hold the

* entries encoding, the 'lensize' variable will hold the number of bytes

* required to encode the entries length, and the 'len' variable will hold the

* entries length.*/

#define ZIP_DECODE_LENGTH(ptr, encoding, lensize, len) do { \

//解析第1个字符为 编码格式 &ZIP_STR_MASK=0xc0

ZIP_ENTRY_ENCODING((ptr), (encoding)); \if ((encoding)

if ((encoding) ==ZIP_STR_06B) { \

(lensize)= 1; \

(len)= (ptr)[0] & 0x3f; \

}//1 << 6 =64

else if ((encoding) ==ZIP_STR_14B) { \

(lensize)= 2; \

(len)= (((ptr)[0] & 0x3f) << 8) | (ptr)[1]; \

}//2 << 6 =128

else if (encoding ==ZIP_STR_32B) { \

(lensize)= 5; \

(len)= ((ptr)[1] << 24) |\

((ptr)[2] << 16) |\

((ptr)[3] << 8) |\

((ptr)[4]); \

}else{ \

assert(NULL); \

} \

}else{ \//超过 0xc0 的长度了,直接使用 1,2,3,4 表示len

(lensize) = 1; \

(len)=zipIntSize(encoding); \

} \

}while(0);/*Extract the encoding from the byte pointed by 'ptr' and set it into

* 'encoding'.*/

#define ZIP_ENTRY_ENCODING(ptr, encoding) do { \(encoding)= (ptr[0]); \if ((encoding) < ZIP_STR_MASK) (encoding) &=ZIP_STR_MASK; \

}while(0)/*Different encoding/length possibilities*/

#define ZIP_STR_MASK 0xc0

#define ZIP_INT_MASK 0x30

#define ZIP_STR_06B (0 << 6) //0x00

#define ZIP_STR_14B (1 << 6) //0x40

#define ZIP_STR_32B (2 << 6) //0x80

#define ZIP_INT_16B (0xc0 | 0<<4) //0xc0

#define ZIP_INT_32B (0xc0 | 1<<4) //0xd0

#define ZIP_INT_64B (0xc0 | 2<<4) //0xe0

#define ZIP_INT_24B (0xc0 | 3<<4) //0xf0

#define ZIP_INT_8B 0xfe //0xfe

View Code

添加kv到对应的key实例中:

//3. 添加kv到 hash表中, 稍微复杂//t_hash.c, 做变更到hash表中

int hashTypeSet(robj *o, sds field, sds value, intflags) {int update = 0;//针对ziplist 的添加, 与 ht 编码的添加, 自然是分别处理

if (o->encoding ==OBJ_ENCODING_ZIPLIST) {

unsignedchar *zl, *fptr, *vptr;

zl= o->ptr;//找到ziplist 的头节点指针

fptr =ziplistIndex(zl, ZIPLIST_HEAD);if (fptr !=NULL) {//尝试查找该 field 对应的元素(从1开始),如果找到则先删除原值,然后统一添加

fptr = ziplistFind(fptr, (unsigned char*)field, sdslen(field), 1);if (fptr !=NULL) {/*Grab pointer to the value (fptr points to the field)*/

//value 不可以为null, 否则 ziplist 将无法工作

vptr =ziplistNext(zl, fptr);

serverAssert(vptr!=NULL);

update= 1;/*Delete value*/

//先删除旧的 value, 再以插入的形式更新, 后续讲删除时再详解

zl = ziplistDelete(zl, &vptr);/*Insert new value*/

//重点,将value添加到 ziplist 中

zl = ziplistInsert(zl, vptr, (unsigned char*)value,

sdslen(value));

}

}//没有找到对应元素,则直接将元素添加到尾部即可

if (!update) {/*Push new field/value pair onto the tail of the ziplist*/zl= ziplistPush(zl, (unsigned char*)field, sdslen(field),

ZIPLIST_TAIL);

zl= ziplistPush(zl, (unsigned char*)value, sdslen(value),

ZIPLIST_TAIL);

}

o->ptr =zl;/*Check if the ziplist needs to be converted to a hash table*/

//大于设置的阀值后,转换ziplist为ht(默认: 512)

if (hashTypeLength(o) >server.hash_max_ziplist_entries)

hashTypeConvert(o, OBJ_ENCODING_HT);

}else if (o->encoding ==OBJ_ENCODING_HT) {

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.*/

if (flags & HASH_SET_TAKE_FIELD &&field) sdsfree(field);if (flags & HASH_SET_TAKE_VALUE &&value) sdsfree(value);returnupdate;

}//3.1. 使用ziplist进行保存 field -> value//ziplist.c, 查找某个 field 是否存在于ziplist中/*Find pointer to the entry equal to the specified entry. Skip 'skip' entries

* between every comparison. Returns NULL when the field could not be found.*/unsignedchar *ziplistFind(unsigned char *p, unsigned char *vstr, unsigned int vlen, unsigned intskip) {int skipcnt = 0;

unsignedchar vencoding = 0;long long vll = 0;while (p[0] !=ZIP_END) {

unsignedintprevlensize, encoding, lensize, len;

unsignedchar *q;//解析整个字符串p的 prevlensize,encoding,lensize,len

ZIP_DECODE_PREVLENSIZE(p, prevlensize);

ZIP_DECODE_LENGTH(p+prevlensize, encoding, lensize, len);

q= p + prevlensize +lensize;//传入1, 代表要跳过一个元素, 比如: 查找key时,跳过1个v,然后继续迭代//跳过了n个元素后,再从此开始key的比对过程

if (skipcnt == 0) {/*Compare current entry with specified entry*/

//针对不同的编码使用不同的比较方式

if(ZIP_IS_STR(encoding)) {//找到相应的元素,直接返回 p 指针

if (len == vlen && memcmp(q, vstr, vlen) == 0) {returnp;

}

}else{/*Find out if the searched field can be encoded. Note that

* we do it only the first time, once done vencoding is set

* to non-zero and vll is set to the integer value.*/

if (vencoding == 0) {if (!zipTryEncoding(vstr, vlen, &vll, &vencoding)) {/*If the entry can't be encoded we set it to

* UCHAR_MAX so that we don't retry again the next

* time.*/vencoding=UCHAR_MAX;

}/*Must be non-zero by now*/assert(vencoding);

}/*Compare current entry with specified entry, do it only

* if vencoding != UCHAR_MAX because if there is no encoding

* possible for the field it can't be a valid integer.*/

if (vencoding !=UCHAR_MAX) {long long ll =zipLoadInteger(q, encoding);if (ll ==vll) {returnp;

}

}

}/*Reset skip count*/

//查找一次,跳过skip次

skipcnt =skip;

}else{/*Skip entry*/skipcnt--;

}/*Move to next entry*/p= q +len;

}returnNULL;

}//ziplist.c, 添加value到ziplist中//zl:ziplist实例, p:要插入的key字串, s:要插入的value字串, len:要插入的value的长度/*Insert an entry at "p".*/unsignedchar *ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned intslen) {return__ziplistInsert(zl,p,s,slen);

}/*Insert item at "p".*/

static unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned intslen) {

size_t curlen=intrev32ifbe(ZIPLIST_BYTES(zl)), reqlen;

unsignedint prevlensize, prevlen = 0;

size_t offset;int nextdiff = 0;

unsignedchar encoding = 0;long long value = 123456789; /*initialized to avoid warning. Using a value

that is easy to see if for some reason

we use it uninitialized.*/zlentry tail;/*Find out prevlen for the entry that is inserted.*/

if (p[0] !=ZIP_END) {

ZIP_DECODE_PREVLEN(p, prevlensize, prevlen);

}else{

unsignedchar *ptail =ZIPLIST_ENTRY_TAIL(zl);if (ptail[0] !=ZIP_END) {

prevlen=zipRawEntryLength(ptail);

}

}/*See if the entry can be encoded*/

if (zipTryEncoding(s,slen,&value,&encoding)) {/*'encoding' is set to the appropriate integer encoding*/reqlen=zipIntSize(encoding);

}else{/*'encoding' is untouched, however zipEncodeLength will use the

* string length to figure out how to encode it.*/reqlen=slen;

}/*We need space for both the length of the previous entry and

* the length of the payload.*/

//加上prevlen,encoding,slen 的长度,以计算value的存放位置

reqlen +=zipPrevEncodeLength(NULL,prevlen);

reqlen+=zipEncodeLength(NULL,encoding,slen);/*When the insert position is not equal to the tail, we need to

* make sure that the next entry can hold this entry's length in

* its prevlen field.*/nextdiff= (p[0] != ZIP_END) ? zipPrevLenByteDiff(p,reqlen) : 0;/*Store offset because a realloc may change the address of zl.*/

//存储当前偏移位置,以便在扩容之后,还能找到相应位置//p = p -zl + zl

offset = p-zl;

zl= ziplistResize(zl,curlen+reqlen+nextdiff);

p= zl+offset;/*Apply memory move when necessary and update tail offset.*/

if (p[0] !=ZIP_END) {/*Subtract one because of the ZIP_END bytes*/

//字符拷贝

memmove(p+reqlen,p-nextdiff,curlen-offset-1+nextdiff);/*Encode this entry's raw length in the next entry.*/zipPrevEncodeLength(p+reqlen,reqlen);/*Update offset for tail*/ZIPLIST_TAIL_OFFSET(zl)=intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+reqlen);/*When the tail contains more than one entry, we need to take

* "nextdiff" in account as well. Otherwise, a change in the

* size of prevlen doesn't have an effect on the *tail* offset.*/zipEntry(p+reqlen, &tail);if (p[reqlen+tail.headersize+tail.len] !=ZIP_END) {

ZIPLIST_TAIL_OFFSET(zl)=intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+nextdiff);

}

}else{/*This element will be the new tail.*/ZIPLIST_TAIL_OFFSET(zl)= intrev32ifbe(p-zl);

}/*When nextdiff != 0, the raw length of the next entry has changed, so

* we need to cascade the update throughout the ziplist*/

if (nextdiff != 0) {//如果本次更新后数据位置变化,则需要更新后续的元素位置

offset = p-zl;

zl= __ziplistCascadeUpdate(zl,p+reqlen);

p= zl+offset;

}/*Write the entry*/

//将 value 写入 p 中, 即写入了 ziplist 中

p +=zipPrevEncodeLength(p,prevlen);

p+=zipEncodeLength(p,encoding,slen);if(ZIP_IS_STR(encoding)) {

memcpy(p,s,slen);

}else{

zipSaveInteger(p,value,encoding);

}

ZIPLIST_INCR_LENGTH(zl,1);returnzl;

}//另外,如果没有旧的元素值时,直接在hash表的末尾添加对应的field->value 即可//ziplist.c, 在尾部进行添加元素,没有许多的情况要考虑,但是代码完全复用 __ziplistInsert()

unsigned char *ziplistPush(unsigned char *zl, unsigned char *s, unsigned int slen, int where) {

unsignedchar *p;

p= (where == ZIPLIST_HEAD) ?ZIPLIST_ENTRY_HEAD(zl) : ZIPLIST_ENTRY_END(zl);return__ziplistInsert(zl,p,s,slen);

}

鉴于插入过程稍微复杂,咱们画个图重新理一下思路:

看起来没ziplist好像没那么简单呢,为啥还要搞这么复杂呢?其实以上代码,仅是在人看来复杂,对机器来说就是更多的移位计算操作,多消耗点cpu就换来了空间上的节省,是可以的。软件本身的复杂性带来了效益,是软件的价值体现,所以,并非所有的东西都是简单即美。

接下来,我们来看一下使用 HT 的编码又如何存储field->value呢?

//3.2. OBJ_ENCODING_HT 的 field -> value 的添加

if (o->encoding ==OBJ_ENCODING_HT) {//hash 表中查找对应的 field

dictEntry *de = dictFind(o->ptr,field);if(de) {

sdsfree(dictGetVal(de));//hset 时使用 HASH_SET_COPY, 所以直接使用 sdsdup() 即可

if (flags &HASH_SET_TAKE_VALUE) {

dictGetVal(de)=value;

value=NULL;

}else{

dictGetVal(de)=sdsdup(value);

}

update= 1;

}else{//新增 field -> value

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

}//添加到 hash 表中,前些篇章讲解过,大概就是计算hash,放入v的过程

dictAdd(o->ptr,f,v);

}

}

如此看来,OBJ_ENCODING_HT 的实现反而简单了哦。

总结下 hash的插入过程,hash 初始创建时都是使用ziplist 进行容纳元素的,在特定情况下会触发 ziplist 为 ht 的编码方式, 比如:

1. hset时自身的参数大于设置值(默认: 64)时直接转换 ziplist -> ht;

2. hash表的元素数量大于设置值(默认: 512)时转换 ziplist -> ht;

这么设计的原因是,元素较少且占用空间较小时,使用ziplist会节省空间,且时间消耗与hash表相关并不大,所以 ziplist 是优先的选择了。但是大量数据还是必须要使用hash表存储的。

二、hmset 批量添加元素

hset 和 hmset 在实现上基本如出一辙,所以简单瞅瞅就得了。

//t_hash.c, hmset key f1 v1 f2 v2

void hmsetCommand(client *c) {inti;

robj*o;//参数个数检查,必定是2n

if ((c->argc % 2) == 1) {

addReplyError(c,"wrong number of arguments for HMSET");return;

}//插入方式与 hset 一毛一样,差别在于批量插入时,会循环向 key-hash表中添加field->value

if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;

hashTypeTryConversion(o,c->argv,2,c->argc-1);//循环insert

for (i = 2; i < c->argc; i += 2) {

hashTypeSet(o,c->argv[i]->ptr,c->argv[i+1]->ptr,HASH_SET_COPY);

}

addReply(c, shared.ok);

signalModifiedKey(c->db,c->argv[1]);

notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id);

server.dirty++;

}

三、hget 获取某字段值

这种命令的时间复杂度都是 O(1), 所以一般是简单至上。

//t_hash.c

void hgetCommand(client *c) {

robj*o;//查找key, 不存在或者类型不一致则直接返回

if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||checkType(c,o,OBJ_HASH))return;//基于o, 返回 field 对应的元素值即可

addHashFieldToReply(c, o, c->argv[2]->ptr);

}//t_hash.c

static void addHashFieldToReply(client *c, robj *o, sds field) {intret;if (o ==NULL) {

addReply(c, shared.nullbulk);return;

}if (o->encoding ==OBJ_ENCODING_ZIPLIST) {

unsignedchar *vstr =NULL;

unsignedint vlen =UINT_MAX;long long vll =LLONG_MAX;//基于 ziplist,

ret = hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll);if (ret < 0) {//响应为空

addReply(c, shared.nullbulk);

}else{//添加到输出缓冲

if(vstr) {

addReplyBulkCBuffer(c, vstr, vlen);

}else{

addReplyBulkLongLong(c, vll);

}

}

}else if (o->encoding ==OBJ_ENCODING_HT) {//hash 表类型则查找 hash 表即可

sds value =hashTypeGetFromHashTable(o, field);//添加到输出缓冲

if (value ==NULL)//响应为空

addReply(c, shared.nullbulk);elseaddReplyBulkCBuffer(c, value, sdslen(value));

}else{

serverPanic("Unknown hash encoding");

}

}//t_hash.c, 从 ziplist 中查找 field 值/*Get the value from a ziplist encoded hash, identified by field.

* Returns -1 when the field cannot be found.*/

int hashTypeGetFromZiplist(robj *o, sds field,

unsignedchar **vstr,

unsignedint *vlen,long long *vll)

{

unsignedchar *zl, *fptr = NULL, *vptr =NULL;intret;

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);if (fptr !=NULL) {/*Grab pointer to the value (fptr points to the field)*/vptr=ziplistNext(zl, fptr);

serverAssert(vptr!=NULL);

}

}if (vptr !=NULL) {

ret=ziplistGet(vptr, vstr, vlen, vll);

serverAssert(ret);return 0;

}return -1;

}//t_hash.c, 从hash表中查找 field 字段的值/*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.*/sds hashTypeGetFromHashTable(robj*o, sds field) {

dictEntry*de;

serverAssert(o->encoding ==OBJ_ENCODING_HT);

de= dictFind(o->ptr, field);if (de == NULL) returnNULL;returndictGetVal(de);

}

四、hmget 批量获取值

与hget如出一辙。

//t_hash.c

void hmgetCommand(client *c) {

robj*o;inti;/*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.*/o= lookupKeyRead(c->db, c->argv[1]);if (o != NULL && o->type !=OBJ_HASH) {

addReply(c, shared.wrongtypeerr);return;

}//循环输出值

addReplyMultiBulkLen(c, c->argc-2);for (i = 2; i < c->argc; i++) {

addHashFieldToReply(c, o, c->argv[i]->ptr);

}

}

五、hgetall 获取所有hash的kv

hgetall 和 hmget 方式稍微有点不一样,原因是为了让 hkeysCommand/hvalsCommand 进行复用。

//t_hash.c

void hgetallCommand(client *c) {

genericHgetallCommand(c,OBJ_HASH_KEY|OBJ_HASH_VALUE);

}void genericHgetallCommand(client *c, intflags) {

robj*o;

hashTypeIterator*hi;int multiplier = 0;int length, count = 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);

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

}static void addHashIteratorCursorToReply(client *c, hashTypeIterator *hi, intwhat) {if (hi->encoding ==OBJ_ENCODING_ZIPLIST) {

unsignedchar *vstr =NULL;

unsignedint vlen =UINT_MAX;long long vll =LLONG_MAX;

hashTypeCurrentFromZiplist(hi, what,&vstr, &vlen, &vll);if(vstr)

addReplyBulkCBuffer(c, vstr, vlen);elseaddReplyBulkLongLong(c, vll);

}else if (hi->encoding ==OBJ_ENCODING_HT) {

sds value=hashTypeCurrentFromHashTable(hi, what);

addReplyBulkCBuffer(c, value, sdslen(value));

}else{

serverPanic("Unknown hash encoding");

}

}

六、hincrby 增加x某字段

hincrby key field 1

//t_hash.c,

void hincrbyCommand(client *c) {long longvalue, incr, oldvalue;

robj*o;

sdsnew;

unsignedchar *vstr;

unsignedintvlen;//解析增加字段值到 incr 中

if (getLongLongFromObjectOrReply(c,c->argv[3],&incr,NULL) != C_OK) return;//获取原值或者设置为0

if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;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*/}else{

value= 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;

}//将相加后的值重置设置回hash表中

value +=incr;new =sdsfromlonglong(value);

hashTypeSet(o,c->argv[2]->ptr,new,HASH_SET_TAKE_VALUE);

addReplyLongLong(c,value);

signalModifiedKey(c->db,c->argv[1]);

notifyKeyspaceEvent(NOTIFY_HASH,"hincrby",c->argv[1],c->db->id);

server.dirty++;

}

七、hdel 删除某字段

hdel key field

//t_hash.c,

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;//循环删除给定字段列表

for (j = 2; j < c->argc; j++) {if (hashTypeDelete(o,c->argv[j]->ptr)) {

deleted++;//当没有任何元素后,直接将key删除

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)

notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1],

c->db->id);

server.dirty+=deleted;

}

addReplyLongLong(c,deleted);

}//具体删除 field, 同样区分编码类型,不同处理逻辑/*Delete an element from a hash.

* Return 1 on deleted and 0 on not found.*/

int hashTypeDelete(robj *o, sds field) {int deleted = 0;if (o->encoding ==OBJ_ENCODING_ZIPLIST) {

unsignedchar *zl, *fptr;

zl= o->ptr;

fptr=ziplistIndex(zl, ZIPLIST_HEAD);if (fptr !=NULL) {//ziplist 删除,依次删除 field, value

fptr = ziplistFind(fptr, (unsigned char*)field, sdslen(field), 1);if (fptr !=NULL) {//ziplistDelete 为原地删除,所以只要调用2次,即把kv删除

zl = ziplistDelete(zl,&fptr);

zl= ziplistDelete(zl,&fptr);

o->ptr =zl;

deleted= 1;

}

}

}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.*/

//hash 删除的,可能需要进行缩容操作,这种处理方法相对特殊些

if (htNeedsResize(o->ptr)) dictResize(o->ptr);

}

}else{

serverPanic("Unknown hash encoding");

}returndeleted;

}//server.c, 是否需要进行 resize

int htNeedsResize(dict *dict) {long longsize, used;

size=dictSlots(dict);

used=dictSize(dict);//HASHTABLE_MIN_FILL=10, 即使用率小于 1/10 时,可以进行缩容操作了

return (size && used && size > DICT_HT_INITIAL_SIZE &&(used*100/size

}

至此,整个hash数据结构的解析算是完整了。总体来说,hash由两种数据结构承载,ziplist在小数据量时使用,稍微复杂,但对于昂贵的内存来说是值得的。hash表在数据量大时使用,容易理解。通过本文的讲解,相信可以验证了你对redis hash 的实现的猜想了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值