12.Redisで知らない秘密-5つの基本構造SortedSetの実現原理

序文

SortedSet(zset)の順序付きコレクションは、Set collection:scoreに基づいて、コレクション内の各要素のシーケンス値を維持していると見なすことができます。これにより、コレクション内の要素をスコアに従って並べ替えることができます。候補者はスコアでランク付けされ、特定のゲームプレーヤーはスコアでランク付けされ、特定のデータはWebサイトのホームページでランク付けされ、最新のコメントは時間でランク付けされます。

Redisはインメモリデータベースです。また、読み取りと書き込みの速度を確保しながら、メモリのオーバーヘッドを考慮する必要があります。SortedSet順序セットの場合、順序値を維持する必要があります。順序セットの基本的な実装では、次を選択できます。配列、リンクされたリスト、バランスの取れたツリーや赤黒ツリーなどの構造ですが、SortedSetはこれらの構造を選択しませんでした。配列内の要素の挿入と削除のパフォーマンスは非常に低く、リンクリストのクエリは低速です。平衡ツリーまたは赤黒ツリーのクエリ効率は高くなりますが、ツリーのパフォーマンスを維持する必要があります。要素を挿入および削除する場合、実装は非常に複雑です。

したがって、SortedSetの最下層は、新しいタイプのデータ構造を使用します。 跳跃表

スキップリストスキップリストの原則

ジャンプテーブルのパフォーマンスは赤黒木に匹敵し、赤黒木よりも実装がはるかに簡単です。では、スキップウォッチとは何ですか?ジャンプリストを理解する前に、次のリンクリストを見てみましょう。

[外部リンク画像の転送に失敗しました。ソースサイトにヒル防止リンクメカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします(img-77seSZ0O-1615940744288)(sortedset.assets / 1615907166172.png)]

値が13のノードをクエリする場合、上記の単一リンクリストに対して、ノードを前から後ろにトラバースし、パフォーマンスが非常に低いと計算する必要があります。クエリ速度を向上させるにはどうすればよいですか。このリンクリストを赤黒木のような構造にしない限り、順序付けられたリンクリストでさえ二分探索のために変更できないことはわかっていますが、赤黒木の実現は面倒です。では、このリンクリストをこのように扱うとどうなりますか?

[外部リンク画像の転送に失敗しました。ソースサイトにヒル防止リンクメカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします(img-HWqiGo40-1615940744291)(sortedset.assets / 1615907457430.png)]

上の図に示すように、リンクリストの第1レベルの要素を2要素ごとに上向きに抽出して、リンクリストの第2レベルを形成します。要素を検索すると、最初に最上位レベルから13が見つかります。 18が見つかった場合13より大きい場合は、10に戻り、次のレベルに移動して検索し、13を検索します。今回は、検索数が前の単一リンクリストのほぼ半分であるため、クエリ時間。別のレイヤーを取得するとどうなりますか?

[外部リンク画像の転送に失敗しました。ソースサイトにヒル防止リンクメカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします(img-EOwpcGLF-1615940744294)(sortedset.assets / 1615907736389.png)]

今のルールに従って、もう1つのレイヤーを抽出しますが、今回の検索数は再び減りますか?実際、このデータ構造は「ジャンプテーブル」のストレージ構造です。実際、クエリのパフォーマンスは赤黒木に匹敵することがわかりますが、実装は赤黒木よりもはるかに簡単です。

SortedSetの低レベルの実装

SortedSetの最下層は、Ziplist圧縮リストと「ジャンプテーブル」の2つのストレージ構造を使用します。Redis構成ファイルには2つの構成があります。

  • zset-max-ziplist-entries 128:zsetが圧縮リストを使用する場合、要素の最大数。デフォルト値は128です。
  • zset-max-ziplist-value 64:zsetが圧縮リストを使用する場合、各要素の最大文字列長。デフォルト値は64です。

zsetが最初の要素を挿入すると、zset-max-ziplist-entriesの値が0に等しいかどうか、zset-max-ziplist-valueが挿入される文字列の長さよりも短いかどうか、次の2つの条件を判断します。 Redisは任意の条件を満たすでしょう。スキップリストが最下位の実装として使用されます。それ以外の場合は、圧縮されたリストが最下位の実装として使用されます。ソースコードを参照してください:t_zset.c

void zaddGenericCommand(client *c, int flags) {
    
    
 ...省略...
 if (zobj == NULL) {
    
    
        if (xx) goto reply_to_client; /* No key + XX option: nothing to do. */
        if (server.zset_max_ziplist_entries == 0 ||
            server.zset_max_ziplist_value < sdslen(c->argv[scoreidx+1]->ptr))
        {
    
    
            zobj = createZsetObject();/ *创建跳跃表*/
        } else {
    
    
            zobj = createZsetZiplistObject(); / *创建压缩列表 */
        }
        dbAdd(c->db,key,zobj);
    }
}

通常の状況では、zset-max-ziplist-entriesは0に構成されず、要素の文字列の長さは長すぎないため、順序付きセットを作成するときは、圧縮リストの基礎となる実装がデフォルトで使用されます。新しい要素がzsetに挿入されると、次の2つの条件が判断されます。zset内の要素の数がzset_max_ziplist_entriesより大きい、挿入された要素の文字列の長さがzset_max_ziplist_valueより大きい。いずれかの条件が満たされると、Redisはzsetの基盤となる実装を圧縮リストからジャンプリストに変換します。t_zset.cのzsetAdd関数を参照してください。

if (zzlLength(zobj->ptr) > server.zset_max_ziplist_entries ||
                sdslen(ele) > server.zset_max_ziplist_value)
     zsetConvert(zobj,OBJ_ENCODING_SKIPLIST);/* 转跳跃表 */

zsetがスキップリストに変換された後、要素が徐々に削除されても、圧縮リストに再度変換されないことは注目に値します。

スキップリストの構造

ホップテーブルは主に、次のように、ホップテーブルノード、ヘッドノード、テールノード、ノード番号、および最大ノードレベルで構成されます。ソースコードについては、server.hを参照してください。

typedef struct zskiplist {
    
    
    struct zskiplistNode *header, *tail;//跳表节点 ,头节点 , 尾节点
    unsigned long length;//节点数量
    int level;//目前表内节点的最大层数
} zskiplist;

typedef struct zset {
    
    
    dict *dict;
    zskiplist *zsl;
} zset;

説明:

  • ヘッダー:ジャンプテーブルのヘッドノードを指し、ヘッドノードはジャンプテーブルの特別なノードであり、レベル配列の要素数は64です。ヘッドノードは、順序セットにメンバーとスコアの値を格納せず、eleの値はNULLであり、スコアの値は0です。ジャンプテーブルの全長には含まれていません。ヘッドノードが初期化されると、64個の要素の前方はすべてNULLを指し、スパン値はすべて0になります。
  • tail:ジャンプリストのテールノードをポイントします
  • length:ホップテーブルの長さ。ヘッドノードを除くノードの総数を示します。
  • level:ジャンプテーブルの最大ノードの高さ。

zskiplistの構造を図に示します。
ここに画像の説明を挿入します

zskiplistNode構造

//跳表节点
typedef struct zskiplistNode {
    
    
    sds ele;//用于存储字符串类型的数据
    double score;//分值
    struct zskiplistNode *backward;//后向指针
    struct zskiplistLevel {
    
    //节点所在的层
        struct zskiplistNode *forward;//前向指针
        unsigned int span;//该层向前跨越的节点数量
    } level[];  //节点层结构 数组,每次创建一个跳表节点时,都会随机生成一个[1,32]之间的值作为level数组的大小。
} zskiplistNode;

説明:

  • ele:文字列型データを格納するために使用されます

  • 後方:後方ポインタ。現在のノードの下部にある前のノード、ヘッドノード、および最初のノードのみを指すことができます。後方はNULLを指します。これは、ジャンプテーブルを後方から前方にトラバースするときに使用されます。

  • スコア:ソートされたスコアを保存するために使用されます

  • レベル:柔軟な配列です。各ノードの配列の長さは異なります。ジャンプテーブルノードを生成する場合、1〜64の値がランダムに生成されます。値が大きいほど、発生する可能性は低くなります。

    • forward:このレイヤーの次のノードを指し、テールノードの前方はNULLを指します。
    • span:フォワードが指すノードとこのノードの間の要素の数。スパン値が大きいほど、スキップされるノードが多くなります。

ジャンプテーブルの各ノードの要素には、順序集合のメンバー値が格納され、スコアにはメンバースコア値が格納されます。すべてのノードのスコアは、小さいものから大きいものへと並べ替えられます。順序付けられたセットのメンバーのスコアが同じである場合、ノードはメンバーの辞書式順序で並べ替えられます。

記事は終わりました。お役に立てば幸いです。よろしければ、コメントをお寄せください。

おすすめ

転載: blog.csdn.net/u014494148/article/details/114915016