みなさん、こんにちは。はい。
RocketMQとKafkaのメッセージがディスクに保存されることは誰もが知っていますが、なぜメッセージの保存とディスクの読み取りと書き込みが非常に高速なのですか?最適化を行いましたか?両方の実装に違いはありますか?それぞれの長所と短所は何ですか?
今日は調べます。
ストレージメディア-ディスク
一般的に、メッセージミドルウェアのメッセージはローカルファイルに保存されます。これは、効率の観点から、ローカルファイルを直接配置するのが最も速く、安定性が最も高いためです。結局のところ、データベースなどのサードパーティのストレージに配置すると、セキュリティが1つ少なくなり、ネットワークのオーバーヘッドも発生します。
メッセージをディスクファイルに保存するプロセスのボトルネックは、ディスクの書き込みと読み取りです。ディスクの読み取りと書き込みが比較的遅いことはわかっていますが、ディスクをストレージメディアとして使用して高スループットを実現するにはどうすればよいですか?
シーケンシャル読み取りおよび書き込み
答えは、順次読み取りと書き込みです。
まず、ページキャッシュについて理解しましょう。ページキャッシュは、ディスクI / O操作を減らすためにオペレーティングシステムがディスクとして使用するキャッシュです。
ディスクへの書き込み時には、実際にはページキャッシュに書き込まれるため、ディスクへの書き込みがメモリへの書き込みになります。書き込まれたページはダーティページになり、オペレーティングシステムは必要に応じてダーティページをディスクに書き込みます。
読み取り時に、ページキャッシュがヒットすると、直接戻ります。ページキャッシュが失われると、ページ障害割り込みが発生し、ディスクからページキャッシュにデータをロードしてから、データを返します。
そして、読み取るときは先読みします。局所性の原則に従って、読み取るときは、隣接するディスクブロックがページキャッシュに読み込まれます。書き込み時には、後で書き込まれ、ページキャッシュが書き込まれるため、一部の小さな書き込み操作を大きな書き込みにマージしてから、ディスクをフラッシュできます。
また、ディスクの構造上、ヘッドが車線を変更する必要がほとんどないか、シーケンシャルI / O時の車線変更時間が非常に短い。
インターネットでのいくつかのテスト結果によると、ディスクへの順次書き込みの速度は、メモリへのランダム書き込みよりも高速です。
もちろん、そのような書き込みにはデータ損失のリスクがあります。たとえば、マシンの電源が突然失われた場合、フラッシュされていないダーティページは失われます。しかし、それはfsync
強制ブラシプレートと呼ぶことができますが、この損失性能はより大きくなります。
したがって、ディスクを同期的にフラッシュするのではなく、メッセージの信頼性を確保するために、マルチコピーメカニズムを使用することをお勧めします。
シーケンシャルI / Oがディスクの構造に適応し、プリリードとポストライトがあることがわかります。RocketMQとKafkaは、どちらもシーケンシャル書き込みと近似シーケンシャル読み取りです。これらはすべて、ファイルの追加を使用してメッセージを書き込みます。新しいメッセージはログファイルの最後にのみ書き込むことができ、古いメッセージは変更できません。
mmap-ファイルメモリマップ
以上のことから、ディスクファイルにアクセスするとページキャッシュにデータが読み込まれますが、ページキャッシュはカーネルスペースに属し、ユーザースペースからはアクセスできないため、ユーザースペースバッファーにデータをコピーする必要があります。
コピープログラムを介してページキャッシュからデータに再度アクセスする必要があることがわかります。したがって、mmap
メモリマップファイルを使用してコピーを回避するために、最適化の波を実行できます。
簡単に言うと、ファイルマッピングは、プログラムの仮想ページをページキャッシュに直接マッピングすることであるため、カーネルモードをユーザーモードにコピーする必要がなく、重複データの生成も回避されます。また、呼び出しread
やwrite
メソッドを介してファイルを読み書きする必要がなくなり、アドレスとオフセットをマッピングすることで直接操作できます。
sendfile-ゼロコピー
メッセージはディスクに保存されているため、消費者がメッセージをプルするようになると、ディスクからメッセージを取得する必要があります。ファイルを送信する一般的なプロセスを見てみましょう。
簡単にDMA
言えば、そのフルネームはダイレクトメモリアクセスです。CPUの介入なしに、システムメモリを個別に直接読み書きでき、グラフィックカードやネットワークカードのように使用できますDMA
。
データが実際には冗長であることがわかるので、mmap
後でファイルを送信するプロセスを見てみましょう。
コンテキストスイッチの数は変更されていませんが、データのコピーが少なくなっmmap
ていることがわかります。これは、前述の効果と同じです。
しかし、データはまだ冗長です。ページキャッシュからネットワークカードにデータを直接コピーすることはできませんか?sendfile
この効果があります。最初にLinux2.1バージョンを見てみましょうsendfile
。
比較read + write
やmmap + 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バイトを書き込み、その後、呼び出します、mlock
とmadvise(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の方が送信効率が高いと思います。
また、スワップの問題はシステムパラメータを介して設定することもできます。
やっと
記事に誤りがありましたら、お早めにご連絡ください。そしてメッセージを残してください〜
はい、少しから少しまで、次の記事でお会いしましょう。