Redisがデータストレージに2つのデータ構造を同時に使用するのはなぜですか?

序文

Redisにはデータ型があり、保存するときに2つのデータ構造を使用して別々に保存しますが、なぜRedisがこれを行うのですか?これを行うと、同じデータが2倍のスペースを占めることになりますか?

コレクションオブジェクトの5つの基本的なタイプ

Redisのコレクションオブジェクトは、文字列型の要素を含む順序付けられていないコレクションであり、コレクション内の要素は一意であり、繰り返し可能ではありません。

コレクションオブジェクトには、intsetとhashtableの2つの基本的なデータ構造があります。内部的には、エンコーディングは以下を区別するために使用されます。

intsetエンコーディング

intset(整数のセット)は、int16_t、int32_t、int64_t型の整数値を保持でき、セットに重複する要素がないことが保証されています。

intsetデータ構造は、次のように定義されています(ソースコードinset.hで)。

typedef struct intset {
    uint32_t encoding;//编码方式
    uint32_t length;//当前集合中的元素数量
    int8_t contents[];//集合中具体的元素
} intset;

复制代码

次の図は、intsetのコレクションオブジェクトストレージの簡略図です。

エンコーディング

intset内のエンコーディングは、現在の整数セットのデータストレージタイプを記録します。主に3つのタイプがあります。

  • INTSET_ENC_INT16

このとき、contents []の各要素はint16_t型の整数値であり、範囲は-32768〜32767(-2の15乗〜2の15乗-1)です。

  • INTSET_ENC_INT32

このとき、contents []の各要素はint32_t型の整数値であり、範囲は-2147483648〜2147483647(-2の31乗〜2の31乗-1)です。

  • INTSET_ENC_INT64

このとき、contents []の各要素はint64_t型の整数値であり、範囲は-9223372036854775808〜9223372036854775807(-2の63乗〜2の63乗-1)です。

コンテンツ[]

contents []構造体の定義はint8_tタイプとして記述されていますが、実際のストレージタイプは上記のエンコーディングによって決定されます。

整数コレクションのアップグレード

整数セットの要素が最初はすべて16ビットであり、int16_tタイプで格納されている場合は、別の32ビット整数を格納する必要があります。次に、元の整数セットをアップグレードし、32ビット整数をアップグレードする必要があります。アップグレード後に保存できます。整数のコレクションに保存されます。これには、整数セットの型のアップグレードが含まれます。アップグレードプロセスには、主に4つのステップがあります。

  • 新しく追加された要素のタイプに応じて基になる配列スペースのサイズを拡張し、アップグレード後に既存の要素のビット数に応じて新しいスペースを割り当てます。
  • 既存の要素を変換し、変換された要素を後ろから前に1つずつ配列に戻します。
  • 新しい要素を配列の先頭または末尾に配置します(アップグレードをトリガーする条件は、現在の配列の整数型が新しい要素を格納できないため、新しい要素が既存の要素よりも大きいか小さいためです)。
  • encodingプロパティを最新のエンコーディングに変更し、lengthプロパティを同期的に変更します。

PS:文字列オブジェクトのエンコーディングと同様に、整数コレクションのタイプがアップグレードされると、エンコーディングはそのまま残り、ダウングレードできません。

アップグレード例

1.エンコーディングがint16_tであり、3つの要素を内部に格納するコレクションストレージがあるとします。

2.このとき、整数50000を挿入する必要があり、格納できないことがわかりました。50000はint32_t型の整数であるため、新しいスペースとアプリケーションのサイズを申請する必要があります。スペースは4*32-48=80です。

3.これで、新しい配列に配置する4つの要素があり、元の配列は3番目にランク付けされているため、アップグレードされた3をビット64〜95に移動する必要があります。

4.先に進み、アップグレードされた2ビットを32〜63ビットに移動します。

5.アップグレードされた1をビット0〜31に移動し続けます。

6. 50000は、ビット96〜127に配置されます。

7.最後に、encoding属性とlength属性が変更され、変更後にアップグレードが完了します。

ハッシュテーブルエンコーディング

ハッシュテーブルの構造は、ハッシュオブジェクトの前の説明で詳細に分析されています。詳細については、ここをクリックしてください。

intsetおよびハッシュテーブルエンコーディング変換

コレクションが次の2つの条件を満たす場合、Redisはintsetエンコーディングの使用を選択します。

  • コレクションオブジェクトが保持するすべての要素は整数値です。
  • コレクションオブジェクトに格納されている要素の数は512以下です(このしきい値は、構成ファイルset-max-intset-entriesによって制御できます)。

コレクション内の要素が上記の2つの条件を満たさない場合、ハッシュテーブルエンコーディングが選択されます。

コレクションオブジェクトの一般的なコマンド

  • sadd key member1 member2:1つ以上の要素memberをsetキーに追加し、成功した追加の数を返します。要素がすでに存在する場合、それは無視されます。
  • sismember key member:要素メンバーがsetキーに存在するかどうかを判別します。
  • srem key member1 member2:setキーの要素を削除すると、存在しない要素は無視されます。
  • ソースdestメンバーの移動:要素メンバーをコレクションソースからdestに移動するか、メンバーが存在しない場合は何もしません。
  • smembers key:設定されたキーのすべての要素を返します。

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

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

sadd num 1 2 3  //设置 3 个整数的集合,会使用 intset 编码
type num //查看类型
object encoding num   //查看编码

sadd name 1 2 3 test  //设置 3 个整数和 1 个字符串的集合,会使用 hashtable 编码
type name //查看类型
object encoding name //查看编码 

复制代码

次の効果を得る:

セット要素に整数しかない場合、セットはintsetエンコーディングを使用し、set要素に非整数が含まれる場合は、ハッシュテーブルエンコーディングが使用されることがわかります。

順序付けられたコレクションオブジェクトの5つの基本的なタイプ

順序集合とRedisの集合の違いは、順序集合の各要素がダブルタイプのスコアに関連付けられ、スコアの昇順で並べ替えられることです。つまり、並べ替えられたセットの順序は、値を自分で設定したときの分数によって決まります。

ソートされたセットオブジェクトには、スキップリストとジップリストの2つの基本的なデータ構造があります。内部的には、エンコーディングによっても区別されます。

スキップリストエンコーディング

スキップリストはスキップリストであり、単にスキップリストと呼ばれることもあります。スキップリストでエンコードされた順序集合オブジェクトは、基礎となる実装としてzset構造を使用し、zsetにはディクショナリとスキップリストの両方が含まれています。

ジャンプテーブル

スキップテーブルは順序付けられたデータ構造であり、その主な機能は、各ノード内の他のノードへの複数のポインターを維持することにより、ノードにすばやくアクセスするという目的を達成することです。

ほとんどの場合、スキップテーブルの効率は平衡ツリーの効率と同じですが、スキップテーブルの実装は平衡ツリーの実装よりもはるかに単純であるため、Redisはスキップテーブルを使用して順序付きを実装することを選択しますセットする。

次の図は、通常の順序付きリンクリストです。要素35を検索する場合、最初から最後までしかトラバースできません(リンクリストの要素はランダムアクセスをサポートしていないため、バイナリ検索は使用できません。配列は添え字を介してランダムにアクセスできるため)、バイナリ検索は一般にソートされた配列に適用可能です)、時間計算量はO(n)です。

次に、リンクリストの真ん中に直接ジャンプできれば、多くのリソースを節約できます。これがスキップリストの原則です。次の図に示すように、スキップリストのデータ構造の例です。 :

上の図では、level1、level2、およびlevel3はスキップリストのレベルです。各レベルには、同じレベルの次の要素へのポインタがあります。たとえば、上の図の要素35を見つけるためにトラバースすると、次のようになります。 3つのオプション:

  • 1つ目は、レベル1レベルのポインターを実行することです。これは、要素35を見つけるために7回(1-> 8-> 9-> 12-> 15-> 20-> 35)トラバースする必要があります。
  • 2つ目は、レベル2レベルのポインターを実行することで、要素35を見つけるために5回(1-> 9-> 12-> 15-> 35)トラバースするだけで済みます。
  • 3番目のタイプはレベル3レベルの要素を実行することです。このとき、要素35を見つけるために3回(1-> 12-> 35)トラバースするだけで済み、効率が大幅に向上します。

スキップリストの保管構造

スキップリストの各ノードは、(ソースコードserver.hの)zskiplistNodeノードです。

typedef struct zskiplistNode {
    sds ele;//元素
    double score;//分值
    struct zskiplistNode *backward;//后退指针
    struct zskiplistLevel {//层
        struct zskiplistNode *forward;//前进指针
        unsigned long span;//当前节点到下一个节点的跨度(跨越的节点数)
    } level[];
} zskiplistNode;

复制代码
  • レベル

levelは、ジャンプテーブルのレベルであり、配列です。つまり、ノードの要素は複数のレベル、つまり他のノードへの複数のポインターを持つことができ、プログラムはでポインターを介したアクセスを改善するための最速のパスを選択できます。さまざまなレベルの速度。

levelは、新しいノードが作成されるたびにべき乗則に従ってランダムに生成される1〜32の数値です。

  • フォワード(フォワードポインタ)

各レイヤーには、リンクリストの最後にある要素へのポインターがあり、要素をトラバースするときにフォワードポインターを使用する必要があります。

  • スパン

スパンは2つのノード間の距離を記録します。NULLを指す場合、スパンは0であることに注意してください。

  • 後方(後方ポインター)

フォワードポインタとは異なり、バックポインタは1つしかないため、毎回前のノードに戻ることしかできません(上の図ではバックポインタは描画されていません)。

  • ele(要素)

ジャンプテーブルの要素はsdsオブジェクト(以前のバージョンではredisObjectオブジェクトを使用)であり、要素は一意である必要があり、繰り返すことはできません。

  • スコア

ノードのスコアはダブルタイプの浮動小数点数です。ジャンプテーブルでは、ノードはスコアに従って昇順で配置され、異なるノードのスコアを繰り返すことができます。

上記の説明はスキップリスト内のノードのみであり、複数のzskiplistNodeノードがzskiplistオブジェクトを形成します。

typedef struct zskiplist {
    struct zskiplistNode *header, *tail;//跳跃表的头节点和尾结点指针
    unsigned long length;//跳跃表的节点数
    int level;//所有节点中最大的层数
} zskiplist;

复制代码

この時点で、順序集合はこのzskiplistで実装されていると思うかもしれませんが、実際には、Redisはzskiplistを直接使用して実装するのではなく、zsetオブジェクトを使用して再度ラップします。

typedef struct zset {
    dict *dict;//字典对象
    zskiplist *zsl;//跳跃表对象
} zset;

复制代码

したがって、最終的に、順序集合がスキップリストエンコーディングを使用する場合、そのデータ構造は次の図に示されます。

上図上部の辞書のキーは順序集合の要素(メンバー)に対応し、値はスコア(スコア)に対応します。上図の下部では、ジャンプテーブルの整数1、8、9、12も要素(メンバー)に対応しており、最後の行のdouble型の数値はスコア(スコア)です。

つまり、ディクショナリとジャンプテーブルのデータは両方とも、格納する要素を指します(2つのデータ構造は最終的に同じアドレスを指すため、データは冗長に格納されません)。なぜRedisがこれを行うのですか?

辞書とスキップテーブルの両方を使用することを選択する理由

ソートされたセットは、スキップテーブルを直接使用するか、辞書のみを使用して実装できますが、スキップテーブルのみを使用する場合は、スパンの大きいポインターを使用して要素をトラバースして必要なデータを見つけることができますが、程度はまだO(logN)に達し、辞書内の要素を取得する複雑さはO(1)です。辞書だけを使用して要素を取得する場合、非常に高速ですが、辞書は順序付けられていません。したがって、範囲を検索する場合は、並べ替えが時間のかかる操作である必要があります。そのため、Redisは2つのデータ構造を組み合わせてパフォーマンスを最大化します。これは、Redisの設計の微妙な点でもあります。

ziplistエンコーディング

圧縮リストは、リストオブジェクトとハッシュオブジェクトの両方で使用されます。詳細については、ここをクリックしてください。

blog.csdn.net/zwx900102/a…

ziplistおよびskiplistエンコーディング変換

順序集合オブジェクトが次の両方の条件を満たす場合、ストレージにziplistエンコーディングを使用します。

  • ソートされたセットオブジェクトに格納されている要素の数は128未満です(zset-max-ziplist-entriesを構成することで変更できます)。
  • ソートされたセットオブジェクトに格納されているすべての要素の全長は64バイト未満です(zset-max-ziplist-valueを構成することで変更できます)。

順序付きコレクションオブジェクトの一般的なコマンド

  • zadd key score1 member1 score2 member2:1つ以上の要素(メンバー)とそのスコアをソートされたセットキーに追加します。
  • zscore key member:ソートされたセットキーのメンバーメンバーのスコアを返します。
  • ジンクビーキーnumメンバー:ソートされたセットキーのメンバーにnumを追加します。numは負の数にすることができます。
  • zcount key min max:ソートされたセットキーのスコア値が間隔[min、max]にあるメンバーの数を返します。
  • zrange key start stop:ソートされたセットキーのスコアが小さいものから大きいものへと並べられた後、[start、stop]間隔のすべてのメンバーを返します。
  • zrevrange key start stop:ソートされたセットキーのスコアが大きいものから小さいものへと並べられた後、[start、stop]間隔のすべてのメンバーを返します。
  • zrangebyscore key min max:小さいものから大きいものへのスコアでソートされたソート済みセットの[min、max]間隔内のすべての要素を返します。デフォルトは閉じた間隔ですが、(または[maxとminの値の前に[または[]を追加して、開いた間隔と閉じた間隔を制御できます。
  • zrevrangebyscore key max min:スコアで大きいものから小さいものへとソートされたソート済みセットの[min、max]間隔内のすべての要素を返します。デフォルトは閉じた間隔ですが、(または[maxとminの値の前に[または[]を追加して、開いた間隔と閉じた間隔を制御できます。
  • zrank key member:ソートされたセット内のメンバー内の要素のランク(小さいものから大きいものへ)を返し、返される結果は0から始まります。
  • zrevrank key member:ソートされたセット内のメンバー内の要素のランキング(大きいものから小さいものへ)を返し、返される結果は0から始まります。
  • zlexcount key min max:ソートされたセットのminとmaxの間のメンバーの数を返します。このコマンドのminとmaxの前には、(または[を付けて、開区間と閉区間を制御する必要があります。特別な値-と+は、それぞれ負の無限大と正の無限大を表します。

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

コマンドを実行する前に、まず構成ファイルのパラメーターzset-max-ziplist-entriesを2に変更してから、Redisサービスを再起動します。

再起動が完了したら、次のコマンドを順番に実行します。

zadd name 1 zs 2 lisi //设置 2 个元素会使用 ziplist
type name //查看类型
object encoding name //查看编码 
    
zadd address 1 beijing 2 shanghai 3 guangzhou 4 shenzhen  //设置4个元素则会使用 skiplist编码
type address  //查看类型
object encoding address //查看编码 

复制代码

次の効果を得る:

要約する

このホワイトペーパーでは、主に、セットオブジェクトと順序付きセットオブジェクトの基になるストレージ構造intsetとskiplistの実装原則を分析し、ソートされたセットがソートを実装する方法と、2つのデータ構造(辞書とスキップリスト)が同時ストレージに使用される理由に焦点を当てます。データ。


著者:
公式アカウント_ITブラザーリンク:https://juejin.cn/post/7075575482669858824
 

おすすめ

転載: blog.csdn.net/wdjnb/article/details/124475826