web-dev-qa-db-ja.com

複数の同時リクエストがあるNode.jsサーバー、どのように機能しますか?

Node.jsは、シングルスレッド、非同期、ノンブロッキングI/Oであることを私は知っています。私はそれについてたくさん読みました。たとえば、PHPはリクエストごとに1つのスレッドを使用しますが、ノードはすべてに1つのスレッドのみを使用します。

3つのリクエストa、b、cがnode.jsサーバーに同時に到着するとします。これらのリクエストのうち3つは、大きなブロック操作を必要とします。たとえば、すべて同じ大きなファイルを読み取りたい場合です。

次に、要求はどのようにキューに入れられ、ブロッキング操作はどの順序で実行され、応答はどの順序でディスパッチされますか?もちろん、いくつのスレッドを使用しますか?

3つのリクエストについて、リクエストからレスポンスまでの流れを教えてください。

18
Siddharth

つのリクエストの一連のイベントの説明は次のとおりです:

  1. 3つのリクエストがnode.jsWebサーバーに送信されます。
  2. 他の2つのリクエストがWebサーバーのリクエストハンドラーをトリガーする前に部分的に到着するリクエストは、実行を開始します。
  3. 他の2つのリクエストは、node.jsイベントキューに入り、順番を待ちます。技術的には、待機中のリクエストが着信TCP=レベルでキューに入れられるか、またはnode.js内でキューに入れられるか(実際にはわかりません)、node.js実装の内部に依存します。ただし、この説明では、重要なのは、着信イベントがキューに入れられ、最初のリクエストの実行が停止するまでトリガーされないことです。
  4. その最初の要求ハンドラーは、非同期操作(ファイルの読み取りなど)が発生するまで実行され、その後、非同期操作が完了するまで他に何もする必要はありません。
  5. その時点で、非同期ファイルI/O操作が開始され、元の要求ハンドラーが返されます(その時点で実行できることで実行されます)。
  6. (ファイルI/Oを待機している)最初のリクエストが返されたので、node.jsエンジンは次のイベントをイベントキューからプルして開始できます。これは、サーバーに到着する2番目の要求になります。最初のリクエストで同じプロセスを通過し、他に何もすることがなくなるまで実行されます(ファイルI/Oも待機します)。
  7. 2番目の要求がシステムに戻ると(ファイルI/Oを待機しているため)、3番目の要求の実行を開始できます。前の2つと同じパスに従います。
  8. 3番目のリクエストもI/Oを待機してシステムに戻ると、node.jsは次のイベントをイベントキューから自由にプルできます。
  9. この時点で、3つの要求ハンドラーはすべて同時に「処理中」です。実際に一度に実行されるのは1つだけですが、すべてが一度に処理されています。
  10. イベントキュー内のこの次のイベントは、他のイベントまたは他の要求であるか、前の3つのファイルI/O操作のいずれかの完了である可能性があります。キューの次のイベントが実行を開始します。これが最初の要求のファイルI/O操作であるとします。その時点で、その最初の要求のファイルI/O操作に関連付けられている完了コールバックを呼び出し、その最初の要求がファイルI/O結果の処理を開始します。このコードは、リクエスト全体を終了して戻るまで、または他の非同期操作(ファイルI/Oの追加など)を開始して戻るまで実行を続けます。
  11. 最終的に、2番目の要求のファイルI/Oの準備が整い、そのイベントがイベントキューからプルされます。
  12. 次に、3番目のリクエストについても同じで、最終的に3つすべてが終了します。

したがって、実際に同時に実行される要求は1つだけですが、複数の要求が同時に「処理中」または「処理中」になる可能性があります。これは、システムがいつでもスレッドを自由に切り替えることができる複数のネイティブスレッドによる「プリエンプティブ」マルチタスクではなく、協調マルチタスクと呼ばれることもあります。JavaScriptの特定のスレッドは、システムに戻るまで実行され、その後、そしてその時だけ、Javascriptの別の部分が実行を開始できます。 Javascriptの一部は非ブロッキング非同期操作を開始できるため、Javascriptのスレッドは、非同期操作がまだ保留されている間にシステムに戻る(他のJavascriptを実行できるようにする)ことができます。これらの操作が完了すると、イベントキューにイベントがポストされ、他のJavascriptが完了してそのイベントがキューの一番上に来ると、そのイベントが実行されます。

シングルスレッド

ここで重要なのは、Javascriptの特定のスレッドは、システムに戻るまで実行されるということです。実行の過程で非同期操作(ファイルI/Oやネットワークなど)を開始した場合、それらのイベントが終了すると、イベントキューにイベントが配置され、JSエンジンが以前にイベントの実行を完了したときにそのイベントが処理され、コールバックが呼び出され、そのコールバックが実行されます。

このシングルスレッドの性質により、マルチスレッドモデルと比較して同時実行の処理方法が大幅に簡素化されます。すべてのリクエストが独自のスレッドを開始する完全なマルチスレッド環境では、共有を希望するすべてのデータは、単純な変数でも競合状態の影響を受け、誰もがそれを読み取る前にミューテックスで保護する必要があります。

Javascriptでは、複数のリクエストが同時に実行されないため、単純な共有変数アクセスにミューテックスは必要ありません。定義上、Javascriptの1つの部分が変数を読み取っている時点では、その時点で他のJavascriptは実行されていません(シングルスレッド)。

Node.jsはスレッドを使用します

注意すべき技術的な違いの1つは、Javascriptの実行のみがシングルスレッドであるということです。 node.jsの内部では、いくつかの目的でスレッド自体を使用します。たとえば、非同期ファイルI/Oは実際にはネイティブスレッドを使用します。ネットワークI/Oは実際にはスレッドを使用しません(ネイティブのイベント駆動型ネットワークを使用します)。

ただし、node.jsの内部でこのスレッドを使用しても、Javascriptの実行に直接影響することはありません。一度に実行されるJavascriptのスレッドはまだ1つだけです。

競合状態

非同期操作が開始されたときに変更されている状態の競合状態がまだ存在する可能性がありますが、これはマルチスレッド環境よりもはるかに一般的ではなく、これらのケースを識別して保護するのがはるかに簡単です。存在する可能性のある競合状態の例として、インターバルタイマーを使用して10秒ごとに複数の温度プローブから読み取りを行う単純なサーバーがあります。これらすべての温度測定値からデータを収集し、1時間ごとにそのデータをディスクに書き込みます。非同期I/Oを使用してデータをディスクに書き込みます。ただし、データをディスクに書き込むためにいくつかの異なる非同期ファイルI/O操作が使用されるため、これらの非同期ファイルI/O操作の間にインターバルタイマーが発生し、サーバーにあるデータが発生する可能性があります。変更するディスクへの書き込みの途中。これは悪いことであり、一貫性のないデータが書き込まれる可能性があります。単純な世界では、ディスクへの書き込みを開始する前にすべてのデータのコピーを作成することでこれを回避できるため、データのディスクへの書き込み中に新しい温度の読み取り値が入力されても、コピーは影響を受けず、コードは影響を受けません。一貫したデータのセットをディスクに書き込みます。ただし、このサーバーの場合、データが大きく、サーバー上のメモリが小さいため(Raspberry Piサーバーです)、すべてのデータのメモリ内コピーを作成することは現実的ではありません。

したがって、データがディスクに書き込まれているときにフラグを設定し、データがディスクに書き込まれているときにフラグをクリアすることで、問題が解決されます。このフラグが設定されているときにインターバルタイマーが起動すると、新しいデータは別のキューに入れられ、ディスクへの書き込み中のコアデータは変更されません。データのディスクへの書き込みが完了すると、キューがチェックされ、検出された温度データがメモリ内の温度データに追加されます。ディスクへの書き込みプロセスの整合性が維持されます。私のサーバーは、この「競合状態」が発生し、それが原因でデータがキューに入れられるたびに、イベントをログに記録します。そして、見よ、それはたまに起こり、データの整合性を維持するためのコードは機能します。

46
jfriend00