記事では、Redisのデータ構造とオブジェクトシステムの設計方法を分析していますか?

RedisはオープンソースのKey-Valueストレージシステムです。6つの低レベルデータ構造を使用して、文字列オブジェクト、リストオブジェクト、ハッシュオブジェクト、コレクションオブジェクト、および順序付けされたコレクションオブジェクトを含むオブジェクトシステムを構築します。今日は、12枚の写真を見て、そのデータ構造とオブジェクトシステムの実現原理を完全に理解します。

この記事の内容は次のとおりです。

  • 最初に、動的文字列、リンクリスト、辞書、スキップリスト、整数セット、圧縮リストの6つの基本的なデータ構造を紹介します。

  • 次に、Redisのオブジェクトシステムに文字列オブジェクト(String)、リストオブジェクト(List)、ハッシュオブジェクト(Hash)、セットオブジェクト(Set)、および順序付きセットオブジェクト(ZSet)を導入します。

  • 最後に、Redisのキースペースと有効期限キー(有効期限)の実装を紹介します。

データ構造

1.単純な動的文字列

Redisは動的文字列SDSを使用して文字列値を表します。次の図は、Redisの値を持つSDS構造を示しています。

  • len:文字列の真の長さを示します(NULLターミネーターを除く)。

  • alloc:文字列の最大容量を示します(最後の追加バイトは含みません)。

  • フラグ:常に1バイトを占有します。最下位の3ビットは、ヘッダーのタイプを示すために使用されます。

  • buf:文字配列。

SDSの構造により、文字列の変更によって引き起こされるメモリの再割り当ての数を減らすことができます。これは、メモリの事前割り当てと遅延スペース解放の2つのメカニズムに依存します。

SDSを変更する必要があり、SDSを拡張する必要がある場合、Redisは変更に必要なスペースをSDSに割り当てるだけでなく、SDSに未使用の追加スペースを割り当てます。

  • SDSの長さ(つまり、len属性の値)が変更後に1MB未満の場合、Redisはlen属性と同じサイズの未使用のスペースを事前に割り当てます。

  • 変更後のSDSの長さが1MBを超える場合、Redisは1MBの未使用スペースを割り当てます。

たとえば、変更後、SDSのlenの長さは20バイトで1MB未満であり、Redisは20バイトのスペースを事前に割り当て、SDS buf配列の実際の長さ(最後のバイトを除く)は20 + 20 =になります。 40バイト。SDSのlenの長さが1MBより大きい場合、割り当てられるスペースは1MBのみです。

同様に、SDSが保存する文字列の長さを短くしても、余分なバイトはすぐには解放されず、後で使用できるようになります。

2.リンクされたリスト

リンクリストはRedisで広く使用されています。たとえば、リストオブジェクトの基本的な実装の1つはリンクリストです。リンクリストオブジェクトに加えて、パブリッシュとサブスクライブ、スロークエリ、モニターなどの機能もリンクリストを使用します。

Redisのリンクリストは二重にリンクされたリストであり、上の図は模式図です。リンクされたリストは最も一般的なデータ構造であるため、ここでは詳細には触れません。

Redisのリンクリスト構造のdup、free、matchメンバー属性は、ポリモーフィックリンクリストの実装に必要なタイプ固有の関数です。

  • dup関数は、リンクリストノードに格納されている値をコピーしてディープコピーするために使用されます。

  • free関数は、リンクリストノードによって保存された値を解放するために使用されます。

  • match関数は、リンクリストノードに格納されている値が別の入力値と等しいかどうかを比較するために使用されます。

3.辞書

辞書は、キースペースやハッシュオブジェクトなどのさまざまなRedis関数を実装するために広く使用されています。以下に模式図を示します。

RedisはMurmurHash2アルゴリズムを使用してキーのハッシュ値を計算し、チェーンアドレス方式を使用してキーの競合を解決します。同じインデックスに割り当てられた複数のキーと値のペアは、一方向リンクリストに接続されます。

4.ジャンプテーブル

Redisは、順序付けられたコレクションオブジェクトの基本的な実装の1つとしてジャンプテーブルを使用します。階層的にリンクされたリストの要素を順番に保存します。効率は、検索、削除、追加などのバランスの取れたツリー操作に匹敵し、期待される対数の時間で完了することができます。バランスの取れたツリーと比較すると、ジャンプテーブル実装ははるかに簡単で直感的です。

ジャンプテーブルの概略図は上の図に示されていますが、ここでは、コアアイデアについて簡単に説明しているだけであり、詳細は説明されていません。

概略図に示されているように、zskiplistNodeはジャンプリストのノード、そのeleは保持されている要素の値、scoreはスコア、ノードはそのスコア値に従って順番に配置され、レベル配列はいわゆる階層型リンクリストの実施形態です。

各ノードのレベル配列のサイズは異なります。レベル配列の値は、次のノードへのポインタとスパン値です。スパン値は、2つのノードのスコアの差です。レベル配列の値が高いほどスパン値は大きくなり、レベル配列の値が低いほどスパン値は小さくなります。

レベル配列は、スケールが異なる定規のようなものです。長さを測定するときは、まず大きなスケールを使用して範囲を推定し、次に縮小したスケールを使用して正確に近似します。

ジャンプテーブルの要素値をクエリするときは、最初のノードのトップレベルから始めます。たとえば、上のジャンプテーブルのo2要素をクエリする場合、zskiplistのヘッダーポインターがそれを指すため、最初にo1ノードから始めます。

まず、レベル[3]からクエリを実行し、そのスパンが2で、o1ノードのスコアが1.0であることを確認します。したがって、合計は3.0であり、o2のスコア値2.0よりも大きくなります。したがって、o2ノードがo1ノードとo3ノードの間にあることがわかります。当時は代わりに小さなスケールの定規が使われていました。レベル[1]のポインターを使用して、o2ノードを正常に検索します。

5.整数のセット

Intesetコレクションは、コレクションオブジェクトの基本的な実装の1つです。コレクションに整数値の要素のみが含まれ、このコレクションの要素数が多くない場合、Redisは整数のコレクションをコレクションオブジェクトの基本的な実装として使用します。

上の図に示されているように、整数セットのエンコーディングは、その型、int16t、int32t、またはint64_tを示します。各要素は、contents配列の配列項目であり、各項目は配列の最小から最大の順に配置され、配列には重複項目が含まれていません。長さ属性は、整数セットに含まれる要素の数です。

6.圧縮リスト

圧縮キューziplistは、リストオブジェクトとハッシュオブジェクトの基本的な実装の1つです。特定の条件が満たされると、リストオブジェクトとハッシュオブジェクトの両方が、圧縮キューを最下層として実装されます。

圧縮キューは、Redisがメモリを節約するために開発したもので、特別にエンコードされた一連の連続メモリブロックで構成される順次データ構造です。その属性値は次のとおりです。

  • zlbytes:長さは4バイトで、圧縮された配列全体のメモリバイトを記録します。

  • zltail:長さは4バイトで、圧縮キューの開始アドレスから圧縮キューの最後までのバイト数を記録します。この属性を使用して、テールノードのアドレスを直接決定できます。

  • zllen:長さはノードの数を含めて2バイトです。属性値がINT16_MAX未満の場合、値はノードの合計数です。それ以外の場合は、キュー全体を走査して合計数を決定する必要があります。

  • zlend:長さは1バイトで、圧縮キューの終わりを示すために使用される特別な値です。

中央の各ノードエントリは、3つの部分で構成されています。

  • previous_entry_length:圧縮リスト内の前のノードの長さ。現在のアドレスを使用してポインター計算を実行し、前のノードの開始アドレスを計算します。

  • エンコーディング:ノードによって保存されたデータのタイプと長さ

  • content:バイト配列または整数のノード値。

対象

6種類の低レベルデータ構造を上で紹介しました。Redisはこれらのデータ構造を直接使用してKey-Valueデータベースを実装するのではなく、これらのデータ構造に基づいてオブジェクトシステムを作成します。このシステムには、文字列オブジェクト、リストオブジェクト、ハッシュオブジェクト、およびコレクションが含まれますオブジェクトとこれらの5種類のオブジェクトの順序付けられたコレクション。各オブジェクトは、上記の基になるデータ構造の少なくとも1つを使用します。

Redisは、さまざまな使用シナリオとコンテンツサイズに基づいてオブジェクトが使用するデータ構造を決定し、それによってさまざまなシナリオでのオブジェクトの効率とメモリフットプリントを最適化します。

Redis RedisObject構造の定義を以下に示します。

typedef struct redisObject {

    unsigned type:4;

    unsigned encoding:4;

    unsigned lru:LRU_BITS; 

    int refcount;

    void *ptr;

} robj;
复制代码

typeはREDISSTRING、REDISLIST、REDISHASH、REDISSET、REDIS_ZSETなどのオブジェクトタイプです。

エンコーディングとは、オブジェクトが使用するデータ構造を指します。完全なセットは次のとおりです。

1.文字列オブジェクト

次の図に示すように、最初に文字列オブジェクトの実装を確認します。

文字列オブジェクトが文字列値を保存し、長さが32バイトを超える場合、図の上部に示すように、文字列オブジェクトはSDSを使用して保存され、オブジェクトエンコーディングはrawに設定されます。文字列の長さが32バイト未満の場合、文字列オブジェクトはembstrエンコーディングを使用して保存されます。

embstrエンコーディングは、短い文字列を保存するために特別に使用される最適化されたエンコーディング方法です。このエンコーディングの構成は、未加工のエンコーディングと同じです。どちらも、上図の下部に示すように、redisObject構造とsdshdr構造を使用して文字列を格納します。

ただし、rawエンコーディングは2つのメモリ割り当てを呼び出して上記の2つの構造を個別に作成しますが、embstrは1つのメモリ割り当てを通じて連続したスペースを割り当て、スペースには一度に2つの構造が含まれます。

embstrは1つのメモリ割り当てのみを必要とし、同じ連続メモリで、キャッシュによってもたらされる利点をより有効に活用しますが、embstrは読み取り専用であり、変更できません。embstrでエンコードされた文字列オブジェクトが追加されると、次に進む前に、Redisはそれをrawエンコーディングに変換します。

2.オブジェクトを一覧表示する

リストオブジェクトのエンコーディングは、ziplistまたはlinkedlistです。以下に模式図を示します。

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

  • リストオブジェクトに格納されているすべての文字列要素の長さが64バイト未満です。

  • リストオブジェクトに保存されている要素の数が512未満です。

これら2つの条件を満たすことができないリストオブジェクトは、リンクリストコード化するか、リンクリストコードに変換する必要があります。

3.ハッシュオブジェクト

ハッシュオブジェクトのエンコードには、ziplistまたはdictを使用できます。以下に模式図を示します。

ハッシュオブジェクトが基になる実装として圧縮キューを使用する場合、プログラムは、キーと値のペアを互いに隣り合って圧縮キューに挿入します。キーを最初に保持するノードと、値を最後に保持するノードです。下の図の上部に示すように、ハッシュには2つのキーと値のペア(名前:トムと年齢:25)があります。

ハッシュオブジェクトが次の2つの条件を満たすことができる場合、ハッシュオブジェクトはziplistを使用してエンコードされます。

  • ハッシュオブジェクトに格納されているすべてのキーと値のペアのキーと値の文字列の長さは、64バイト未満です。

  • ハッシュオブジェクトに格納されているキーと値のペアの数は512未満です。

これら2つの条件を満たすことができないハッシュオブジェクトは、dictエンコーディングを使用するか、dictエンコーディングに変換する必要があります。

4.コレクションオブジェクト

セットオブジェクトエンコーディングでは、intsetまたはdictを使用できます。

intset-encodedコレクションオブジェクトは整数セットを最下層の実装として使用し、すべての要素は整数セットに格納されます。

dictでエンコードする場合、ディクショナリの各キーは文字列オブジェクトであり、各文字列オブジェクトはセット要素であり、ディクショナリの値はすべてNULLに設定されます。以下に示すように。

コレクションオブジェクトが次の2つの条件を同時に満たすことができる場合、オブジェクトはintsetを使用してエンコードされます。

  • コレクションオブジェクトに格納されているすべての要素は整数値です。

  • コレクションオブジェクトに格納される要素の数は512を超えません。

それ以外の場合は、エンコーディングにdictを使用します。

5.順序付けられたコレクションオブジェクト

順序付きセットのエンコーディングは、ziplistまたはskiplistにすることができます。

順序付けられたコレクションがziplistでエンコードされている場合、各コレクション要素は、隣り合う2つの圧縮リストノードで表されます。最初のノードは要素の値、2番目のノードは要素のスコア、つまり並べ替え比較の値です。

圧縮リストのコレクション要素は、下の図の上部に示すように、スコアの小さい順に並べ替えられます。

順序付きセットは、zset構造を基本的な実装として使用するスキップリストを使用してエンコードされます。zet構造には、辞書とジャンプテーブルの両方が含まれます。

それらの中で、ジャンプテーブルはすべての要素をスコアの小さい順から大きい順に保存し、各ジャンプテーブルノードは要素を保存し、そのスコア値は要素のスコアです。ディクショナリは、メンバーからスコアへのマッピングを作成します。ディクショナリのキーはコレクションのメンバーの値であり、ディクショナリの値はコレクションのメンバーのスコアです。辞書を使用して、特定のメンバーのスコアをO(1)複雑度で見つけることができます。以下に示すように。

ジャンプテーブルのコレクション要素の値オブジェクトとディクショナリは共有されるため、追加のメモリ消費はありません。

順序付きセットオブジェクトが次の2つの条件を同時に満たすことができる場合、オブジェクトはziplistを使用してエンコードされます。

  • 順序付きセットに格納されている要素の数は128未満です。

  • 順序付けされたセットに格納されているすべての要素の長さは64バイト未満です。

それ以外の場合は、スキップリストエンコーディングを使用します。

6.データベースキースペース

Redisサーバーには複数のRedisデータベースがあり、各Redisデータには独自の独立したキー値スペースがあります。各Redisデータベースはdictを使用して、すべてのキーと値のペアをデータベースに格納します。

キースペースのキーはデータベースのキーです。各キーは文字列オブジェクトであり、値オブジェクトは文字列オブジェクト、リストオブジェクト、ハッシュテーブルオブジェクト、コレクションオブジェクト、および順序付きコレクションオブジェクトのいずれかです。

キースペースに加えて、Redisはdict構造を使用してキーの有効期限を保存します。上図に示すように、キーはキースペースのキー値であり、値は有効期限です。

Redisは期限切れのディクショナリを通じて、キーが期限切れかどうかを直接判断できます。まず、キーが期限切れのディクショナリに存在するかどうかを確認します。存在する場合は、キーの有効期限を現在のサーバーのタイムスタンプと比較します。 。

原作:道路技術張犬の卵

転載:WeChatパブリックアカウント

元のリンク:mp.weixin.qq.com/s/gQnuynv6X ...


一緒に進歩し、学び、共有する

誰でも私のパブリックアカウント[ 風と波は静かで静かです ]に注意を払うことを歓迎します多数のJava関連の記事、学習資料がその中で更新され、照合された情報もその中に配置されます。

あなたが書くのが良いと思うなら、ちょうどそれと同じように、注意を向けてください!注意を払い、迷子にならず、更新を続けてください!

おすすめ

転載: juejin.im/post/5e9c56e4f265da47b35c7d5f