一般に、Node.jsは10,000の同時リクエストをどのように処理しますか?


395

Node.jsがシングルスレッドとイベントループを使用して、一度に1つだけ処理するリクエストを処理することを理解しています(非ブロッキング)。しかし、それでも、それはどのように機能するのでしょうか、10,000の同時リクエストとしましょう。イベントループはすべてのリクエストを処理しますか?時間がかかりすぎませんか?

(まだ)マルチスレッドWebサーバーよりも高速であることが理解できません。マルチスレッドWebサーバーの方がリソース(メモリ、CPU)が高くなることは理解していますが、それでもまだ高速ではないでしょうか?私はおそらく間違っています。多数のリクエストでこのシングルスレッドがどのように高速になるか、10,000のような多数のリクエストを処理するときに通常どのように(高レベルで)処理するかについて説明してください。

また、そのシングルスレッドは、その大量でも十分に拡張できますか?Node.jsの学習を始めたばかりであることを覚えておいてください。


5
ほとんどの作業(データの移動)にはCPUが関与しないためです。
— OrangeDog 2016年

5
また、Javascriptを実行しているスレッドが1つしかないからといって、他のスレッドが作業を行っていないわけではないことにも注意してください。
— OrangeDog 2016年

この質問は広すぎるか、他のさまざまな質問の重複です。
— OrangeDog 2016年


Node.jsはシングルスレッド処理に加えて、「非ブロッキングI / O」と呼ばれる処理を実行します。ここですべての魔法が行われます
— Anand N

回答:


764

この質問をする必要がある場合は、ほとんどのWebアプリケーション/サービスの機能に慣れていない可能性があります。あなたはおそらくすべてのソフトウェアがこれを行うと考えています:

user do an action
       │
       v
 application start processing action
   └──> loop ...
          └──> busy processing
 end loop
   └──> send result to user

ただし、これはWebアプリケーション、または実際にデータベースをバックエンドとして使用するアプリケーションが機能する方法ではありません。Webアプリはこれを行います:

user do an action
       │
       v
 application start processing action
   └──> make database request
          └──> do nothing until request completes
 request complete
   └──> send result to user

このシナリオでは、ソフトウェアはその実行時間のほとんどを0%のCPU時間を使用してデータベースが戻るのを待ちます。

マルチスレッドネットワークアプリ:

マルチスレッドネットワークアプリは、上記のワークロードを次のように処理します。

request ──> spawn thread
              └──> wait for database request
                     └──> answer request
request ──> spawn thread
              └──> wait for database request
                     └──> answer request
request ──> spawn thread
              └──> wait for database request
                     └──> answer request

したがって、スレッドはほとんどの時間を0%のCPUを使用して、データベースがデータを返すのを待機しています。その間、スレッドごとに完全に別個のプログラムスタックを含むスレッドに必要なメモリを割り当てる必要がありました。また、完全なプロセスの開始ほど正確ではないスレッドを開始する必要があります。安いです。

シングルスレッドイベントループ

ほとんどの時間は0%CPUを使用しているので、CPUを使用していないときにコードを実行してみませんか?このように、各リクエストはマルチスレッドアプリケーションと同じ量のCPU時間を取得しますが、スレッドを開始する必要はありません。だから私たちはこれをします:

request ──> make database request
request ──> make database request
request ──> make database request
database request complete ──> send response
database request complete ──> send response
database request complete ──> send response

実際にはどちらのアプローチも、処理を支配するのはデータベースの応答時間であるため、ほぼ同じレイテンシでデータを返します。

ここでの主な利点は、新しいスレッドを生成する必要がないため、速度を低下させる大量のmallocを実行する必要がないことです。

マジック、目に見えないスレッド

一見不思議なことは、上記の両方のアプローチでワークロードを「並行」に実行する方法です。答えは、データベースはスレッド化されているということです。したがって、私たちのシングルスレッドアプリは、実際には別のプロセスであるデータベースのマルチスレッド動作を利用しています。

シングルスレッドアプローチが失敗する場所

データを返す前に多くのCPU計算を行う必要がある場合、シングルスレッドアプリは大きく失敗します。ここで、データベースの結果を処理するforループを意味するのではありません。それはまだほとんどO(n)です。つまり、フーリエ変換(mp3エンコードなど)、レイトレーシング(3Dレンダリング)などを行うことです。

シングルスレッドアプリのもう1つの落とし穴は、単一のCPUコアしか利用しないことです。したがって、クアッドコアサーバーを使用している場合(今日では珍しくありません)、他の3つのコアを使用していません。

マルチスレッドアプローチが失敗する場所

スレッドごとに大量のRAMを割り当てる必要がある場合、マルチスレッドアプリは大きく失敗します。まず、RAMの使用自体は、シングルスレッドアプリほど多くのリクエストを処理できないことを意味します。さらに悪いことに、mallocは低速です。たくさんのオブジェクト(最近のウェブフレームワークでは一般的です)を割り当てると、シングルスレッドアプリよりも遅くなる可能性があります。これが通常node.jsが勝つ場所です。

マルチスレッド化を悪化させる1つのユースケースは、スレッドで別のスクリプト言語を実行する必要がある場合です。通常、まずその言語のランタイム全体をmallocする必要があり、次にスクリプトで使用される変数をmallocする必要があります。

したがって、C、go、またはjavaでネットワークアプリを作成している場合、スレッド化のオーバーヘッドは通常それほど悪くありません。PHPまたはRubyを提供するC Webサーバーを作成している場合、JavaScriptまたはRubyまたはPythonでより高速なサーバーを作成するのは非常に簡単です。

ハイブリッドアプローチ

一部のWebサーバーはハイブリッドアプローチを使用しています。たとえばNginxとApache2は、ネットワーク処理コードをイベントループのスレッドプールとして実装します。各スレッドはイベントループを実行して、シングルスレッドのリクエストを同時に処理しますが、リクエストは複数のスレッド間で負荷分散されます。

一部のシングルスレッドアーキテクチャでもハイブリッドアプローチを使用しています。1つのプロセスから複数のスレッドを起動する代わりに、クアッドコアマシン上の4つのnode.jsサーバーなど、複数のアプリケーションを起動できます。次に、ロードバランサーを使用して、プロセス間でワークロードを分散します。

実際には、2つのアプローチは、技術的に同一の鏡像です。


106
これは、これまでに読んだノードの説明としては断然最良です。「:データベースシングルスレッドのアプリが実際には別のプロセスのマルチスレッド動作を活用して、」仕事をしたと
— kenobiwan

たとえば、名前を取得してそれを変更するなど、クライアントがノードで複数のリクエストを行っている場合はどうでしょうか。また、この操作はサーバーにプッシュして、多数のクライアントで非常に高速に処理するとします。このようなシナリオをどのように処理できますか?
— Remario 2017

3
@CaspainCaldionそれは、非常に高速で多くのクライアントが何を意味するかによって異なります。現状のまま、node.jsは1秒あたり1000以上のリクエストを処理でき、速度はネットワークカードの速度にのみ制限されます。同時に接続されているクライアントではなく、1秒あたり1000リクエストであることに注意してください。これは、10000の同時クライアントを問題なく処理できます。本当のボトルネックはネットワークカードです。
— slebetman 2017

1
@slebetman、これまでで最高の説明。ただし、いくつかの情報を処理し、それに応じて結果を提供する機械学習アルゴリズムがある場合、マルチスレッドアプローチまたはシングルスレッドを使用する必要があります
— Ganesh Karewad '25

5
@GaneshKarewadアルゴリズムはCPUを使用し、サービス(データベース、REST APIなど)はI / Oを使用します。AIがjsで書かれたアルゴリズムである場合、別のスレッドまたはプロセスで実行する必要があります。AIが別のコンピューターで実行されているサービス(Amazon、Google、IBM AIサービスなど)の場合、シングルスレッドアーキテクチャを使用します。
— slebetman 2017

46

あなたが考えているように見えるのは、ほとんどの処理がノードのイベントループで処理されるということです。ノードは実際にI / O作業をスレッドにファームします。I / O操作は通常、CPU操作よりも桁違いに長い時間がかかるので、なぜCPUはそれを待つのですか その上、OSはすでにI / Oタスクを非常にうまく処理できます。実際、Nodeは待機しないため、CPU使用率が大幅に向上します。

例えとして、NodeJSは、I / Oシェフがキッチンで注文を準備しているときに顧客の注文を受け取るウェイターであると考えてください。他のシステムには複数のシェフがいて、顧客の注文を受け、食事を準備し、テーブルを空にしてから、次の顧客の世話をします。


5
レストランのアナロジーをありがとう!私は、類推や実際の例を非常に簡単に学ぶことができます。
— LaVache

13

Node.jsがシングルスレッドとイベントループを使用して、一度に1つだけ処理するリクエストを処理することを理解しています(非ブロッキング)。

ここであなたが言ったことを誤解しているかもしれませんが、「一度に1つ」は、イベントベースのアーキテクチャを完全には理解していないようです。

「従来の」(イベント駆動ではない)アプリケーションアーキテクチャでは、プロセスは何かが発生するのを待つのに多くの時間を費やします。Node.jsなどのイベントベースのアーキテクチャでは、プロセスは単に待機するだけでなく、他の作業を続行できます。

たとえば、クライアントから接続を取得し、それを受け入れ、要求ヘッダー(httpの場合)を読み取ってから、要求の処理を開始します。あなたはリクエストボディを読むかもしれません、あなたは一般的にクライアントにいくつかのデータを送り返すことになります(これは要点を示すためだけに、手順の意図的な単純化です)。

これらの各段階で、ほとんどの時間は、もう一方の端からデータが到着するのを待つために費やされます。メインのJSスレッドでの処理に費やされる実際の時間は、通常、かなり最小限です。

I / Oオブジェクト(ネットワーク接続など)の状態が変化して処理が必要になった場合(ソケットでデータが受信された、ソケットが書き込み可能になったなど)、メインのNode.js JSスレッドがリストとともに起動します。処理が必要なアイテムの数。

関連するデータ構造を見つけ、その構造でイベントを発行します。これにより、コールバックの実行、受信データの処理、ソケットへのデータの書き込みなどが行われます。処理が必要なすべてのI / Oオブジェクトが処理されると、処理されると、メインのNode.js JSスレッドは、さらにデータが利用可能である(または他の操作が完了したかタイムアウトした)と通知されるまで再び待機します。

次にウェイクアップされるのは、異なるネットワーク接続など、異なるI / Oオブジェクトを処理する必要があるためと考えられます。毎回、関連するコールバックが実行され、その後、スリープ状態に戻り、何かが起こるのを待ちます。

重要な点は、さまざまな要求の処理がインターリーブされていることです。1つの要求が最初から最後まで処理されず、次の要求に移ることはありません。

私の考えでは、これの主な利点は、要求が遅いことです(たとえば、2Gデータ接続を介して1MBの応答データを携帯電話デバイスに送信しようとしている、または本当に遅いデータベースクエリを実行している) tより速いものをブロックします。

従来のマルチスレッドWebサーバーでは、通常、処理される要求ごとにスレッドがあり、処理が完了するまでその要求のみを処理します。遅いリクエストがたくさんある場合はどうなりますか?これらの要求の処理に多くのスレッドがぶら下がってしまい、他の要求(非常に簡単に処理できる非常に単純な要求である可能性があります)がそれらの背後にキューイングされます。

Node.js以外にも、イベントベースのシステムは他にもたくさんあり、従来のモデルと比較して同様の利点と欠点がある傾向があります。

イベントベースのシステムがすべての状況で、またはすべてのワークロードで高速であるとは主張しません。CPUベースのワークロードではなく、I / Oベースのワークロードでうまく機能する傾向があります。


12

シングルスレッドイベントループモデルの処理手順:

  • クライアントはWebサーバーに要求を送信します。

  • Node JS Webサーバーは、クライアント要求にサービスを提供するために内部的に限定スレッドプールを維持します。

  • Node JS Webサーバーはそれらのリクエストを受信し、キューに入れます。「イベントキュー」として知られています。

  • Node JS Webサーバーの内部には、「イベントループ」と呼ばれるコンポーネントがあります。この名前が付けられた理由は、要求を受信して​​処理するために無限ループを使用しているためです。

  • イベントループはシングルスレッドのみを使用します。Node JSプラットフォーム処理モデルの中心です。

  • イベントループは、クライアント要求がイベントキューに配置されているかどうかを確認します。そうでない場合は、着信要求を無期限に待機します。

  • はいの場合、イベントキューから1つのクライアント要求を取得します。

    1. クライアントが要求するプロセスを開始します
    2. そのクライアント要求がブロッキングIO操作を必要としない場合は、すべてを処理し、応答を準備してクライアントに送り返します。
    3. そのクライアント要求がデータベース、ファイルシステム、外部サービスとのやり取りのようないくつかのブロッキングIO操作を必要とする場合、それは異なるアプローチに従います
  • 内部スレッドプールからスレッドの可用性をチェックします
  • 1つのスレッドを取得し、このクライアント要求をそのスレッドに割り当てます。
  • そのスレッドは、その要求の取得、処理、ブロッキングIO操作の実行、応答の準備、およびイベントループへの送信を担当します。

    詳細については@Rambabu Posaによって非常にうまく説明されており、このリンクを投げてください


そのブログ投稿で与えられた図は間違っているようです、彼らがその記事で述べたことは完全に正しいわけではありません。
— rranj

11

slebetmanの回答への追加:Node.JS10,000の同時要求を処理できると言う場合、それらは本質的に非ブロッキング要求です。つまり、これらの要求は主にデータベースクエリに関係しています。

内部的にevent loopは、Node.JSがを処理しthread pool、各スレッドがを処理しnon-blocking request、イベントループは、のスレッドの1つに作業を委任した後も、引き続き要求を待機しthread poolます。スレッドの1つが作業を完了すると、スレッドevent loopがakaを終了したというシグナルを送信しますcallback。Event loop次に、このコールバックを処理して、応答を返します。

NodeJSを初めて使用する場合は、nextTickイベントループが内部でどのように機能するかを理解するために詳細を読んでください。http://javascriptissexy.comでブログを読んでください。JavaScript/ NodeJSを使い始めたとき、それらは本当に役に立ちました。


2

コードの実行中に何が起こるかをより明確にするために、slebetmanの回答に追加します。

nodeJsの内部スレッドプールには、デフォルトで4つのスレッドしかありません。リクエスト全体がスレッドプールからの新しいスレッドにアタッチされるのとは異なり、リクエストの実行全体は、通常のリクエスト(ブロッキングタスクなし)と同じように発生します。ファイル操作またはhttp要求を呼び出すと、タスクはlibuvによって提供される内部スレッドプールにキューイングされます。また、nodeJsはデフォルトで内部スレッドプールに4つのスレッドを提供するため、5番目または次の同時要求ごとにスレッドが解放されるまで待機し、これらの操作が終了するとコールバックがコールバックキューにプッシュされます。イベントループによってピックアップされ、応答が返されます。

ここで、一度だけのコールバックキューではなく、多くのキューがあるという別の情報があります。

  1. NextTickキュー
  2. マイクロタスクキュー
  3. タイマーキュー
  4. IOコールバックキュー(リクエスト、ファイル操作、データベース操作)
  5. IOポーリングキュー
  6. フェーズキューまたはSetImmediateを確認してください
  7. ハンドラキューを閉じる

要求が来るたびに、コードはキューに入れられたコールバックのこの順序で実行されます。

新しいスレッドに添付されているブロッキング要求がある場合とは異なります。デフォルトでは4つのスレッドしかありません。そこで、別のキューイングが発生しています。

コード内でファイルの読み取りなどのブロッキングプロセスが発生すると、スレッドプールからスレッドを利用する関数が呼び出され、操作が完了すると、コールバックがそれぞれのキューに渡され、その順序で実行されます。

すべては、コールバックのタイプに基づいてキューに入れられ、上記の順序で処理されます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.