Java Gangの古典的なインタビューの質問のレビュー:Redisはなぜそれほど速いのですか?

ソース:
https ://juejin.cn/post/6978280894704386079

序文

みなさん、こんにちは。Redisは非常に高速で、QPSは100,000(1秒あたりのリクエスト数)に達する可能性があることは誰もが知っています。なぜRedisはとても速いのですか?この記事はあなたと一緒に学びます。

メモリに基づく

メモリの読み取りと書き込みは、ディスクの読み取りと書き込みよりもはるかに高速であることは誰もが知っています。Redisは、メモリストレージに基づいて実装されたデータベースであり、データがディスクに保存されているデータベースと比較して、ディスクI/Oの消費を節約します。MySQLなどのディスクデータベースはクエリの効率を上げるためにインデックスを作成する必要がありますが、Redisデータはメモリに保存され、メモリ内で直接動作するため、非常に高速です。

効率的なデータ構造

MySQLインデックスは、効率を向上させるためにB+ツリーのデータ構造を選択することを知っています。実際、合理的なデータ構造により、アプリケーション/プログラムを高速化できます。まず、Redisのデータ構造と内部コーディング図を見てみましょう。

SDSシンプルダイナミックストリング

struct sdshdr { //SDS简单动态字符串
    int len;    //记录buf中已使用的空间
    int free;   // buf中空闲空间长度
    char buf[]; //存储的实际内容
}

文字列の長さの処理

C言語では、カタツムリを拾った男の子の文字列の長さを取得するには、最初からトラバースする必要があり、複雑さはO(n)です。Redisには、記録するlenフィールドがすでにあります。現在の文字列の長さ、直接取得できます。つまり、時間計算量はO(1)です。

メモリの再割り当ての数を減らします

C言語では、文字列を変更するにはメモリを再割り当てする必要があります。変更の頻度が高いほど、メモリが割り当てられる頻度が高くなり、メモリの割り当てによってパフォーマンスが低下します。Redisでは、SDSは2つの最適化戦略を提供します。スペースの事前割り当てと遅延スペースの解放です。

スペースの事前割り当て

SDSの単純な動的文字列変更とスペース拡張では、必要なメモリスペースの割り当てに加えて、追加の未使用スペースが割り当てられます。配布ルールはサワードウです:

SDSの変更後、lenの長さが1M未満の場合、lenと同じ長さの未使用のスペースが追加で割り当てられます。たとえば、len = 100の場合、再割り当て後、bufの実際の長さは100(使用済みスペース)+ 100(余分なスペース)+ 1(ヌル文字)=201になります。

SDSが変更された後、lenの長さが1Mより大きい場合、プログラムは1Mの未使用スペースを割り当てます。

不活性空間の解放

SDSが短縮された場合、余分なメモリスペースを再利用する代わりに、freeを使用して余分なスペースを記録します。後続の変更操作では、空き領域を直接使用して、メモリ割り当てを削減します。

ハッシュ

KVインメモリデータベースとして、Redisはグローバルハッシュを使用してすべてのキーと値のペアを格納します。このハッシュテーブルは複数のハッシュバケットで構成されています。ハッシュバケットのエントリ要素には、*keyおよび*valueポインターが格納されます。ここで、* keyは実際のキーを指し、*valueは実際の値を指します。

ハッシュテーブルのルックアップレートは非常に高速で、JavaのHashMapにいくらか似ています。これにより、 O(1) 時間計算量のキーと値のペアをすばやく見つけることができます。最初にキーでハッシュ値を計算し、対応するハッシュバケットの場所を見つけてから、エントリを見つけ、エントリ内の対応するデータを見つけます。

一部の友人は疑問を抱くかもしれません。ハッシュテーブルに大量のデータを書き込むと、ハッシュの衝突の問題が発生し、効率が低下します。

ハッシュの衝突: 同じハッシュ値が異なるキーを介して計算されるため、同じハッシュバケットが生成されます。

Redisは、連鎖ハッシュを使用してハッシュの衝突を解決します。連鎖ハッシュとは、同じハッシュバケット内で、複数の要素がリンクリストに格納され、それらがポインターによって順番に接続されることを意味します。

一部の友人はまだ質問があるかもしれません:ハッシュ衝突チェーンの要素は、ポインターを介して1つずつ検索してから操作することしかできません。ハッシュテーブルに大量のデータが挿入されると、競合が多くなり、競合リンクリストが長くなり、クエリの効率が低下します。

効率を維持するために、Redisはハッシュテーブルに対して再ハッシュ操作を実行します。つまり、ハッシュバケットを追加し、競合を減らします。再ハッシュをより効率的にするために、Redisはデフォルトで2つのグローバルハッシュテーブルも使用します。1つはメインハッシュテーブルと呼ばれる現在の使用用で、もう1つはスタンバイハッシュテーブルと呼ばれる拡張用です。

ジャンプテーブル

ジャンプテーブルは、Redisに固有のデータ構造であり、実際には、検索効率を向上させるためにリンクリストに追加されるマルチレベルのインデックスです。スキップテーブルの簡単な回路図は次のとおりです。

  • 各レベルには順序付けられたリンクリストがあり、一番下のリンクリストにはすべての要素が含まれています。
  • スキップテーブルは、平均O(logN)、最悪のO(N)ノードルックアップをサポートし、順次操作を通じてノードをバッチ処理できます。

ziplist ziplist

圧縮リストziplistは、リストキーとディクショナリキーの低レベルの実装の1つです。これは、特別にコード化された一連のメモリブロックで構成されるリストです。ziplistには複数のエントリを含めることができ、各エントリには、次のように文字配列または制限された長さの整数を含めることができます。

  • zlbytes:圧縮リスト全体が占めるメモリのバイト数を記録します
  • zltail:テールノードから開始ノードまでのオフセット
  • zllen:圧縮リスト全体に含まれるノードの数を記録します
  • entryX:圧縮リストに含まれる各ノード
  • zlend:特別な値0xFF(10進数で255)、圧縮リストの終わりを示すために使用されます

メモリは連続して割り当てられるため、トラバーサルは高速です。

合理的なデータエンコーディング

Redisはさまざまな基本データ型をサポートしており、各基本型は異なるデータ構造に対応し、各データ構造は異なるエンコーディングに対応しています。パフォーマンスを向上させるために、Redisの設計者は、データ構造が最も適切なエンコーディングの一致であると結論付けました。

Redisはオブジェクト(redisObject)を使用してデータベース内のキー値を表します.Redisでキー値ペアを作成すると、少なくとも2つのオブジェクトが作成され、1つのオブジェクトがキー値ペアとして使用されるキーオブジェクトであり、もう1つはキー値ペアの値オブジェクト。

typedef struct redisObject{
    //类型
   unsigned type:4;
   //编码
   unsigned encoding:4;
   //指向底层数据结构的指针
   void *ptr;
    //...
 }robj;

redisObjectでは、type は、Stringオブジェクト、Listオブジェクト、Hashオブジェクト、Setオブジェクト、zsetオブジェクトなどのオブジェクトタイプに対応します。エンコーディングはエンコーディング に対応します。

  • 文字列:数値が格納されている場合はint型でエンコードされ、39バイト以下の非デジタル文字列が格納されている場合はembstrになり、39バイトより大きい場合はrawエンコードになります。
  • リスト:リスト内の要素数が512未満の場合、リストの各要素の値は64バイト未満(デフォルト)、ziplistエンコーディングを使用します。それ以外の場合は、linkedlistエンコーディングを使用します。
  • ハッシュ:ハッシュタイプ要素の数は512未満です。すべての値が64バイト未満の場合は、ziplistエンコーディングを使用します。それ以外の場合は、ハッシュテーブルエンコーディングを使用します。
  • セット:セット内の要素がすべて整数で、要素の数が512未満の場合は、intsetエンコーディングを使用します。それ以外の場合は、ハッシュテーブルエンコーディングを使用します。
  • Zset:順序セット内の要素の数が128未満で、各要素の値が64バイト未満の場合は、ziplistエンコーディングを使用します。それ以外の場合は、skiplist(スキップテーブル)エンコーディングを使用します。

合理的なスレッドモデル

シングルスレッドモデル:コンテキスト切り替えを回避します

Redisはシングルスレッドです。つまり、RedisネットワークIOとキーと値のペアの読み取りと書き込みは1つのスレッドで実行されます。ただし、永続化、非同期削除、クラスターデータ同期など、Redisの他の機能は、実際には追加のスレッドによって実行されます。

Redisのシングルスレッドモデルは、CPUの不要なコンテキスト切り替え競合するロックの消費を回避します。シングルスレッドであるため、コマンドの実行時間が長すぎると(hgetallコマンドなど)、ブロックが発生します。Redisは高速実行シナリオ用のインメモリデータベースであるため、lrange、smembers、hgetallなどのコマンドの使用には注意が必要です。

コンテキストスイッチングとは何ですか?例えば:

たとえば、英語の小説を読んでいて、特定のページが表示され、読めない単語を見つけ、ブックマークを追加して、辞書に移動するとします。辞書を調べた後、戻ってブックマークから読み始めます。プロセスは非常に快適です。

この本を一人で読んでいれば大丈夫です。しかし、あなたが辞書に行き、誰かがあなたの本をめくって逃げてしまうと。あなたが戻ってきて、その本があなたが見ていたページではないことに気付いたとき、あなたはあなたのページを見つけるために時間をかけなければなりません。

本の場合、自分で見てラベルを付けるのは問題ありませんが、多くの人がめくっていると、本のさまざまなラベルが乱雑になります。この説明は大雑把かもしれませんが、真実は同じであるはずです。

I/O多重化

I / O多重化とは何ですか?

  • I/O :网络 I/O
  • マルチプレックス:複数のネットワーク接続
  • 再利用:同じスレッドを再利用します。
  • IO多重化は、実際には同期IOモデルであり、スレッドが複数のファイルハンドルを監視できるようにします。ファイルハンドルの準備ができると、対応する読み取りおよび書き込み操作を実行するようにアプリケーションに通知できます。ファイルハンドルの準備ができていない場合は、ブロックされます。アプリケーションとcpuを渡します。

複数のI/O多重化テクノロジーにより、単一のスレッドで複数の接続要求を効率的に処理できます。Redisは、I/O多重化テクノロジーの実装としてepollを使用します。また、Redis独自のイベント処理モデルは、ネットワークI / Oに多くの時間を浪費することなく、接続、読み取りと書き込み、およびepollのクローズをイベントに変換します。

仮想メモリメカニズム

Redisは、VMメカニズムを独自に直接構築します。一般的なシステムのように処理するためにシステム関数を呼び出すことはなく、移動や要求に一定の時間を浪費します。

Redisの仮想メモリメカニズムとは何ですか?

仮想メモリメカニズムは、アクセス頻度の低いデータ(コールドデータ)をメモリからディスクに一時的にスワップすることで、アクセスが必要な他のデータ(ホットデータ)のために貴重なメモリスペースを解放します。VM機能により、ホットデータとコールドデータを分離できるため、ホットデータはメモリ内に残り、コールドデータはディスクに保存されます。このようにして、メモリ不足によるアクセス速度の低下の問題を回避できます。

おすすめ

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