RocketMQとKafkaの最下層の最適化の完全な分析

みなさん、こんにちは。はい。

RocketMQとKafkaのメッセージがディスクに保存されることは誰もが知っていますが、なぜメッセージの保存とディスクの読み取りと書き込みが非常に高速なのですか?最適化を行いましたか?両方の実装に違いはありますか?それぞれの長所と短所は何ですか?

今日は調べます。

ストレージメディア-ディスク

一般的に、メッセージミドルウェアのメッセージはローカルファイルに保存されます。これは、効率の観点から、ローカルファイルを直接配置するのが最も速く、安定性が最も高いためです。結局のところ、データベースなどのサードパーティのストレージに配置すると、セキュリティが1つ少なくなり、ネットワークのオーバーヘッドも発生します。

メッセージをディスクファイルに保存するプロセスのボトルネックは、ディスクの書き込みと読み取りです。ディスクの読み取りと書き込みが比較的遅いことはわかっていますが、ディスクをストレージメディアとして使用して高スループットを実現するにはどうすればよいですか?

シーケンシャル読み取りおよび書き込み

答えは、順次読み取りと書き込みです。

まず、ページキャッシュについて理解しましょう。ページキャッシュは、ディスクI / O操作を減らすためにオペレーティングシステムがディスクとして使用するキャッシュです。

ディスクへの書き込み時には、実際にはページキャッシュに書き込まれるため、ディスクへの書き込みがメモリへの書き込みになります。書き込まれたページはダーティページになり、オペレーティングシステムは必要に応じてダーティページをディスクに書き込みます。

読み取り時に、ページキャッシュがヒットすると、直接戻ります。ページキャッシュが失われると、ページ障害割り込みが発生し、ディスクからページキャッシュにデータをロードしてから、データを返します。

そして、読み取るとき先読みします。局所性の原則に従って、読み取るときは、隣接するディスクブロックがページキャッシュに読み込まれます。書き込み時には、後で書き込まれ、ページキャッシュが書き込まれるため、一部の小さな書き込み操作を大きな書き込みにマージしてから、ディスクをフラッシュできます。

また、ディスクの構造上、ヘッドが車線を変更する必要がほとんどないか、シーケンシャルI / O時の車線変更時間が非常に短い。

インターネットでのいくつかのテスト結果によると、ディスクへの順次書き込みの速度は、メモリへのランダム書き込みよりも高速です。

もちろん、そのような書き込みにはデータ損失のリスクがあります。たとえば、マシンの電源が突然失われた場合、フラッシュされていないダーティページは失われます。しかし、それはfsync強制ブラシプレートと呼ぶことができますが、この損失性能はより大きくなります。

したがって、ディスクを同期的にフラッシュするのではなく、メッセージの信頼性を確保するために、マルチコピーメカニズムを使用することをお勧めします

シーケンシャルI / Oがディスクの構造に適応し、プリリードとポストライトがあることがわかります。RocketMQとKafkaは、どちらもシーケンシャル書き込みと近似シーケンシャル読み取りです。これらはすべて、ファイルの追加を使用してメッセージを書き込みます。新しいメッセージはログファイルの最後にのみ書き込むことができ、古いメッセージは変更できません。

mmap-ファイルメモリマップ

以上のことから、ディスクファイルにアクセスするとページキャッシュにデータが読み込まれますが、ページキャッシュはカーネルスペースに属し、ユーザースペースからはアクセスできないため、ユーザースペースバッファーにデータをコピーする必要があります。

コピープログラムを介してページキャッシュからデータに再度アクセスする必要があることがわかります。したがって、mmapメモリマップファイルを使用してコピーを回避するために、最適化の波を実行できます。

簡単に言うと、ファイルマッピングは、プログラムの仮想ページをページキャッシュに直接マッピングすることであるため、カーネルモードをユーザーモードにコピーする必要がなく、重複データの生成も回避されますまた、呼び出しreadwriteメソッドを介してファイル読み書きする必要がなくなりアドレスとオフセットをマッピングすることで直接操作できます

sendfile-ゼロコピー

メッセージはディスクに保存されているため、消費者がメッセージをプルするようになると、ディスクからメッセージを取得する必要があります。ファイルを送信する一般的なプロセスを見てみましょう。

簡単にDMA言え、そのフルネームはダイレクトメモリアクセスです。CPUの介入なしに、システムメモリを個別直接読み書きでき、グラフィックカードやネットワークカードのように使用できますDMA

データが実際には冗長であることがわかるので、mmap後でファイルを送信するプロセスを見てみましょう

コンテキストスイッチの数は変更されていませんが、データのコピーが少なくなっmmapていることがわかりますこれは、前述の効果と同じです。

しかし、データはまだ冗長です。ページキャッシュからネットワークカードにデータを直接コピーすることはできませんか?sendfileこの効果があります。最初にLinux2.1バージョンを見てみましょうsendfile

比較read + writemmap + writeコンテキスト切り替えのニーズを満たすために送信されるシステム呼び出しであるため、確かに少なくなりますが、それでも冗長なデータのようです。はい、Linux2.4バージョンのsendfile + DMAと「Scatter-gather」。真の冗長性なしを実現します。

これは、JavaFileChannal.transferTo()の下部使用されるゼロコピーと呼ばれることが多いものsendfileです。

次に、上記のポイントがRocketMQとKafkaでどのように適用されるかを見てみましょう。

RocketMQとKafkaのアプリケーション

RocketMQ

採用Topic混合追加方式、つまり、CommitLogファイルには、メッセージがどのトピックのどのキューに属しているかに関係なく、このブローカーに割り当てられたすべてのメッセージが含まれます。

したがって、すべてのメッセージが順番に追加されてCommitLog書き込まれ、メッセージに対応するCosumerQueueが確立されます。次に、コンシューマーはCosumerQueueを介してメッセージの実際の物理アドレスを取得し、CommitLogに移動してメッセージを取得します。CosumerQueueは、メッセージのインデックスとして理解できます。

CommitLogとCosumerQueueはどちらもRocketMQでmmapを採用しています。

メッセージを送信する場合、デフォルトでは、データをヒープメモリにコピーしてから送信します。コードを見てみましょう。

この構成では、transferMsgByHeapデフォルトがtrueであることがわかります。これは、コンシューマープルメッセージコードを見るとわかります。

RocketMQはデフォルトでメッセージをヒープ内のバッファーにコピーし、それを応答本文に詰め込んで送信することがわかります。ただし、ヒープを通過しないようにパラメーターで構成できますが、真のゼロコピーは使用せず、mappedBufferを介してSocketBufferに送信されます。

そのため、RocketMQはシーケンシャルディスク書き込みとmmapを使用します。Sendfileは使用されず、SocketBufferへのページバッファーのコピーがあります。

次に、メッセージをプルすると、厳密に言えば、CommitLogメッセージは混合して保存されるため、CommitLogの読み取りはランダムになります。**しかし、全体として、メッセージはCommitLogから順番に読み取られ、すべて古いデータから新しいデータを順番に読み取ります。**一般的に、メッセージは保存されるとすぐに消費されるため、メッセージはこの時点でもページキャッシュにあるはずなので、ディスクを読み取る必要はありません。

また、ページキャッシュは定期的にフラッシュされますが、これは制御できず、メモリが制限され、スワップなどが発生します ** mmapは実際には単なるマッピングであり、ページが実際に読み取られたときに生成されます。ページ障害が中断された場合にのみ、データが実際にメモリにロードされ、メッセージキューの監視グリッチが発生する可能性があります。

そのため、RocketMQは、ファイルの事前割り当てやファイルの予熱など、いくつかの最適化を行いました

ファイルの事前割り当て

CommitLogのデフォルトサイズは1Gです。サイズ制限を超えると、新しいファイルを準備する必要があります。RocketMQは、AllocateMappedFileService継続的に処理するためにバックグラウンドスレッド開始します。AllocateRequestAllocateRequestは実際には事前に割り当てられたリクエストであり、次のファイル割り当てを事前に準備します。ジッタの原因となるメッセージ書き込みプロセス中のファイル割り当てを防ぐため。

ファイルのウォームアップ

あるwarmMappedFile方法では、それは、現在マップされたファイルの各ページをトラバース0バイトを書き込み、その後、呼び出します、mlockmadvise(MADV_WILLNEED)

もう一度見てみましょうthis.mlock内部は実際にはと呼ばれmlockていmadvise(MADV_WILLNEED)ます。

mlock:物理メモリ内のプロセスで使用されるアドレススペースの一部またはすべてをロックして、プロセスがスワップスペースにスワップされないようにすることができます。

madvise:このファイルは近い将来アクセスされることをオペレーティングシステムに通知するので、事前に数ページを読むことをお勧めします。

RocketMQの概要

シーケンシャルディスク書き込みは、全体として、シーケンシャルディスク読み取りであり、真のゼロコピーではなく、mmapが使用されます。また、ページキャッシュとmmapレイジーロード(アクセス中にページが欠落しているときにデータが実際にロードされる)の不確実性のために、ファイルの事前割り当てとファイルの予熱が使用されます。つまり、各ページは0バイトで書き込まれ、mlock合計がと呼ばれmadvise(MADV_WILLNEED)ます。

カフカ

KafkaのログストレージはRocketMQとは異なり、パーティションとファイルです。

Kafkaのメッセージ書き込みも、単一パーティションの順次書き込みです。パーティションが少ない場合は、全体として順次書き込みと見なされます。ログファイルはmmapを使用しませんが、インデックスファイルはmmapを使用します。しかし、Kafkaはメッセージの送信にゼロコピーを使用しました。

メッセージはネットワークから送信されるため、Mmapは実際にはメッセージの書き込みには役に立ちません。メッセージを送信する場合、SocketBufferへのページバッファのコピーが1つ少ないため、sendfileはmmap + writeよりも効率的です。

Kafkaのメッセージのソースコードを見ると、最後の呼び出しはFileChannel.transferTo、最下層がsendfileであるということです。

Kafkaのソースコードから、mlockやその他の操作の同様のRocketMQは見つかりませんでした。まず、mmapをログに記録してから実際にスワップするのは役に立たないと思うので、Linuxシステムパラメータをvm.swappiness調整できます。ここでは、0ではなく1に設定することをお勧めします。

メモリが本当に不足していると仮定して、0に設定すると、メモリが使い果たされてスワップできない場合、一部のプロセスが突然終了します。1を設定すると、少なくともドラッグできます。適切な監視方法があれば、それを見つける機会を与えることもでき、突然停止することはありません。

RocketMQとKafkaの比較

1つ目は順番に書き込むことですが、RocketMQはすべてのメッセージを1つのファイルに保存し、Kafkaはパーティションごとに1つのファイルを使用します

パーティションごとに1つのファイル移行またはデータ複製に関してより柔軟です。

ただし、パーティション多い場合は、複数のファイル間で書き込みを頻繁に切り替える必要があります。ファイルごとに順番に書き込みますが、グローバルな観点からはランダム書き込みであり、読み取り時も同じです。ランダムな読み取りファイルが1つしかないRocketMQには、この問題はありません。

メッセージの送信の観点から、RocketMQはmmap +書き込みを使用し、予熱して、ページ障害による大きなファイルmmapのパフォーマンスの問題を軽減します。Kafkaはsendfileを使用します。比較的言えば、SocketBufferへのページバッファーのコピーが1つ少ないため、Kafkaの方が送信効率が高いと思います。

また、スワップの問題はシステムパラメータを介して設定することもできます。

やっと

記事に誤りがありましたら、お早めにご連絡ください。そしてメッセージを残してください〜


はい、少しから少しまで、次の記事でお会いしましょう

おすすめ

転載: blog.csdn.net/yessimida/article/details/108973712