DUICUO

Kafka がなぜ高速なのかが分かりました。

Kafka は、メッセージ キュー (MQ) として使用する場合でも、ストレージ層として使用する場合でも、基本的に 2 つの機能を持ちます。1 つ目は、プロデューサーによって生成されたデータがブローカーに保存され、2 つ目は、コンシューマーがブローカーからデータを読み取ります。

[[337633]]
Pexelsからの画像

Kafka の速度は、読み取りと書き込みの操作に反映されています。Kafka がなぜこれほど高速なのか、その理由を説明しましょう。

[[337634]]

パーティションを使用した並列処理

Kafka は Pub-Sub メッセージング システムであり、公開とサブスクライブの両方でトピックを指定する必要があることは周知の事実です。

トピックは単なる論理的な概念です。各トピックには1つ以上のパーティションが含まれており、異なるパーティションは異なるノードに存在する場合があります。

一方、異なるパーティションを異なるマシンに配置できるため、クラスタリングによる利点を最大限に活用して、マシン間の並列処理を実現できます。

一方、各パーティションは物理的にフォルダに対応しているため、同一ノード上に複数のパーティションが存在する場合でも、設定により同一ノード上の異なるパーティションを異なるディスク上に配置することができ、ディスク間の並列処理を実現し、複数ディスクの利点を最大限に発揮することができます。

並列処理は確実に速度を向上させます。複数のワーカーは1つのワーカーよりも確実に高速です。異なるディスクに並列で書き込むことは可能でしょうか?また、ディスクの読み書き速度を制御することは可能でしょうか?ディスクI/Oの基本的な概念について簡単に説明しましょう。

ハードドライブのパフォーマンスを制限する要因は何ですか?ディスクI/O特性に基づいてシステム設計をどのように行うべきでしょうか?

ハードドライブの主な内部コンポーネントは、ディスクプラッター、ドライブアーム、読み取り/書き込みヘッド、そしてスピンドルモーターです。実際のデータはプラッターに書き込まれ、読み取りと書き込みは主にドライブアーム上の読み取り/書き込みヘッドによって行われます。

実際の動作では、スピンドルがディスク プラッタを回転させ、ドライブ アームが伸びて読み取りヘッドがプラッタ上で読み取りおよび書き込み操作を実行できるようになります。

ディスクの物理構造は次の図に示されています。

プラッター1枚の容量には限りがあるため、ハードドライブは通常2枚以上のプラッターで構成されています。各プラッターは両面に情報を記録できるため、1枚のプラッターは2つの読み取り/書き込みヘッドに対応します。

ディスクは多数の扇形の領域に分割されており、それぞれをセクターと呼びます。ディスク表面では、ディスクの中心を中心とする半径の異なる同心円をトラックと呼び、異なるディスク上の同じ半径のトラックが形成する円筒をシリンダーと呼びます。

トラックとシリンダーはどちらも異なる半径の円を表し、多くの場合、トラックとシリンダーは同じ意味で使用できます。

ディスク プラッターの垂直ビューを次の図に示します。

ディスクパフォ​​ーマンスに影響を与える重要な要素は、ディスクサービス時間、つまりディスクがI/O要求を完了するまでの時間です。これは、シーク時間、回転待ち時間、データ転送時間の3つの要素で構成されます。

機械式ハードドライブ(HDD)はシーケンシャルリード/ライト性能に優れていますが、ランダムリード/ライト性能は劣ります。これは主に、リード/ライトヘッドが正しいトラックに移動するのに時間がかかるためです。ランダムリード/ライト動作中は、ヘッドを常に動かす必要があり、ヘッドシークに時間がかかり、結果としてパフォーマンスが低下します。ハードドライブを評価するための重要な指標は、IOPSとスループットです。

Kafka や HBase などの多くのオープンソース フレームワークは、追加専用書き込みを使用してランダム I/O を可能な限りシーケンシャル I/O に変換し、シーク時間と回転待ち時間を削減して IOPS を最大化します。

興味のある学生はディスクI/O [1] を参照してください。ディスクの読み書き速度は、シーケンシャル読み書きかランダム読み書きか、使用方法によって異なります。

シーケンシャルディスク書き込み

Kafka では、各パーティションは順序付けられた不変のメッセージシーケンスです。新しいメッセージはパーティションの末尾に継続的に追加されます。これをシーケンシャル書き込みと呼びます。

かなり昔に「1 秒あたり 200 万回の書き込み(3 台の安価なマシンで)」というベンチマーク テストが実行されました。

  1. http://ifeve.com/benchmarking-apache-kafka-2-million-writes-second-three-cheap-machines/

ディスク容量の制限により、すべてのデータを保存することはできません。実際、メッセージングシステムであるKafkaは、すべてのデータを保存する必要はなく、古いデータを削除する必要があります。

さらに、順次書き込みメカニズムにより、Kafka がさまざまな削除戦略を使用してデータを削除する場合、「読み取り/書き込み」モードを使用してファイルを変更するのではなく、パーティションを複数のセグメントに分割します。

各セグメントは物理ファイルに対応しており、ファイル全体を削除するとパーティション内のデータが削除されます。

古いデータを消去するこの方法では、ファイルへのランダム書き込み操作も回避されます。

ページキャッシュを最大限に活用する

キャッシュ層を導入する目的は、Linuxオペレーティングシステムにおけるディスクアクセスのパフォーマンスを向上させることです。キャッシュ層は、ディスク上のデータの一部をメモリにキャッシュします。

データ要求が到着すると、データがキャッシュ内に存在し、かつ最新であれば、ユーザープログラムに直接渡されます。これにより、基盤となるディスクに対する操作が不要になり、パフォーマンスが向上します。キャッシュ層は、ディスクIOPSが200を超える主な理由の1つです。

Linux実装では、ファイルキャッシュはページキャッシュとバッファキャッシュの2層に分かれています。各ページキャッシュには複数のバッファキャッシュが含まれています。

ページ キャッシュは主に、プロセスがファイルの読み取り/書き込み操作を実行するときに、ファイル システム上のファイル データのキャッシュとして使用されます。

バッファ キャッシュは主に、ブロック デバイスからの読み取りやブロック デバイスへの書き込み時にブロック データをキャッシュするシステムで使用するために設計されています。

ページキャッシュを使用する利点:

  • I/O スケジューラは、連続する小さな書き込みブロックをより大きな物理書き込みブロックに組み立てることで、パフォーマンスを向上させます。
  • I/O スケジューラは一部の書き込み操作の順序を変更し、ディスク ヘッドの移動時間を短縮します。
  • 空きメモリ(JVM以外のメモリ)を最大限に活用してください。アプリケーションレベルのキャッシュ(JVMヒープメモリなど)を使用すると、GCの負荷が増加します。
  • 読み取り操作はページキャッシュ内で直接実行できます。消費速度と生成速度が同程度であれば、物理ディスクを介してデータを交換する必要がない場合もあります(ページキャッシュを介して直接交換できます)。
  • プロセスが再起動すると、JVM キャッシュは無効になりますが、ページ キャッシュは引き続き利用できます。

ブローカーはデータを受信すると、ディスクに書き込む前にページキャッシュにデータを書き込むだけです。データが完全にディスクに書き込まれることを保証するものではありません。そのため、マシンがクラッシュした場合、ページキャッシュ内のデータがディスクに書き込まれず、データ損失が発生する可能性があります。

ただし、このような損失は、マシンの電源喪失など、オペレーティングシステムが動作を停止した場合にのみ発生します。これらのシナリオは、Kafka レベルのレプリケーションメカニズムによって完全に解決できます。

このような状況でデータの損失を防ぐためにページ キャッシュ内のデータを強制的にディスクにフラッシュすると、実際にはパフォーマンスが低下します。

このため、Kafka ではページ キャッシュ内のデータを強制的にディスクにフラッシュするための flush.messages および flush.ms パラメータを提供していますが、Kafka ではこれらの使用は推奨されていません。

ゼロコピー技術

Kafka では、大量のネットワークデータをディスクに永続化(プロデューサーからブローカーへ)し、ネットワーク経由でディスクファイルを転送(ブローカーからコンシューマーへ)します。このプロセスのパフォーマンスは、Kafka 全体のスループットに直接影響します。

オペレーティングシステムの中核はカーネルであり、通常のアプリケーションから独立しています。カーネルは保護されたメモリ空間にアクセスでき、また、基盤となるハードウェアデバイスへのアクセス権限も持っています。

ユーザー プロセスがカーネルを直接操作することを防ぎ、カーネルのセキュリティを確保するために、オペレーティング システムは仮想メモリをカーネル空間とユーザー空間の 2 つの部分に分割します。

従来のLinuxシステムでは、標準I/Oインターフェース(読み取りや書き込みなど)はデータのコピー操作に基づいています。つまり、I/O操作はカーネルアドレス空間バッファとユーザーアドレス空間バッファ間でデータのコピーを引き起こします。そのため、標準I/OはキャッシュI/Oとも呼ばれます。

このアプローチの利点は、要求されたデータが既にカーネルのキャッシュに格納されている場合、実際のI/O操作を削減できることです。ただし、欠点は、データのコピープロセスによってCPUオーバーヘッドが発生することです。

Kafkaの生成と消費を次の2つのプロセスに簡略化します[2]。

  • ネットワーク データはディスクに保存されます (プロデューサーからブローカーへ)。
  • ディスク ファイルはネットワーク経由で送信されます (ブローカーからコンシューマーへ)。

① ネットワークデータをディスクに保存する(プロデューサーからブローカーへ)

従来のモデルでは、ネットワークからファイルにデータを転送するには、4 つのデータ コピー、4 つのコンテキスト スイッチ、および 2 つのシステム コールが必要です。

  1. data = socket.read ( ) // ネットワークデータを読み取る
  2. ファイル file = new File()
  3. file.write(data) // ディスクに保存
  4. ファイル.フラッシュ()

このプロセスには、実際には 4 つのデータのコピーが含まれます。

  • まず、DMA コピーを使用してネットワーク データがカーネル モードのソケット バッファーにコピーされます。
  • 次に、アプリケーションはカーネル モード バッファーからユーザー モード (CPU コピー) にデータを読み取ります。
  • 次に、ユーザー プログラムはユーザー モード バッファーをカーネル モードにコピーします (CPU コピー)。
  • 最後に、DMA コピーを使用してデータがディスク ファイルにコピーされます。

DMA (ダイレクト メモリ アクセス): DMA は、CPU の介入なしに周辺機器とシステム メモリ間の双方向データ転送を可能にするハードウェア メカニズムです。

DMA を使用すると、システム CPU を実際の I/O データ転送プロセスから解放できるため、システムのスループットが大幅に向上します。

同時に、次の図に示すように 4 つのコンテキスト スイッチが行われます。

ディスクへのデータの永続化は通常リアルタイムではなく、これはKafkaプロデューサーのデータの永続化にも当てはまります。Kafkaデータはリアルタイムでディスクに書き込まれるのではなく、最新のオペレーティングシステムのページングストレージを最大限に活用して、メモリ(前のセクションで説明したページキャッシュ)を使用したI/O効率を向上させます。

Kafka では、プロデューサーによって生成されたデータはブローカーに保存されます。このプロセスでは、ソケットバッファから読み取られたネットワークデータは、実際にはカーネル空間内のディスクに直接書き込まれる可能性があります。

ソケット バッファーからアプリケーション プロセス バッファーにネットワーク データを読み込む必要はありません。ここで、アプリケーション プロセス バッファーは実際にはブローカーであり、ブローカーは永続化のためにプロデューサーからデータを受信します。

この特定のシナリオでは、ソケット バッファーからネットワーク データを受信し、アプリケーション プロセスが中間処理なしでそれを直接保存する必要がある場合、mmap メモリ ファイル マッピングを使用できます。

メモリ マップ ファイル (mmap、MMFile とも呼ばれます): mmap の目的は、カーネル内の読み取りバッファーのアドレスをユーザー バッファーにマップすることです。

これにより、カーネル バッファとアプリケーション メモリの共有が可能になり、カーネル読み取りバッファからユーザー バッファにデータをコピーする必要がなくなります。

その動作原理は、オペレーティングシステムのPageオブジェクトを直接使用して、ファイルを物理メモリに直接マッピングすることです。マッピングが完了すると、物理メモリ上の操作がハードドライブに同期されます。

この方法を使用すると、ユーザー空間からカーネル空間へのコピーのオーバーヘッドが排除され、I/O が大幅に改善されます。

mmapには重大な欠点があります。それは信頼性が低いことです。mmapに書き込まれたデータは、実際にはハードドライブに書き込まれません。オペレーティングシステムは、プログラムがFlushを明示的に呼び出した場合にのみ、データをハードドライブに書き込みます。

Kafka は、プロアクティブにフラッシュするかどうかを制御するためのパラメータ `producer.type` を提供します。Kafka が mmap に書き込んだ直後にフラッシュして Producer に戻る場合、同期と呼ばれます。

Producer は、Flush を呼び出さずに mmap に書き込んだ直後に戻ります。これは非同期 (async) と呼ばれますが、デフォルトは同期です。

ゼロコピー技術とは、コンピュータが操作を実行するときに CPU がメモリ領域間でデータをコピーする必要がないため、コンテキストの切り替えと CPU のコピー時間が短縮される技術を指します。

その機能は、ネットワーク デバイスからユーザー プログラム空間へのデータグラムの送信中にデータのコピーとシステム コールの数を減らし、それによって CPU の関与をゼロにし、この点での CPU の負荷を完全に排除することです。

現在、ゼロコピー技術には主に3つの種類があります[3]。

  • ダイレクトI/O:データはカーネルを直接通過し、ユーザーアドレス空間とI/Oデバイス間で転送されます。カーネルは、仮想メモリの設定など、必要な補助タスクのみを実行します。
  • カーネルとユーザー空間の間でデータをコピーしないようにする: アプリケーションがデータにアクセスする必要がない場合は、mmap、sendfile、splice && tee、および sockmap を使用してカーネル空間からユーザー空間にデータをコピーすることで、データのコピーを回避できます。
  • コピーオンライト: この技術により、事前にデータをコピーするのではなく、変更が必要な場合にのみデータをコピーできます。

② ディスクファイルはネットワーク経由で送信されます(ブローカーからコンシューマーへ)。

従来の方法では、最初にディスクから読み取り、次にソケット経由で送信しますが、実際には 4 つのコピーが行われます。

  1. バッファ= File.read   
  2. ソケット.send(バッファ)

このプロセスは、上で説明したメッセージ生成プロセスと比較できます。

  • まず、ファイル データはシステム コールを介してカーネル モード バッファー (DMA コピー) に読み込まれます。
  • 次に、アプリケーションはメモリ状態バッファからユーザー状態バッファ (CPU コピー) にデータを読み取ります。
  • 次に、ユーザー プログラムがソケット経由でデータを送信すると、そのデータがユーザー モード バッファーからカーネル モード バッファーにコピーされます (CPU コピー)。
  • 最後に、データは DMA コピーを介して NIC バッファにコピーされます。

Linux 2.4以降のカーネルは、Sendfileシステムコールを通じてゼロコピー機能を提供しています。データはDMA経由でカーネルモードバッファにコピーされ、その後DMA経由でNICバッファに直接コピーされるため、CPUによるコピーは不要です。これが「ゼロコピー」という用語の由来です。

データのコピーが減るだけでなく、ファイルの読み取りとネットワーク送信全体が 1 回の Sendfile 呼び出しで完了し、プロセス全体でコンテキストの切り替えが 2 回だけになるため、パフォーマンスが大幅に向上します。

ここでの Kafka のアプローチは、NIO の transferTo/transferFrom メソッドを通じてオペレーティング システムの Sendfile を呼び出すことによってゼロコピーを実現することです。

合計 2 回のカーネル データ コピー、2 回のコンテキスト スイッチ、および 1 回のシステム コールが発生し、CPU データ コピーが排除されました。

バッチ処理

多くの場合、システムのボトルネックとなるのは CPU やディスクではなく、ネットワーク I/O です。

したがって、オペレーティング システムによって提供される低レベルのバッチ処理に加えて、Kafka クライアントとブローカーは、ネットワーク経由でデータを送信する前に、複数のレコード (読み取りと書き込みを含む) をバッチで蓄積します。

レコードのバッチ処理により、ネットワーク ラウンドトリップのオーバーヘッドが軽減され、より大きなデータ パケットが使用されるため、帯域幅の使用率が向上します。

データ圧縮

プロデューサーはブローカーに送信する前にデータを圧縮することで、ネットワーク転送コストを削減できます。現在サポートされている圧縮アルゴリズムには、Snappy、Gzip、LZ4などがあります。データ圧縮は通常、最適化手法としてバッチ処理と組み合わせて使用​​されます。

次回、面接官に Kafka が高速な理由を聞かれたら、こう答えようと思います。

  • パーティションを並列処理します。
  • ディスクの機能を最大限に活用するには、ディスクに順番に書き込みます。
  • 最新のオペレーティング システムのページ キャッシュを利用し、メモリを活用して I/O 効率を向上させます。
  • ゼロコピー技術を採用しています。プロデューサーによって生成されたデータはブローカーに永続化され、mmap ファイル マッピングを使用して高速な順次書き込みが実現されます。カスタマーはブローカーからデータを読み取り、Sendfile を使用してディスク ファイルを OS カーネル バッファーに読み取り、その後ネットワーク転送のために NIO バッファーに転送することで、CPU 消費を削減します。

参考文献:

  • Meituan – ディスクI/Oのすべて

https://tech.meituan.com/2017/05/19/about-desk-io.html

  • Kafkaゼロコピー

https://zhuanlan.zhihu.com/p/78335525

  • Linux - ゼロコピー

https://cllc.fun/2020/03/18/linux-zero-copy/

著者: 偽物ではない

編集者:タオ・ジアロン

出典:WeChat公式アカウントJavaKeeper(ID:JavaKeeper)より転載