Redisの8 - - 符号化された辞書オブジェクトのソースコードを読み取るために大きなビン続い

辞書は、キーと値のペアのために記憶さ抽象データ構造です。そのC言語のデータ構造組み込みの辞書がありませんので、Redisのは、達成するために、独自の辞書を構築します。

Redisのでは、基礎となるデータベースを達成するために辞書を使用することです。データベースのCURD操作は、辞書の操作に基づいて構築されています。

添加を表すために使用されるデータベース、基礎となる辞書またはハッシュキーの実装に加えて。多くの主要な比較を備える場合には、ハッシュキー、または重要な要素の比較的長い文字列が、辞書には、基礎となるハッシュキーの実装としてのRedisを適応させます。

1つの実現辞書

基本となる実装としてハッシュテーブルを使用してRedisの辞書。複数のノードを有することができ、ハッシュテーブル、ハッシュテーブル、ハッシュテーブル上の各ノードは、キー値ペアの辞書を保持します。

1.1ハッシュテーブル

辞書で使用Redisのハッシュテーブル構造:

typedef struct dictht {
    dictEntry **table;      // 哈希表数组
    unsigned long size;     // 哈希表大小
    unsigned long sizemask; // 哈希表大小掩码,用来计算索引
    unsigned long used;     // 哈希表现有节点的数量
} dictht;
  • 属性テーブルは配列です。アレイ内の各要素は、ポインタdictEntry構造であり、各構造dictEntryはキーと値のペアを保持しています。
  • サイズ属性は、それは、テーブルの配列のサイズで、ハッシュテーブルのサイズを記録します。
  • 使用されるプロパティは、ノード(キーと値のペア)の数があるハッシュテーブルを記録します。
  • Sizemask属性値がサイズ1の総数に等しく、ハッシュキーを用いて決定されるプロパティの値は、テーブルのインデックス・アレイ上に配置されるべきです。

図1は、ハッシュテーブル4のサイズのためにブランクを示します。

ハッシュ・テーブルのサイズが空である4

1.2ハッシュ・テーブル・ノード

ハッシュテーブルノードがdictEntry構造を使用して、各dictEntry構造はキーと値のペアで保存されて言いました:

typedef struct dictEntry {
    void *key;              // 键
    union {
        void *val;          // 值类型之指针
        uint64_t u64;       // 值类型之无符号整型
        int64_t s64;        // 值类型之有符号整型
        double d;           // 值类型之浮点型
    } v;                    // 值
    struct dictEntry *next; // 指向下个哈希表节点,形成链表
} dictEntry;
  • キー属性は、キーを保持し、vは、保存属性値です。
  • 次のプロパティは、ハッシュテーブル内の別のノードへのポインタです。このポインタは、キーの競合を解決するために一緒に接続された同一の主要な問題のために複数のハッシュ値であることができます。

図2は、次のポインタにより、同じ2つのインデックスキーK1及びK0が互いに接続されている場合を示しています。

鍵k0とk1は互いに接続されています

辞書1.3

辞書の構造:

typedef struct dict {
    dictType *type; // 类型特定函数
    void *privdata; // 私有数据
    dictht ht[2];   // 哈希表(两个)
    long rehashidx; // 记录 rehash 进度的标志。值为 -1 表示 rehash 未进行
    int iterators;  // 当前正在迭代的迭代器数
} dict;

構造以下dictType:

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;

タイププロパティとprivdata特性が辞書の多状態のセットを作成するために、キーと値のペアの異なるタイプのためのものです。どこで:

  • type属性は、ポインタdictType構造であり、各機能クラスターdictType構造はキーと値のペアの特定のタイプの動作のために保持します。Redisのは、辞書を使用せずに特定の機能のために、異なるタイプを設定することができます。
  • privdataプロパティは、これらのタイプの特定の関数に渡されるオプションのパラメータが含まれています。

およびHTプロパティは、2つのハッシュテーブルを含むアレイです。通常の状況下では、唯一の辞書HTの使用は、[0]、[0]焼き直したHT時に[1]のみHTを用いています。

電流は、それが-1されていない場合は、現在の進捗焼き直しを記録しプロパティを、rehashidx、焼き直し。焼き直しは、心配しないでください何のためとして、それは後に詳述します。

図3は、辞書のない焼き直しではありません。

辞書の焼き直しません

2挿入アルゴリズム

辞書に新しいキーと値のペアを追加する場合、Redisのは、最初のキーインデックス値とハッシュ値を、インデックス値、新しいノードにキーと値のペアを含むハッシュテーブルのキーに基づいて算出します指定された配列インデックスのハッシュテーブル。次のようなアルゴリズムは次のようになります。

# 使用字典设置的哈希函数,计算 key 的哈希值
hash = dict->type->hashFunction(key);
# 使用哈希表的 sizemask 属性和哈希值,计算出索引值
# 根据不同情况,使用 ht[0] 或 ht[1]
index = hash & dict[x].sizemask;

図4  - 空の辞書
キーと値のペア[K0は、V0]辞書に追加された場合、図4に示すように、次の順序で挿入されています。

hash = dict-type->hashFunction(k0);
index = hash & dict->ht[0].sizemask; # 8 & 3 = 0

計算、[K0、V0]キーは、図5に示す位置にハッシュテーブルの配列インデックス0上に配置されるべきです。

図5  - 辞書K0-V0を追加します。

2.1キー違反

2つ以上のキーの数は上記と同じ配列インデックスのハッシュテーブルに割り当てられているがあるとき、私たちは信じて、これらの債券その紛争の建設

Redisのハッシュテーブル使用してチェーンアドレス法を建て競合を解決します。各ノードは、ハッシュテーブルの次のポインタを有し、ノードのハッシュテーブル複数の次のポインタを持つ単独でリンクされたリストを構成することができる、それが次のポインタへのリンクを単独でリンクされたリストの単一のインデックス内に複数のノードに割り当てられます。

栗のために、我々は次のポインタと、ここで、それゆえ[K2、V2]図6に示すハッシュテーブルのキー値ペア、及び2 K2の算出された指標値、及びK1の競合を望む、と仮定示すようにK1及びK2のノードは、互いに接続されます。

図6  - ハッシュテーブルは、2つのキーと値のペアが含まれています

図7  - 競合を解決するためにK1およびK2リストを使用します

そして、プログレッシブ。3焼き直しの焼き直し

随着对字典的操作,哈希表报错的键值对会逐渐增多或者减少,为了让哈希表的负载因子维持在一个合理的范围之内,当哈希表报错的键值对数量太多或者太少时,程序需要对哈希表进行相应的扩容或收缩。这个扩容或收缩的过程,我们称之为 rehash。

对于负载因子,可以通过以下公式计算得出:

# 负载因子 = 哈希表已保存节点数量 / 哈希表大小
load_factor = ht[0].used / ht[0].size;

3.1 哈希表的扩容与收缩

扩容

对于哈希表的扩容,源码如下:

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

当以下条件被满足时,程序会自动开始对哈希表执行扩展操作:

  • 服务器当前没有进行 rehash;
  • 哈希表已保存节点数量大于哈希表大小;
  • dict_can_resize 参数为 1,或者负载因子大于设定的比率(默认为 5);

收缩

哈希表的收缩,源码如下:

int htNeedsResize(dict *dict) {
    long long size, used;
    size = dictSlots(dict); // ht[2] 两个哈希表的大小之和
    used = dictSize(dict);  // ht[2] 两个哈希表已保存节点数量之和
    # DICT_HT_INITIAL_SIZE 默认为 4,HASHTABLE_MIN_FILL 默认为 10。
    return (size > DICT_HT_INITIAL_SIZE &&
            (used*100/size < HASHTABLE_MIN_FILL));
}
void tryResizeHashTables(int dbid) {
    if (htNeedsResize(server.db[dbid].dict))
        dictResize(server.db[dbid].dict);
    if (htNeedsResize(server.db[dbid].expires))
        dictResize(server.db[dbid].expires);
}

当 ht[] 哈希表的大小之和大于 DICT_HT_INITIAL_SIZE(默认 4),且已保存节点数量与总大小之比小于 4,HASHTABLE_MIN_FILL(默认 10,也就是 10%),会对哈希表进行收缩操作。

3.2 rehash

扩容和收缩哈希表都是通过执行 rehash 操作来完成,哈希表执行 rehash 的步骤如下:

  1. 为字典的 ht[1] 哈希表分配空间,这个哈希表的空间大小取决于要执行的操作,以及 ht[0] 当前包含的键值对数量。
    1. 如果执行的是扩容操作,那么 ht[1] 的大小为**第一个大于等于 ht[0].usedx2 的 2^n。
    2. 如果执行的是收缩操作,那么 ht[1] 的大小为第一个大于等于 ht[0].used 的 2^n。
  2. 将保存在 ht[0] 中的所有键值对 rehash 到 ht[1] 上面:rehash 指的是重新计算键的哈希值和索引值,然后将键值对都迁移到 ht[1] 哈希表的指定位置上。
  3. 当 ht[0] 包含的所有键值对都迁移到 ht[1] 后,此时 ht[0] 变成空表,释放 ht[0],将 ht[1] 设置为 ht[0],并在 ht[1] 新创建一个空白哈希表,为下一次 rehash 做准备。

示例:

図8は - の辞書焼き直しを実行します
假设程序要对图 8 所示字典的 ht[0] 进行扩展操作,那么程序将执行以下步骤:
1)ht[0].used 当前的值为 4,那么 4*2 = 8,而 2^3 恰好是第一个大于等于 8 的,2 的 n 次方。所以程序会将 ht[1] 哈希表的大小设置为 8。图 9 是 ht[1] 在分配空间之后的字典。

辞書に割り当てられたHT1ハッシュテーブルスペース -  9

2)将 ht[0] 包含的四个键值对都 rehash 到 ht[1],如图 10。

図10  -  HT0すべてのキーと値のペアをHT1に移行されます

3)释放 ht[0],并将 ht[1] 设置为 ht[0],然后为 ht[1] 分配一个空白哈希表。如图 11:

図11  - フィールド焼き直しの完了後

至此,对哈希表的扩容操作执行完毕,程序成功将哈希表的大小从原来的 4 改为了 8。

3.3 渐进式 rehash

对于 Redis 的 rehash 而言,并不是一次性、集中式的完成,而是分多次、渐进式地完成,所以也叫渐进式 rehash

之所以采用渐进式的方式,其实也很好理解。当哈希表里保存了大量的键值对,要一次性的将所有键值对全部 rehash 到 ht[1] 里,很可能会导致服务器在一段时间内只能进行 rehash,不能对外提供服务。

因此,为了避免 rehash 对服务器性能造成影响,Redis 分多次、渐进式的将 ht[0] 里面的键值对 rehash 到 ht[1]。

渐进式 rehash 就用到了索引计数器变量 rehashidx,详细步骤如下:

  1. 为 ht[1] 分配空间,让字典同时持有 ht[0] 和 ht[1] 两个哈希表。
  2. 在字段中维持一个索引计数器变量 rehashidx,并将它的值设置为 0,表示开始 rehash。
  3. 在 rehash 期间,每次对字典执行 CURD 操作时,程序除了执行指定的操作外,还会将 ht[0] 哈希表在 rehashidx 索引上的所有键值对移动到 ht[1],当 rehash 完成后,程序将 rehashidx 的值加一。
  4. 辞書の操作で、最終的に点の時間で、すべてのキーと値のペアにHT [0] [1]、プログラム時間属性値がrehashidxするHTを焼き直しするであろう-1焼き直しが完了したことを示します。

プログレッシブ分割する唯一の方法で焼き直し、ルールは、作業は、このように集中焼き直しによって引き起こされる問題を回避する、辞書に等しくCURD操作を共有するために必要な鍵の計算を焼き直します。

また、焼き直し中の辞書には、削除、検索、更新、および他の操作は、2つのハッシュテーブル上で実行されます。例えば、辞書張で鍵を見つけるために、プログラムは今[0]内部を見つけるために、HTますが、見つからない場合は、[1]のルックアップにHTに行きます。

[0]キーが含まれている[1]、HTの不在[0] HTを確実にするために任意の操作を追加し、新しいキーと値のペアのみHTの保存に使用されることに注意だけで、数が増加しない減少rehashオペレーションは、最終的には空のテーブルになりました。

17から12は、完全な焼き直し漸進的なプロセスを示しています。

1)辞書を焼き直していませんでした

图 12 - 未进行 rehash 的字典

2)キーインデックス0対の焼き直しで

图 13 - rehash 索引 0 上的键值对

インデックスのキー3)焼き直しつのペアで

图 14 - rehash 索引 1 上的键值对

二対の上4)焼き直しインデックスキー

图 15 - rehash 索引 2 上的键值对

5)3組のインデックスキーが焼き直し

图 16 - rehash 索引 3 上的键值对

6)完成焼き直し

图 17 - rehash 执行完毕

概要

  1. フィールドは、広く様々な機能のために使用されているデータベースと、ハッシュキーを含む、Redisのを実施しました。
  2. 基礎となる実装として使用するハッシュテーブルのRedisの辞書、2つのハッシュテーブルを有し、各辞書のみ焼き直し使用される通常の使用。
  3. 競合キーを解決するために、チェーンのアドレスを使用してハッシュテーブル法は、それが同一のインデックスに複数のキーに割り当てられているが、単独でリンクされたリストに接続されます。
  4. ときに伸縮操作、プログレッシブ完全な焼き直しのためのハッシュテーブル。

おすすめ

転載: www.cnblogs.com/BeiGuo-FengGuang/p/11301070.html