Redisのハッシュ分布が不均一な場合の対処方法

序文

Redisはキーと値のペアのデータベースであり、そのキーはハッシュを介して保存されます。Redis全体を外部ハッシュと見なすことができます。これが外部ハッシュと呼ばれる理由は、Redisが内部ハッシュと呼ばれるタイプのハッシュも内部的に提供するためです。データストレージにハッシュオブジェクトを使用する場合、Redis全体で2層のハッシュストレージを通過します。

 

ハッシュオブジェクト

ハッシュオブジェクト自体もKey-Valueストレージ構造であり、基になるストレージ構造は、ziplist(圧縮リスト)とhashtable(ハッシュテーブル)の2つのタイプに分けることもできます。これらの2つのストレージ構造は、コーディングによっても区別されます。

エンコーディング属性の説明オブジェクトエンコーディングコマンドの戻り値OBJ_ENCODING_ZIPLIST圧縮リストを使用してハッシュオブジェクトziplistを実装しますOBJ_ENCODING_HT辞書を使用してハッシュオブジェクトハッシュテーブルを実装します

 

ハッシュ表

RedisのKey-ValueはdictEntryオブジェクトによってラップされ、ハッシュテーブルはdictEntryオブジェクトを再度パッケージ化することによって取得されます。これはハッシュテーブルオブジェクトdicthtです。

typedef struct dictht {    dictEntry **table;//哈希表数组    unsigned long size;//哈希表大小    unsigned long sizemask;//掩码大小,用于计算索引值,总是等于size-1    unsigned long used;//哈希表中的已有节点数} dictht;复制代码

注:上記の構造定義のテーブルは配列であり、その各要素はdictEntryオブジェクトです。

 

辞書

シンボルテーブル、連想配列、またはマップとも呼ばれるディクショナリには、ディクショナリ内にネストされたハッシュテーブルdicthtオブジェクトがあります。ディクショナリhtの定義は次のとおりです。

typedef struct dictType {    uint64_t (*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;复制代码

その中で、dictTypeはいくつかの一般的に使用される関数を定義し、そのデータ構造は次のように定義されます。

typedef struct dictType {    uint64_t (*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;复制代码

ハッシュオブジェクトを作成すると、次の図が得られます(一部の属性は省略されています)。

Redisハッシュ分布が不均一な場合の対処方法

 

再ハッシュ操作

配列ht [2]はdictで定義され、2つのハッシュテーブルht [0]とht [1]がht [2]で定義されています。Redisはデフォルトでht [0]のみを使用し、ht [1]を使用せず、ht [1]の初期化にスペースを割り当てません。

ハッシュオブジェクトを設定する場合、ハッシュ配列(上図のdictEntry [3])のどの添え字が該当するかは、ハッシュ値を計算することによって決定されます。ハッシュの衝突が発生した場合(計算されたハッシュ値が同じ場合)、同じ添え字に複数のdictEntryが含まれるため、リンクリストが形成されます(上の図の右端のポイントはNULL位置を指します)が、注意が必要です。最後の挿入要素は常にリンクリストの一番上にあります(つまり、ハッシュの競合が発生すると、ノードは常にリンクリストの先頭に配置されます)。

データを読み取るときに、複数の要素を持つノードに遭遇した場合、リンクリストをトラバースする必要があるため、リンクリストが長いほど、パフォーマンスが低下します。ハッシュテーブルのパフォーマンスを確保するには、次の2つの条件のいずれかが満たされたときにハッシュテーブルを再ハッシュする必要があります。

  • 負荷係数が1以上で、dict_can_resizeが1の場合。
  • 負荷率が安全しきい値(dict_force_resize_ratio = 5)以上の場合。

PS:負荷率=ハッシュテーブルで使用されているノードの数/ハッシュテーブルのサイズ(例:h [0] .used / h [0] .size)。

 

再ハッシュステップ

拡張ハッシュと縮小ハッシュはどちらも、主に次の5つの手順で、スペースの割り当てと解放を伴う再ハッシュを実行することで完了します。

  1. 辞書dictのht [1]ハッシュテーブルにスペースを割り当てます。そのサイズは、現在のハッシュテーブルに保存されているノードの数によって異なります(例:ht [0] .used)。
  2. ディクショナリの属性rehashixの値を0に設定し、再ハッシュ操作が実行されていることを示します。
  3. ht [0]内のすべてのキーと値のペアのハッシュ値を順番に再計算し、それらをht [1]配列の対応する位置に配置します。rehashixの値は、の再ハッシュが完了した後、1ずつインクリメントする必要があります。キーと値のペア。
  4. ht [0]のすべてのキーと値のペアがht [1]に移行されたら、ht [0]を解放し、ht [1]をht [0]に変更してから、新しいht [1]配列を作成します。次の再ハッシュのために。
  5. 辞書の属性rehashixを-1に設定します。これは、この再ハッシュ操作が終了し、次の再ハッシュを待つことを意味します。

 

プログレッシブリハッシュ

このRedisでの再ハッシュ操作は、一度にすべて再ハッシュするのなく  、 ht [0] から ht [1]のキーと値のペア複数回ゆっくりと 再ハッシュする ため、この操作はプログレッシブリハッシュとも呼ばれ ます。プログレッシブリハッシュは、分割統治法である集中型リハッシュによってもたらされる膨大な量の計算を回避できます。

プログレッシブリハッシュプロセスでは、新しいキーと値のペアが格納されている可能性があるため、現時点では** Redisのアプローチは、ht [0]を確保するために、新しく追加されたキーと値のペアをht [1]に均一に配置することです。キーと値のペアの数は減少するだけです**。

操作が再ハッシュを実行しているときに、サーバーがクライアントの要求操作からコマンドを受信すると、最初にht [0]をクエリし 、次に結果はht [1] クエリよりも少なくなります

 

ziplist

ziplistの一部の機能は、前回の記事で個別に分析されています。詳細については、ここをクリックしてください。ただし、ハッシュオブジェクトのziplistとリストオブジェクトのziplistの違いは、ハッシュオブジェクトがキー値形式であるため、zipリストもキー値として表示され、キーと値が表示されることに注意してください。互いに接近している:

Redisハッシュ分布が不均一な場合の対処方法

 

ziplistとハッシュテーブルのエンコーディング変換

ハッシュオブジェクトが次の2つの条件を満たすことができる場合、ハッシュオブジェクトはストレージにziplistエンコーディングを使用することを選択します。

  • ハッシュオブジェクト内のすべてのキーと値のペア(キーと値を含む)の全長は64バイト以下です(このしきい値はパラメーターhash-max-ziplist-valueで制御できます)。
  • ハッシュオブジェクト内のキーと値のペアの数は512以下です(このしきい値は、パラメーターhash-max-ziplist-entriesによって制御できます)。

これらの2つの条件のいずれかが満たされない場合、ハッシュオブジェクトはストレージにハッシュテーブルエンコーディングを使用することを選択します。

 

ハッシュオブジェクトの一般的なコマンド

  • hset key field value:単一のフィールド(ハッシュオブジェクトのキー値)を設定します。
  • hmset key field1 value1 field2 value2:複数のフィールド(ハッシュオブジェクトのキー値)を設定します。
  • hsetnx key field value:ハッシュテーブルキーのfieldフィールドの値をvalueに設定します。フィールドがすでに存在する場合、操作は実行されません。
  • hget key field:ハッシュテーブルキーのfieldフィールドに対応する値を取得します。
  • hmget key field1 field2:ハッシュテーブルキーの複数のフィールドに対応する値を取得します。
  • hdel key field1 field2:ハッシュテーブルキーの1つ以上のフィールドを削除します。
  • hlen key:ハッシュテーブルキーのフィールド数を返します。
  • Hincrbyキーフィールドインクリメント:ハッシュテーブルキーのフィールドフィールドの値にインクリメントを追加します。インクリメントは負の数にすることができます。フィールドが数値でない場合、エラーが報告されます。
  • hincrbyfloatキーフィールドインクリメント:ハッシュテーブルキーのフィールドフィールドの値にインクリメントを追加します。インクリメントは負の数にすることができます。フィールドがfloatタイプでない場合、エラーが報告されます。
  • hkeys key:ハッシュテーブルキーのすべてのフィールドを取得します。
  • hvalsキー:ハッシュテーブルのすべてのフィールドの値を取得します。

ハッシュオブジェクトを操作するための一般的なコマンドがわかっているので、前述のハッシュオブジェクトのタイプとエンコードを確認できます。テストする前に他のキー値の干渉を防ぐために、最初にflushallコマンドを実行してRedisデータベースをクリアします。

次に、次のコマンドを順番に実行します。

hset address country chinatype addressobject encoding address复制代码

次の効果を得る:

Redisハッシュ分布が不均一な場合の対処方法

 

ハッシュオブジェクトにキーと値のペアが1つしかない場合、基になるエンコーディングはziplistであることがわかります。

ここで、hash-max-ziplist-entriesパラメーターを2に変更し、Redisを再起動して、最後に次のコマンドを入力してテストします。

hmset key field1 value1 field2 value2 field3 value3object encoding key复制代码

出力後、次の結果が得られます。

Redisハッシュ分布が不均一な場合の対処方法

 

ご覧のとおり、エンコーディングはハッシュテーブルになっています。

 

総括する

この記事では主に、ハッシュテーブルの使用、Redisで一般的に使用される5つのデータ型の中でハッシュ型の基礎となるストレージ構造、およびハッシュ分布が不均一な場合にRedisが再ハッシュを実行する方法を紹介します。最後に、一般的に使用されるハッシュについて学びました。オブジェクトいくつかの例を通して、この記事の結論を注文して検証します。

おすすめ

転載: blog.csdn.net/Java0258/article/details/112990267