redis哈希槽,c++ 哈希_Redis源碼解析十一--Hash鍵實現Redis 哈希鍵命令實現(t_hash)

 2023-10-05 阅读 26 评论 0

摘要:Redis 哈希鍵命令實現(t_hash)1. 哈希命令介紹Redis 所有哈希命令如下表所示:Redis 哈希命令詳解2. 哈希類型的實現之前在redis對象系統源碼剖析和注釋中提到,一個哈希類型的對象的編碼有兩種,分別是OBJ_ENCODING_ZIPLIST和OBJ_ENCODING_HT。redis 壓縮列

Redis 哈希鍵命令實現(t_hash)

1. 哈希命令介紹

Redis 所有哈希命令如下表所示:Redis 哈希命令詳解

538600855d6d8087d7f677f9f7b3a6d2.png

2. 哈希類型的實現

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

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

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

8f16bb0b8ff0cb8ecd16818ed7d8d791.png

但是默認創建的哈希類型的對象編碼為OBJ_ENCODING_ZIPLIST,OBJ_ENCODING_HT類型編碼是通過達到配置的閾值條件后,進行轉換得到的。

閾值條件為:

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

一個哈希對象的結構定義如下:

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

例如,我們創建一個 user:info 哈希鍵,有三個字段,分別是name,sex,passwd。

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

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

根據這些信息的大小,redis應該為其創建一個編碼為OBJ_ENCODING_ZIPLIST的哈希對象。如下圖所示:

5f60729568884d7853e51434e57071ef.png

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

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

fd11996115b40fa129bd67a9653099de.png

和列表數據類型一樣,哈希數據類型基于ziplist和hash table進行封裝,實現了哈希數據類型的接口:

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

這些函數接口的注釋請上github查看:哈希類型函數接口的注釋

3. 哈希類型的迭代器

和列表類型一樣,哈希數據類型也實現自己的迭代器,而且也是基于ziplist和字典結構的迭代器封裝而成。

typedef struct { robj *subject; // 哈希類型迭代器所屬的哈希對象 int encoding; // 哈希對象的編碼類型 // 用ziplist編碼 unsigned char *fptr, *vptr; // 指向當前的key和value節點的地址,ziplist類型編碼時使用 // 用于字典編碼 dictIterator *di; // 迭代HT類型的哈希對象時的字典迭代器 dictEntry *de; // 指向當前的哈希表節點} hashTypeIterator;#define OBJ_HASH_KEY 1 // 哈希鍵#define OBJ_HASH_VALUE 2 // 哈希值

3. 哈希類型的迭代器

和列表類型一樣,哈希數據類型也實現自己的迭代器,而且也是基于ziplist和字典結構的迭代器封裝而成。

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

4. 哈希命令的實現

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

具體所有注釋請看:哈希類型命令的注釋

  • Hgetall一類命令的底層實現

HKEYS、HVALS、HGETALL

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

Redis 3.2版本以上新加入的

void hstrlenCommand(client *c) { robj *o; // 以寫操作取出哈希對象,若失敗,或取出的對象不是哈希類型的對象,則發送0后直接返回 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,o,OBJ_HASH)) return; // 發送field對象的值的長度給client addReplyLongLong(c,hashTypeGetValueLength(o,c->argv[2]));}
  • HDEL命令實現
void hdelCommand(client *c) { robj *o; int j, deleted = 0, keyremoved = 0; // 以寫操作取出哈希對象,若失敗,或取出的對象不是哈希類型的對象,則發送0后直接返回 if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,o,OBJ_HASH)) return; // 遍歷所有的字段field for (j = 2; j < c->argc; j++) { // 從哈希對象中刪除當前字段 if (hashTypeDelete(o,c->argv[j])) { deleted++; //更新刪除的個數 // 如果哈希對象為空,則刪除該對象 if (hashTypeLength(o) == 0) { dbDelete(c->db,c->argv[1]); keyremoved = 1; //設置刪除標志 break; } } } // 只要刪除了字段 if (deleted) { // 發送信號表示鍵被改變 signalModifiedKey(c->db,c->argv[1]); // 發送"hdel"事件通知 notifyKeyspaceEvent(NOTIFY_HASH,"hdel

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/1/116566.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息