redis は KV タイプのインメモリ データベースです。データベース ストレージの核となるのはハッシュ テーブルです。select コマンドを実行して、 storage db, all 操作はすべてハッシュ テーブルに基づいています. ハッシュ データ構造と redis の実装については以下で分析します.
/*Hash表一个节点包含Key,Value数据对 */ typedef struct dictEntry { void *key; union { void *val; uint64_t u64; int64_t s64; double d; } v; struct dictEntry *next; /* 指向下一个节点, 链接表的方式解决Hash冲突 */ } dictEntry; /* 存储不同数据类型对应不同操作的回调函数 */ typedef struct dictType { unsigned int (*hashFunction)(const void *key); void *(*keyDup)(void *privdata, const void *key); void *(*valDup)(void *privdata, const void *obj); int (*keyCompare)(void *privdata, const void *key1, const void *key2); void (*keyDestructor)(void *privdata, void *key); void (*valDestructor)(void *privdata, void *obj); } dictType; typedef struct dictht { dictEntry **table; /* dictEntry*数组,Hash表 */ unsigned long size; /* Hash表总大小 */ unsigned long sizemask; /* 计算在table中索引的掩码, 值是size-1 */ unsigned long used; /* Hash表已使用的大小 */ } dictht; typedef struct dict { dictType *type; void *privdata; dictht ht[2]; /* 两个hash表,rehash时使用*/ long rehashidx; /* rehash的索引, -1表示没有进行rehash */ int iterators; /* */ } dict;
# dictのht[2]には2つのハッシュテーブルがあり、初めてデータを格納するときは、 ht[0] 最小サイズ 4 のハッシュ テーブルが作成されます ht[0] の size と used が一致すると、ht[1] の dict に size*2 のハッシュ テーブルが作成されます、ht[は直接使用されません。0] のデータが ht[0] にコピーされ、プログレッシブ リハッシュが実行されます。つまり、後続の操作 (検索、設定、取得など) でゆっくりとコピーされます。新しく追加された要素は将来 ht[.0] に追加されるため、ht[1] がいっぱいになると、ht[0] 内のすべてのデータが確実に ht[1] にコピーされます。 ##4. ハッシュ テーブルを作成する
ハッシュ テーブルに要素を追加するには、まずスペースが十分であるかどうかを判断し、次にキーに対応するハッシュ値を計算し、追加する必要があるキーと値をテーブルに追加します。 .
dict *dictCreate(dictType *type, void *privDataPtr) { /*分配内存*/ dict *d = zmalloc(sizeof(*d)); /*初始化操作*/ _dictInit(d,type,privDataPtr); return d; }
要素の検索手順は、まずハッシュ値を計算し、ht[0]とht[1]のインデックス位置を計算して検索します。 ##
int dictAdd(dict *d, void *key, void *val) { /*添加入hash表中, 返回新添加元素的实体结构体*/ dictEntry *entry = dictAddRaw(d,key); if (!entry) return DICT_ERR; /*元素val值放入元素实体结构中*/ dictSetVal(d, entry, val); return DICT_OK; } /* *添加元素实体函数 */ dictEntry *dictAddRaw(dict *d, void *key) { int index; dictEntry *entry; dictht *ht; if (dictIsRehashing(d)) _dictRehashStep(d); /*根据key值计算新元素在hash表中的索引, 返回-1则表示元素已存在, 直接返回NULL*/ if ((index = _dictKeyIndex(d, key)) == -1) return NULL; /*如果在进行rehash过程,则新元素添加到ht[1]中, 否则添加到ht[0]中 */ ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0]; entry = zmalloc(sizeof(*entry)); entry->next = ht->table[index]; ht->table[index] = entry; ht->used++; /*设置元素key*/ dictSetKey(d, entry, key); return entry; } /* *计算索引的函数 */ static int _dictKeyIndex(dict *d, const void *key) { unsigned int h, idx, table; dictEntry *he; /* 判断hash表是否空间足够, 不足则需要扩展 */ if (_dictExpandIfNeeded(d) == DICT_ERR) return -1; /* 计算key对应的hash值 */ h = dictHashKey(d, key); for (table = 0; table <= 1; table++) { /*计算索引*/ idx = h & d->ht[table].sizemask; /*遍历冲突列表, 判断需要查找的key是否已经在冲突列表中*/ he = d->ht[table].table[idx]; while(he) { if (dictCompareKeys(d, key, he->key)) return -1; he = he->next; } if (!dictIsRehashing(d)) break; } return idx; } /* *判断hash表是否需要扩展空间 */ static int _dictExpandIfNeeded(dict *d) { /*redis的rehash采用的渐进式hash, rehash时分配了原来两倍的内存空间, 在rehash阶段空间必定够用*/ if (dictIsRehashing(d)) return DICT_OK; /* hash表是空的需要初始化空间, 默认是4*/ if (d->ht[0].size == 0) return dictExpand(d, DICT_HT_INITIAL_SIZE); /* 已使用空间满足不了设置的条件*/ if (d->ht[0].used >= d->ht[0].size && (dict_can_resize || d->ht[0].used/d->ht[0].size > dict_force_resize_ratio)) { /*扩展空间, 使用空间的两倍*/ return dictExpand(d, d->ht[0].used*2); } return DICT_OK; } /* *扩展空间或者初始化hash表空间 */ int dictExpand(dict *d, unsigned long size) { dictht n; /* 对需要分配大小圆整为2的倍数 */ unsigned long realsize = _dictNextPower(size); /* 如果空间足够则表明调用错误 */ if (dictIsRehashing(d) || d->ht[0].used > size) return DICT_ERR; n.size = realsize; n.sizemask = realsize-1; n.table = zcalloc(realsize*sizeof(dictEntry*)); n.used = 0; /*hash表为空初始化hash表*/ if (d->ht[0].table == NULL) { d->ht[0] = n; return DICT_OK; } /*新分配的空间放入ht[1], 后面一步一步进行rehash*/ d->ht[1] = n; d->rehashidx = 0; return DICT_OK; }
dictEntry *dictFind(dict *d, const void *key) { dictEntry *he; unsigned int h, idx, table; if (d->ht[0].size == 0) return NULL; /*如果正在进行rehash, 执行一次rehash*/ if (dictIsRehashing(d)) _dictRehashStep(d); h = dictHashKey(d, key); /*由于可能正在rehash, 因此要从ht[0]和ht[1]中分别进行查找, 找不到返回NULL*/ for (table = 0; table <= 1; table++) { idx = h & d->ht[table].sizemask; he = d->ht[table].table[idx]; /*遍历冲突列表查找元素*/ while(he) { if (dictCompareKeys(d, key, he->key)) return he; he = he->next; } if (!dictIsRehashing(d)) return NULL; } return NULL; }
redis の Ziplist データ構造を参照できます。hash_max_ziplist_entries と hash_max_ziplist_value の値は、しきい値として使用されます。hash_max_ziplist_entries は、ziplist 内の要素の数がこの値を超えると、この値を超える必要があることを意味します。 dict 構造に変換; hash_max_ziplist_value ziplist 内のデータ長がこの値を超えると、dict 構造に変換する必要があることを示します。
以上がRedisハッシュの実装方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。