Node.jsとPHPを例として使用した非同期アーキテクチャとマルチスレッドアーキテクチャの違い

最近、非同期アーキテクチャ上に構築されたプラットフォームが増加しています。 世界最速のnginx Webサーバーは、非同期モデル上に構築されています。 機敏なサーバーサイドjavascriptは、Node.jsに直面して積極的に開発されています。 このアーキテクチャは何に適していますか? 従来のマルチスレッドシステムとの違いは何ですか? この主題に関して膨大な数の記事が書かれましたが、それらはすべての人々に主題の完全な理解を与えませんでした。 多くの場合、Node.jsとPHP + Apacheをめぐる論争を観察する必要があります。 多くの人は、Node.jsでできることはあるが、PHPではできない、またはその逆の理由を理解していません。 この記事では、それらのアーキテクチャの違いをもう一度詳しく説明したいと思います。 2つのシステムの例として、PHPとNode.jsを備えたWebサーバーを取り上げます。



マルチスレッドモデル



このモデルは誰にでも知られています。 このアプリケーションは、一定数のスレッド(プール)を作成し、各スレッドにタスクと処理用のデータを渡します。 タスクは並行して実行されます。 ストリームに共通のデータがない場合、同期のためのオーバーヘッドがないため、作業が十分に高速になります。 作業の完了後、スレッドは強制終了されませんが、プール内にあり、次のタスクを待機します。 これにより、スレッドの作成と削除のオーバーヘッドがなくなります。 PHPを使用するWebサーバーが機能するのは、このシステム上です。 各スクリプトは独自のスレッドで動作します。 1つのスレッドが1つの要求を処理します。 非常に多数のスレッドがあり、遅い要求は長時間ストリームを処理し、速い要求はほぼ瞬時に処理され、他の作業のためにスレッドを解放します。 これにより、低速のリクエストがすべてのプロセッサ時間を消費することはなく、高速のリクエストは強制的にハングします。 しかし、そのようなシステムには一定の制限があります。 たとえば、データベースやファイルシステムを操作している場合など、多数の低速クエリが発生すると、状況が発生する可能性があります。 このようなリクエストは、すべてのスレッドを自分自身で使用するため、他のリクエストを実行できなくなります。 要求の実行に1ミリ秒しか必要としない場合でも、時間どおりに処理されません。 これは、スレッドの数を増やすことで解決でき、十分に多数の低速な要求を処理できるようになります。 しかし、残念ながら、スレッドはOSによって処理され、プロセッサ時間はそれに割り当てられます。 したがって、作成するスレッドが多いほど、処理のオーバーヘッドコストが増え、各スレッドに割り当てられるプロセッサ時間が短くなります。 この状況はPHP自体によって悪化します。データベース、ファイルシステム、I / Oを操作するブロッキング操作もCPU時間を費やしますが、その時点では有用な作業は行われません。 ここでは、ブロッキング操作の機能について詳しく説明します。 この状況を想像してください。複数のスレッドがあります。 各リクエストは、リクエスト自体を処理する1ミリ秒、データベースにアクセスしてデータを受信するための2ミリ秒、受信したデータをレンダリングする1ミリ秒で構成されるリクエストを処理します。 合計で、各リクエストに4ミリ秒かかります。 データベースにクエリを送信すると、スレッドは応答を待機し始めます。 データが返されるまで、ストリームは処理を実行しません。 これは、要求ごとに4msで2msのダウンタイムです! はい、データベースからデータを受信せずにページをレンダリングすることはできません。 待たなければなりません。 しかし同時に、プロセッサのダウンタイムは50%になります! また、上記から、各スレッドのプロセッサ時間の割り当てに追加のOS費用を投じることができます。 そして、より多くのフロー-より多くのこれらのコスト。 その結果、かなり長いダウンタイムが発生します。 この時間は、データベースおよびファイルシステムへのクエリの期間に直接依存します。 有用な作業でプロセッサを完全にロードできる最適なソリューションは、非ブロッキング操作を使用するアーキテクチャへの移行です。



非同期モデル



マルチスレッドよりも一般的ではないモデルですが、機能は劣りません。 非同期モデルはイベントキュー上に構築されます。 イベントが発生すると(要求が来た、ファイルが読み取られ、データベースからの応答が来た)、それはキューの最後に置かれます。 このキューを処理するスレッドは、キューの先頭からイベントを取得し、このイベントに関連付けられたコードを実行します。 キューが空ではない間、プロセッサは仕事で忙しくなります。 このスキームによると、Node.js。 イベントキューを処理する単一のスレッドがあります(クラスターモジュールで-複数のスレッドが存在します)。 ほとんどすべての操作は非ブロッキングです。 ブロッカーも利用できますが、その使用は非常に推奨されません。 次に、理由を理解します。 リクエスト1 + 2 + 1msで同じ例を見てみましょう。リクエストの到着に関連するイベントはメッセージキューから取得されます。 リクエストを処理し、1msを費やします。 次に、データベースへの非同期の非ブロッキングクエリが作成され、制御が直ちに渡されます。 キューから次のイベントを取得して実行できます。 たとえば、もう1つのリクエストを受け取り、処理を実行し、データベースにリクエストを送信し、コントロールを返し、もう一度同じことを行います。 そして、最初のリクエストに対するデータベースの応答があります。 関連するイベントはキューに入れられます。 キューに何もなければ、すぐに実行され、データがレンダリングされてクライアントに返されます。 キューに何かがある場合、他のイベントの処理を待つ必要があります。 通常、1つの要求の処理速度は、マルチスレッドシステムとブロック操作の処理速度に匹敵します。 最悪の場合、他のイベントが処理されるのを待つのに時間がかかり、リクエストの処理が遅くなります。 しかし、その時点で、ブロッキング操作のあるシステムは2ミリ秒の応答を待つだけでしたが、ノンブロッキング操作のあるシステムは、他の2つのリクエストのさらに2つの部分を完了することができました! 各リクエストは全体的に少し遅くなりますが、単位時間あたりにより多くのリクエストを処理できます。 全体的なパフォーマンスが向上します。 プロセッサは常に有用な作業で忙しくなります。 同時に、マルチスレッドシステムでスレッドを切り替えるよりも、キューを処理してイベントからイベントに移動する時間ははるかに短くなります。 したがって、非ブロッキング操作を使用する非同期システムには、システム内のコアの数よりも多くのスレッドを含めることはできません。 Node.jsは通常、シングルスレッドモードでのみ機能し、プロセッサを最大限に活用するには、たとえばnginxを使用して、サーバーの複数のコピーを手動で作成し、それらの間で負荷を分散する必要がありました。 クラスターモジュールは、いくつかのコアで動作するようになりました(執筆時点では、まだ実験的な状態です)。 ここで、2つのシステムの主な違いが明らかになります。 ブロッキング操作のあるマルチスレッドシステムでは、ダウンタイムが長くなります。 スレッドの数が多すぎると大量のオーバーヘッドが発生する可能性がありますが、数が不十分な場合は、低速なリクエストが多い場合に速度が低下する可能性があります。 ノンブロッキング操作を伴う非同期アプリケーションは、プロセッサー時間をより効率的に使用しますが、設計はより困難です。 これは特にメモリリークに影響を与えます-Node.jsプロセスは非常に長時間動作する可能性があり、プログラマーが各リクエストの処理後にデータのクリアを処理しない場合、リークが発生し、サーバーを再起動する必要が徐々に生じます。 ブロッキング操作を使用した非同期アーキテクチャもありますが、収益性ははるかに低く、いくつかの例で後ほど見ることができます。 非同期アプリケーションを開発するときに考慮しなければならない機能を強調し、非同期アーキテクチャの機能を処理しようとするときに経験するいくつかのエラーを分析します。



ブロック操作を使用しないでください。 決して



少なくとも、Node.jsのアーキテクチャを完全に理解し、ブロッキング操作を正確に操作できないまでは。

PHPからNode.jsに切り替えるとき、一部の人々は以前と同じスタイルでコードを書きたいかもしれません。 実際、最初にファイルを読み取り、その処理を続行する必要がある場合、次のコードを記述できないのはなぜですか。



var fs = require('fs');
var data = fs.readFileSync("img.png");
response.write(data);

      
      





, . , , Node.js , . . , , . :



var fs = require('fs');
fs.readFile("img.png", function(err, data){
	response.write(data);
});

      
      





: , , Node.js . — , readFile . , , — . , : , . , . , .

, event-loop:



var fs = require('fs');
var dataModified = false;
var myData;

fs.readFile("file.txt", function(err, data){
	dataModified = true;
	myData = data+" last read "+new Date();
});

while (true){
	if(dataModified)
		break;
}

response.write(myData);

      
      





, . , , . - … !



var fs = require('fs');
var events = require('events');
var myData;
var eventEmitter = new events.EventEmitter();

fs.readFile("file.txt", function(err, data){
	myData = data+" last read "+new Date();
	eventEmitter.emit('dataModified', myData);
});

eventEmitter.on('dataModified', function(data){
	response.write(data);
});

      
      





-, . — , , emit , . events.EventEmitter . eventEmitter.on , .

, Node.js. , . , .



.



, ? , ? — . , . , . , , , .



function incredibleGigantCycle(){
	cycleProcess();
	process.nextTick(incredibleGigantCycle);
}

      
      





. .



,



( Node.js — ). (, 500) , . - ? , . , , , . , . , , .



,



, , . Node.js Sync. callback. , , .



var fs = require('fs');
fs.readFile("img.png", function(err, data){

});
response.write(data);

      
      





. . — . . . , , callback-. .





, «-» Node.js, callback', PHP ? .

:



 $user->getCountry()->getCurrency()->getCode()

      
      









user.getCountry(function(country){
	country.getCurrency(function(currency){
		console.log(currency.getCode())
	})
})

      
      







3 . : PHP . , . , . , - , . — , , , . 3 . PHP , Node.js , , .





Node.js , PHP , . Node.js , . — Node.js. , PHP — Node.js , .



All Articles