Redis做爲nosql數據庫,kv string型數據的支持是最基礎的,可是若是僅有kv的操做,也不至於有redis的成功。(memcache就是個例子)redis
Redis除了string, 還有hash,list,set,zset。sql
因此,咱們就來看看hash的相關操做實現吧。數據庫
首先,咱們從做用上理解hash存在的意義:Redis hash 是一個 string 類型的 field 和 value 的映射表,hash 特別適合用於存儲對象。從另外一個方面來講是,hash能夠聚合不少相似的屬性,這是string中難以實現的。數據結構
因此,整體來講,hash的命令與string的命令差不太多。其操做手冊以下:app
1> hdel 命令:刪除一個或多個哈希表字段
格式:HDEL key field2 [field2]
返回值:被成功刪除字段的數量,不包括被忽略的字段。curl2> hexists 命令:查看哈希表 key 中,指定的字段是否存在
格式:HEXISTS key field
返回值:若是哈希表含有給定字段,返回 1 。 若是哈希表不含有給定字段,或 key 不存在,返回 0 。nosql3> hget 命令:獲取存儲在哈希表中指定字段的值
格式:HGET key field
返回值:返回給定字段的值。若是給定的字段或 key 不存在時,返回 nil 。ide4> hgetall 命令:獲取在哈希表中指定 key 的全部字段和值
格式:HGETALL key
返回值:以列表形式返回哈希表的字段及字段值。 若 key 不存在,返回空列表。ui5> hincrby 命令:爲哈希表 key 中的指定字段的整數值加上增量 increment
格式:HINCRBY key field increment
返回值:執行 HINCRBY 命令以後,哈希表中字段的值。this6> 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 struct zlentry { unsigned int prevrawlensize, prevrawlen; unsigned int lensize, len; unsigned int headersize; unsigned char encoding; unsigned char *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 struct dict { dictType *type; void *privdata; dictht ht[2]; long rehashidx; /* rehashing not in progress if rehashidx == -1 */ unsigned long iterators; /* number of iterators currently running */ } dict; typedef struct dictht { dictEntry **table; unsigned long size; unsigned long sizemask; unsigned long used; } dictht; typedef struct dictEntry { void *key; void *val; struct dictEntry *next; } dictEntry;
1、hset 設置單個 field -> value
「增刪改查」中的「增改」 就是它了。
// t_hash.c, set key field value void hsetCommand(client *c) { int update; 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); return NULL; } } return o; } // object.c, 建立hashObject, 以 ziplist 形式建立 robj *createHashObject(void) { unsigned char *zl = ziplistNew(); robj *o = createObject(OBJ_HASH, zl); o->encoding = OBJ_ENCODING_ZIPLIST; return o; } // ziplist.c static unsigned char *createList() { unsigned char *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); return zl; } // 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, int end) { int i; 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, int enc) { 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, int enc) { serverAssert(o->encoding == OBJ_ENCODING_ZIPLIST); if (enc == OBJ_ENCODING_ZIPLIST) { /* Nothing to do... */ } else if (enc == OBJ_ENCODING_HT) { hashTypeIterator *hi; dict *dict; int ret; // 迭代器建立 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) { unsigned char *zl; unsigned char *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) 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 */ 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.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. */ unsigned char *ziplistIndex(unsigned char *zl, int index) { unsigned char *p; unsigned int 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. */ 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) { return NULL; } // 當前指針偏移當前元素長度(根據ziplist協議),即到下一元素指針位置 p += zipRawEntryLength(p); if (p[0] == ZIP_END) { return NULL; } return p; } /* Return the total number of bytes used by the entry pointed to by 'p'. */ static unsigned int zipRawEntryLength(unsigned char *p) { unsigned int prevlensize, 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, 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); } /* 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, int what, unsigned char **vstr, unsigned int *vlen, long long *vll) { int ret; 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. */ unsigned int 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] < ZIP_BIGLEN) { \ (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) < ZIP_STR_MASK) { \ // 0 << 6 =0 // 具體解析以下代碼, 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
添加kv到對應的key實例中:
// 3. 添加kv到 hash表中, 稍微複雜 // t_hash.c, 作變動到hash表中 int hashTypeSet(robj *o, sds field, sds value, int flags) { int update = 0; // 針對ziplist 的添加, 與 ht 編碼的添加, 天然是分別處理 if (o->encoding == OBJ_ENCODING_ZIPLIST) { unsigned char *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); return update; } // 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. */ unsigned char *ziplistFind(unsigned char *p, unsigned char *vstr, unsigned int vlen, unsigned int skip) { int skipcnt = 0; unsigned char vencoding = 0; long long vll = 0; while (p[0] != ZIP_END) { unsigned int prevlensize, encoding, lensize, len; unsigned char *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) { return p; } } 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) { return p; } } } /* Reset skip count */ // 查找一次,跳過skip次 skipcnt = skip; } else { /* Skip entry */ skipcnt--; } /* Move to next entry */ p = q + len; } return NULL; } // ziplist.c, 添加value到ziplist中 // zl:ziplist實例, p:要插入的key字串, s:要插入的value字串, len:要插入的value的長度 /* Insert an entry at "p". */ unsigned char *ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) { return __ziplistInsert(zl,p,s,slen); } /* Insert item at "p". */ static unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) { size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), reqlen; unsigned int prevlensize, prevlen = 0; size_t offset; int nextdiff = 0; unsigned char 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 { unsigned char *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); return zl; } // 另外,若是沒有舊的元素值時,直接在hash表的末尾添加對應的field->value 便可 // ziplist.c, 在尾部進行添加元素,沒有許多的狀況要考慮,可是代碼徹底複用 __ziplistInsert() unsigned char *ziplistPush(unsigned char *zl, unsigned char *s, unsigned int slen, int where) { unsigned char *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表存儲的。
2、hmset 批量添加元素
hset 和 hmset 在實現上基本一模一樣,因此簡單瞅瞅就得了。
// t_hash.c, hmset key f1 v1 f2 v2 void hmsetCommand(client *c) { int i; 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++; }
3、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) { int ret; if (o == NULL) { addReply(c, shared.nullbulk); return; } if (o->encoding == OBJ_ENCODING_ZIPLIST) { unsigned char *vstr = NULL; unsigned int 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); else addReplyBulkCBuffer(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, 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); 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) return NULL; return dictGetVal(de); }
4、hmget 批量獲取值
與hget一模一樣。
// t_hash.c 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. */ 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); } }
5、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, int flags) { 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, int what) { if (hi->encoding == OBJ_ENCODING_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) { sds value = hashTypeCurrentFromHashTable(hi, what); addReplyBulkCBuffer(c, value, sdslen(value)); } else { serverPanic("Unknown hash encoding"); } }
6、hincrby 增長x某字段
hincrby key field 1
// t_hash.c, void hincrbyCommand(client *c) { long long value, incr, oldvalue; robj *o; sds new; unsigned char *vstr; unsigned int vlen; // 解析增長字段值到 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++; }
7、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) { unsigned char *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"); } return deleted; } // server.c, 是否須要進行 resize int htNeedsResize(dict *dict) { long long size, used; size = dictSlots(dict); used = dictSize(dict); // HASHTABLE_MIN_FILL=10, 即便用率小於 1/10 時,能夠進行縮容操做了 return (size && used && size > DICT_HT_INITIAL_SIZE && (used*100/size < HASHTABLE_MIN_FILL)); }
至此,整個hash數據結構的解析算是完整了。整體來講,hash由兩種數據結構承載,ziplist在小數據量時使用,稍微複雜,但對於昂貴的內存來講是值得的。hash表在數據量大時使用,容易理解。經過本文的講解,相信能夠驗證了你對redis hash 的實現的猜測了。