Dahlさん、冗談を言う必要があります。またはNode.jsがWebサーバーの進化の冠である理由

WTFはNode.jsですか?

Node.jsは、周囲に多くのノイズ、絶賛レビュー、怒りの叫び声があります。 同時に、私の観察によると、Node.jsが人々の心に固定されているものについての次の考えは、「これはサーバー側でJavaScriptを記述し、Google ChromeのJavaScriptエンジンを使用できるようにするものです」。 舌のファンは熱心にため息をつきました。 それは実現しました!」、対戦相手は歯を食いしばった:「まあ、私たちはまだ、サーバーでのプロトタイプと動的なタイピングでこのナンセンスを十分に持っていませんでした!」 そして、彼らは一緒になって、槍をブログとフォーラムに分割しました。

同時に、両方のキャンプの代表者の多くは、Node.jsは難解な玩具であり、ブラウザスクリプトの言語を「新しい車輪」に移植するための楽しいアイデアであると考えています。 完全に正直に言うと、私もこの観点を固守したと告白します。 ある素晴らしい瞬間、私は心を取り、「もう少し深く掘り下げる」ことを決めました。 Node.jsの作成者であるRyan Dahlは 、狂信者とほど遠いが、本当の問題を解決しようとする男であることが判明しました。 そして彼の創造はおもちゃではなく、実用的な解決策です。


それでは、Node.jsとは何ですか? 公式Webサイトには、「V8 JavaScriptのイベントI / O」という碑文があります。 あまり意味がありませんよね? さて、「自転車を発明」して、この悪名高い「V8 JavaScriptのイベントI / O」を「考えて」みましょう。 もちろん、コードを書くことはしません(貧弱なライアンはまだこれをしなければなりませんでした)が、Node.jsを作成し、それをどのように配置すべきかというアイデアにつながる結論のチェーンを構築してみてください。

シンプルで愚かなこと


そのため、Webアプリケーションはクライアント/サーバープログラミングモデルを使用することを知っています。 クライアントはユーザーのブラウザであり、サーバーは、たとえば、ホスティング事業者のデータセンターにあるマシンの1つです(好みに応じていずれかを選択します)。 ブラウザーはサーバーにリソースを要求し、サーバーはそれをクライアントに提供します。 クライアントとサーバー間の通信のこの方法は、「 クライアントプル 」と呼ばれますこれは、クライアントが文字通りサーバーをプルするためです。 サーバーは、迷惑なクライアントの質問への回答を「熟考」し、消化可能な形式で彼に与えます。

シンプルなウェブサーバー

そのため、最も単純なWebサーバーのモデルがあります。ブラウザクライアントから要求を受信し、処理して応答を返すプログラムです。

パラレルユニバース


すばらしいですが、そのような単純なサーバーは一度に1人のユーザーとしか通信できません。 リクエストを処理する時点で、もう1人のクライアントが彼に宛てた場合、サーバーが最初に応答するまで待つ必要があります。 そのため、ユーザーからのリクエストの処理を並列化する必要があります。 明らかな解決策:ユーザー要求をオペレーティングシステムの個別のスレッドまたはプロセスで処理します。 そのようなプロセスまたはスレッドワーカーをそれぞれ呼び出しましょう。

スレッドを備えたサーバー

何らかの形で、このアプローチの後に、今日最も人気のある多くのWebサーバー( ApacheIISなど )が続きます 。 このモデルは実装が比較的簡単であると同時に、今日のほとんどの中小規模のWebリソースのニーズを満たすことができます。

しかし、同時に数千のリクエストを処理する必要がある場合、このモデルは完全に機能しません。 これにはいくつかの理由があります。 まず、プロセスとスレッドの両方を作成することは、あらゆるオペレーティングシステムにとって非常に重要です。 ただし、事前にスレッドやプロセスを作成して、必要に応じて使用することもできます。 OK、 スレッドの場合は「 スレッドプール 」、プロセスの場合は「 prefork 」と呼ばれるメカニズムを思いついたところです。 これは、サーバーの起動時にこのオーバーヘッド操作を実行できるため、プロセスとスレッドの作成にリソースを無駄にしないために役立ちます。 第二に、作成されたすべてのワーカーが忙しい場合はどうでしょうか? 新しいものを作成しますか? ただし、サーバーのすべてのプロセッサコアは既に完全に読み込まれているため、スレッドまたはプロセスをさらに追加すると、既に実行されているスレッドおよびプロセスとプロセッサ時間を競います。 これは、両方がさらに遅く動作することを意味します。 はい、前述のように、スレッドとプロセスの作成とサービスはRAMの消費量の点でコストのかかるものであり、1,000人のユーザーごとにスレッドを作成すると、サーバーとワーカーにメモリがまったく残っていない状況にすぐに気付くことがありますは、ハードウェアリソースをめぐって常に競合状態にあります。

無限は制限ではありません!


利用可能なコンピューティングリソースが不足しており、解決できない状況にあるように思われます。 唯一の解決策は、ハードウェアリソース拡張することです 。これはあらゆる点で高価です。 反対側から問題を見てみましょう:私たちの労働者の大半は忙しいですか? クライアントからのリクエストを受け入れ、レスポンスを作成し、クライアントに送信します。 それで、弱いリンクはどこにありますか? クライアントにはリクエストを受信し、レスポンスを送信するという2つの方法があります。 これがそうであることを理解するには、今日のインターネット接続の平均速度を思い出してください。 ただし、I / Oサブシステムは非同期モードで動作できるため、ワーカーをブロックしない場合があります。 うーん、実際には、ワーカーが行うことは、クライアントへの応答を生成し、I / Oサブシステムのタスクを管理することだけです。 以前は、各ワーカーはリクエスト処理サイクル全体を実行する責任を負っていたため、一度に1人のクライアントにしかサービスを提供できませんでした。 これで、ネットワークI / OをI / Oサブシステムに委任すると、1人のワーカーが複数のリクエストを同時に処理できるようになります。たとえば、あるクライアントに対する応答を生成し、別のワーカーに対する応答はI / Oサブシステムによって与えられます。 これで、ユーザーごとにスレッドを割り当てる必要がなくなりましたが、サーバープロセッサに1つのワーカーを作成できるため、最大のハードウェアリソースを提供できます。

実際には、そのような委任はイベント指向のプログラミングパラダイムを使用して実装されます。 このパラダイムに従って開発されたプログラムは、 有限状態マシンとして実装できます。 特定のイベントは、特定のマシンをある状態から別の状態に変換します。 私たちの場合、サーバーは、クライアントへの応答を生成し、特定の操作を実行する準備ができているかどうかI / Oサブシステムの記述子をポーリングし、成功した場合は新しいタスクを与えるエンドレスサイクルの形式で実装されます。 I / Oサブシステムの記述子をポーリングするプロセスは、「 ポーリング 」と呼ばれます。 事実、効果的なポーリング実装は現在* nixシステムでのみ利用可能です。 後者は、これらの目的のために、線形ランタイムで非常に高速なシステムコールを提供します(たとえば、Linuxではepoll 、BSDシステムではkqueue )。 これは、ハードウェアリソースを最大限に使用できるため、非常に効率的なサーバーモデルです。 実際、図を見れば簡単にわかるように、どのサーバーサブシステムもアイドルアイドル状態ではありません。

非同期I / Oを備えたサーバー

同様の概念は、負荷の高いシステムで実証されているnginxlightppdなどのサーバーでも使用されています。

一緒に行こう


しかし(常に「1つ」があります)、その前に、応答を生成する時間がクライアントと通信するよりも桁違いに短いという考えから始めました。 そして、これは部分的に真実です。 それでも、応答の生成は複雑で複雑なタスクになることがあります。これには、ディスクの読み取りや書き込み、データベース(リモートサーバー上にある場合もあります)の操作が含まれる場合があります。 さて、実際に元の問題に戻ったことがわかりました。 実際には、次のように解決されます。システムは、 フロントエンドとバックエンドの 2つの部分に分割されます。 フロントエンドは、クライアントが直接通信するサーバーです。 原則として、これは非同期イベントモデルを備えたサーバーであり、クライアントとの通信を迅速に確立し、クライアントにリクエストの結果(たとえば、nginx)を提供できます。 バックエンドはブロッキングI / Oモデル(Apacheなど)を備えたサーバーであり、I / Oサブシステムと同様に、フロントエンドがクライアントへの応答の作成を委任します。 このようなフロントエンドは、「 リバースプロキシ 」とも呼ばれます。これは、実際には通常のプロキシサーバーであるが、リクエストをリダイレクトするサーバーと同じサーバー環境にインストールされているためです。

実生活との類似性を引き出す場合、フロントエンドは華麗な白い歯と高価なスーツを備えたマネージャーであり、バックエンドは工場の労働者のグループであり、入出力サブシステムはマネージャーが働いて所有する会社の輸送部門です工場。 クライアントは、輸送部門を介して彼に手紙を送ることにより、マネージャーに頼ります。 マネージャーは、製品のバッチを提供するためにクライアントと取引を行い、バッチを作成するようにワーカーに命令を送信します。 マネージャー自身は、順番に、労働者が注文の実行を完了するまで期待しませんが、彼らの即時の責任に対処し続けます-顧客との取引を行い、プロセス全体がスムーズにスムーズに進むことを確認します。 マネージャーは定期的に労働者と連絡を取り、注文の準備状況について問い合わせ、バッチの準備ができている場合は、輸送部門に注文をクライアントに送信するよう指示します。 もちろん、商品がクライアントに届くのを定期的に監視しています。 これは、数千年前に発明された分業のアイデアが、ハイテクにおける予期せぬ応用を発見した方法です。

そして、リーパー、Shvets、そしてパイプ上のイグレット(JavaScriptはそれと何の関係があるのでしょうか?)


まあ、これはすべてうまくいきますが、どういうわけか私たちのシステムは非常に複雑になりましたね? はい、応答の生成を別のサーバーに委任しますが、これは依然として最速のプロセスではありません。ファイルI / Oとデータベースの操作によりブロックが発生し、プロセッサのダウンタイムが避けられないためです。 それでは、応答生成プロセスのボトルネックを排除しながら、システムの整合性をどのように復元するのでしょうか? 基本的に 、ワトソン-すべてのI / Oを実行し、データベースノンブロッキング、イベントに基づいて作業します(そう、最もイベントが多いI / O )!

「しかし、これによりWebアプリケーション作成のパラダイム全体が変わり、既存のフレームワークのほとんどはもはや適用または適用できませんが、それらを使用するソリューションはエレガントではありません!」とあなたは言います。 はい、人的要因を排除することはできません- マーフィーの法則を適用すると、「 I / Oをブロックする機能を使用できれば、だれかが遅かれ早かれそれを行う 」と主張できるため、最初のアイデア全体が壊れます。 これは時間、プロジェクトの範囲、プログラマーの資格の問題にすぎません。 「抽象化に注意してください。 使用する必要があるかもしれません。」( 英語。 「抽象化の作成には注意してください使用する必要があるかもしれません。」) それでは、ミニマリズムに固執して、Webアプリケーションを開発できる基盤のみを作成し、同時に非同期プログラミングモデルに合わせて調整し、それから逸脱する機会、そして最も重要なことには欲求がないようにします。 それで、私たちが必要とする最小は何ですか?

明らかに、まず最初に、 ランタイム環境が必要です 。その主な要件は、応答生成コードと非同期入出力の高速実行です。 イベントモデルに合わせて調整された最新のプログラミング言語は、すべてのWeb開発者に知られていると同時に、実装を迅速かつ迅速に開発していますか? 答えは明らかです-これはJavaScriptです。 さらに、 Googleの JavaScriptエンジンV8を自由に使用でき、非常にリベラルなBSDライセンスの下で配布されています。 V8は多くの面で美しいです:まず、 JITコンパイル他の多くの最適化手法を使用し 、次に、よく作られた、考え抜かれ、活発に開発されているソフトウェア製品の例です(通常、V8を本当に高品質の例として挙げます職場の同僚向けのC ++コード)。 これらすべてにlibevライブラリを追加します。これにより、イベントループを簡単に整理し、ポーリングメカニズムの高レベルラッパーを提供できます(さまざまなオペレーティングシステムの実装の機能を心配する必要はありません)。 また、高速の非同期ファイルI / Oにはlibeioライブラリが必要です。 さて、これでランタイムは準備完了とみなすことができます。

そしてもちろん、すべての基本的なI / O操作と機能のためのJavaScriptラッパーを含む標準ライブラリが必要です。これを使用しないと、Web開発に遠く行くことはできません(たとえば、HTTPヘッダーとURLの解析、ハッシュのカウント、DNS-解決など)。

Node.jsアーキテクチャ

おめでとうございます。非常に高速なサーバーという概念を思いついたのです。Node.js。

私はただ言っています


要約すると、Node.jsは非常に若いプロジェクトであり、正しく使用すればWeb開発の世界に革命をもたらすことができると言いたいと思います。 今日、プロジェクトには、実際の高負荷システムでの使用を複雑にする多くの未解決の問題があります( 先例はすでに存在します)。 たとえば、Node.jsは本質的に1つのワーカーにすぎません。 たとえば、デュアルコアプロセッサを使用している場合、Node.jsでハードウェアリソースを完全に利用する唯一の方法は、2つのサーバーインスタンス(コアごとに1つ)を起動し、リバースプロキシ(たとえば、同じnginx)を使用して負荷を分散することですそれら。
しかし、そのような問題はすべて解決可能であり、積極的に取り組んでおり、Node.jsを中心に巨大なコミュニティが既に構築されており、多くの大企業がこの開発にかなりの注意を払っています。 Dahl氏が彼の事件を終わらせることを願うだけです(偶然、 彼を助けることができます )。読者の皆さん、Node.jsの開発に多くの楽しい時間を費やすべきです。

Source: https://habr.com/ru/post/J108241/


All Articles