Disruptorがこんなに速いのはなぜですか

Disruptorはオープンソースで効率的なプロデューサー/コンシューマーフレームワークであり、このフレームワークの機能を直接説明することは困難ですが、Javaとして理解できますBlockingQueueこれを理解する方がはるかに簡単ではありません、これはプロデューサー-コンシューマ・キューが、よりその性能BloockingQueueはるかに優れた単一のマシンとして知られては、TPSの何百万人を持つことができます。

かく乱機能

Disruptorには、次の3つの主要な特性があります。

  • イベントマルチキャスト
  • イベントに事前にメモリを割り当てる
  • ロックフリー操作

通常、キューを使用する場合、キュー内の1つのメッセージのみが消费者使用されますが、Disruptorでは、同じメッセージを複数のコンシューマーが同時に処理でき、複数のコンシューマーは並列です。

可能と同時に、そのような要求データの必要性のような場所で使用されるデータは、複数のログに格納されリモートマシンへのバックアップサービス処理、以下のように:

メッセージを1つのコンシューマーでのみ処理できる場合、上記の3つの処理ロジックを線形で完了するか、コンシューマーに入れて非同期で処理する必要があります。これらの操作を複数のコンシューマーに分割して、同じメッセージ並行して使用できる場合、処理効率は大幅に向上します。

このように、次のメッセージの処理を開始する前に、メッセージがすべてのコンシューマによって処理されることを確認する必要があります。コンシューマは同時に異なるメッセージを処理できないため、JavaのCyclicBarrierなどのツールは、すべてのコンシューマが同時に次のメッセージを処理できるようにする必要があります。 、SequenceBarrierはDisruptorに実装され、この機能を実現します。

Disruptorの目標は、低レイテンシ環境に適用することです。低レイテンシシステムでは、メモリをまったく割り当てる必要がないか、Javaではガベージコレクションによる一時停止時間を減らす必要があります。Disruptorでは、RingBufferを使用してこの目標を達成し、RingBufferでオブジェクトを事前に作成してから、これらのオブジェクトを繰り返し使用してガベージコレクションを回避します。この実装はスレッドセーフです。

Disruptorでは、スレッドセーフティの実装は基本的にロックを使用しませんが、CASなどのロックフリーメカニズムを使用してスレッドセーフティを保証します。

コアコンセプト

Disruptorを正式に理解する前に、いくつかのコアコンセプトを理解する必要があります。Disruptorのコードは複雑ではなく、基本的にこれらのコアコンセプトを中心に展開します。

  • プロデューサー:データのプロデューサー。プロデューサー自体はディスラプターとは何の関係もありません。データを生成する任意のコード、またはforループでもかまいません。
  • イベント:プロデューサーは、ユーザーが自分のニーズに応じて定義できるデータを生成します
  • RingBuffer:イベントのデータ構造を保存し、事前にメモリを割り当て、プログラムの実行中にオブジェクトを作成しないようにするために使用されます
  • EventHandler:ユーザー自身が実装するコンシューマー
  • シーケンス:Disruptorのコンポーネントを識別するために使用され、複数のコンポーネント間の調整はそれに依存します
  • シーケンサー:Disruptorのコアメカニズム。コア同時実行アルゴリズムを実装して、プロデューサーとコンシューマー間でメッセージが正しく配信されるようにします。
  • SequenceBarrier:すべてのコンシューマーが新しいメッセージを同時に処理できるようにするために使用されます
  • WaitStrategy:消費者待機戦略
  • EventProcessor:メッセージをコンシューマーに配信する具体的な実装

上記のコンポーネントがDisruptorを構成し、フレームワーク全体のコードサイズは実際には非常に小さく、7000行未満である必要があり、コードは非常にクリーンです。コードは基本的に継承を使用しませんが、インターフェース指向のプログラミング合成を使用するため、コード間の結合は非常に低くなります。

RingBufferとSequencerは2つの最も重要なコンポーネントであり、前者はメッセージを格納するために使用され、後者はメッセージの正常な生成と消費を制御します。

Disruptorの中心的な目標はプログラムのスループットを改善することです。そのため、プログラムはこれらの目標を中心に達成され、主に次のことを行います。

  • ガベージコレクションを減らす
  • 複数のコンシューマーがメッセージを並行して処理できるようにする
  • ロックフリーアルゴリズムを使用して同時実行性を実現する
  • キャッシュラインの充填

RingBufferはメッセージを格納するためのコンテナであり、内部実装は配列を使用します。

private final Object[] entries;
复制代码

Disruptorを開始する前に、データのサイズを指定し、この配列を初期化する必要があります。

private void fill(EventFactory<E> eventFactory)
{
    for (int i = 0; i < bufferSize; i++)
    {
        entries[BUFFER_PAD + i] = eventFactory.newInstance();
    }
}
复制代码

配列が初期化されると、それはリサイクルされなくなります。すべてのメッセージは、既に作成されているこれらのオブジェクトをリサイクルするため、これは循環配列です。RingBufferの実装を次の図に示します。

次に、循環配列を操作するときは、配列へのプロデューサーとコンシューマーのアクセスを制御する必要があります。一方、Disruptorは複数のプロデューサーと複数のコンシューマーをサポートするため、スレッドの安全性を確保する必要があります。パフォーマンスを確保するために、ロックを使用してスレッドの安全性を確保することはありません(BlockingWaitStrategyのみがロックを使用します)。RingBufferのアクセス制御では、主にCASを使用して完了します。

protected final E elementAt(long sequence)
{
    return (E) UNSAFE.getObject(entries, REF_ARRAY_BASE + ((sequence & indexMask) << REF_ELEMENT_SHIFT));
}
复制代码

一方、プロデューサーとコンシューマーの速度にアクセスする場合、プロデューサーは未使用のメッセージを書き込むことができないため、メッセージが失われます。RingBufferでは、ヘッドポインターとテールポインターは制御に使用されませんが、シーケンスを通じて制御するには、プロデューサがデータを書き込むときに、現在のシリアル番号と書き込まれるデータの量をコンシューマの位置と比較して、書き込むのに十分なスペースがあるかどうかを確認します。

RingBufferには、次のコードがあります。

abstract class RingBufferPad
{
    protected long p1, p2, p3, p4, p5, p6, p7;
}
复制代码

このコードはキャッシュラインフィリングと呼ばれます。これに関しては、CPUのキャッシュメカニズムを理解する必要があります。メモリのアクセス速度がCPUの速度から離れすぎているため、CPUキャッシュもCPUとメモリの間に追加されます。通常、レベル3が追加され、第1レベルと第2レベルはCPUコア専用であり、第3レベルキャッシュは複数のコア間で共有されます。

多くの場合、Javaの最後の変数など、CPUキャッシュに変更されない値をキャッシュして、CPUキャッシュの速度を最大化できるようにしますが、CPUキャッシュには特性があります。意志のCPUのキャッシュラインは、変数が近い変数の最終変更になります定義した場合の単位としては、そう、変数がそれぞれの時間を変更すると、データは、その後、最終的な変数がキャッシュにもはやもあり、再びメモリに書き込まれませんCPUはキャッシュされるため、他のデータがキャッシュされないようにするために、キャッシュラインの前後を埋める必要があります。

abstract class RingBufferPad
{
    // 填充缓存行的前部分
    protected long p1, p2, p3, p4, p5, p6, p7;
}
abstract class RingBufferFields extends RingBufferPad{ 
    ......
    // 下面需要被缓存到 CPU 缓存的数据
    private final long indexMask; 
    private final Object[] entries; 
    protected final int bufferSize;
    protected final Sequencer sequencer; 
    ...... 
}
public final class RingBuffer extends RingBufferFields implements Cursored, EventSequencer, EventSink{ 
    ...... 
    // 填充缓存行的后部分
    protected long p1, p2, p3, p4, p5, p6, p7; 
    ......
}
复制代码

この方法では、RingBufferFieldsのデータがCPUキャッシュに読み込まれた後、メモリから読み取る必要はありません。

Disruptorは、さまざまな方法でパフォーマンスを向上させます。そのため、非常に高速です。

原文

WeChatパブリックアカウントをフォローして、

おすすめ

転載: juejin.im/post/5e99cb25e51d4546ee76cda8