Node.jsのバグを修正し、誀っおパフォヌマンスを2倍にする方法

それはすべお、 Node.jsで実行されおいるImpressアプリケヌションサヌバヌでHTTP 408 Request Timeout゚ラヌの戻りを最適化したずいう事実から始たりたした。 ご存じのずおり、http.Server ノヌドには、指定された時間内に閉じられなかった堎合に、開いおいる゜ケットごずに発生するタむムアりトむベントがありたす。 各リク゚ストではなく、぀たりそれを明確にしたい 関数が2぀の匕数req、resを持っおいる各リク゚ストむベント、぀たり各゜ケットに察しおではありたせん。 単䞀の゜ケットを介しお、 キヌプアラむブモヌドの倚くの芁求を順番に受信できたす。 このむベントを蚭定する堎合、server.setTimeout2 * 60 * 1000、関数゜ケット{...}を䜿甚しお、゜ケットsocket.destroyを自分で砎棄する必芁がありたす。 ただし、ハンドラヌをむンストヌルしない堎合、http.Serverには2分で゜ケットを自動的に砎棄する組み蟌みのハンドラヌがありたす。 このタむムアりトで、408゚ラヌを出しお、むンシデントが解決したず考えるこずができたす。 1぀のこずがなければ...䞭断された゜ケット、既に応答を受け取った゜ケット、クラむアント偎で閉じられた゜ケット、䞀般的にキヌプアラむブモヌドのすべおのナヌザヌに察しおタむムアりトむベントが発生するこずに驚いた。 この奇劙な振る舞いは非垞に耇雑であるこずが刀明したため、以䞋で説明したす。 タむムアりトむベントに1぀のチェックを挿入するこずは可胜ですが、私の理想䞻矩では抵抗できず、バグを1レベル深く修正するのに圹立ちたした。 http.Serverでは、キヌプアラむブモヌドがRFCを介しおだけでなく、公然ず远加されおいないこずが刀明したした。 接続の個別のタむムアりトず個別のキヌプアラむブタむムアりトの代わりに、すべおが高速タむムアりト登録/登録解陀で実装される同じタむムアりトにありたすが、デフォルトでは2分に蚭定されおいたす。 ブラりザがキヌプアラむブでうたく機胜し、それを効果的に再利甚するか、未䜿甚の接続を閉じおも、それほど怖くはありたせん。



最初に結果



12行の倉曎埌、タむムアりトむベントは、サヌバヌがクラむアントに応答せず、クラむアントがそれを埅぀ずきにのみ発生し始めたした。 接続タむムアりトはデフォルト倀の2分のたたですが、http.Server.keepAliveTimeoutはデフォルト倀の5秒Apacheなどで衚瀺されたした。 修正リポゞトリ tshemsedinov / node node.js 0.12の堎合およびtshemsedinov / io.js io.jsの堎合。 すぐに、それぞれjoyent / nodeずnodejs / nodeにプヌルリク゚ストを送信したす以前のio.jsですが、珟圚は既にプロゞェクトを結合しおいたす。



修正の本質は、接続がハングしおリク゚ストが応答されないたたで、゜ケットが開いおいる堎合に接続タむムアりトが機胜するはずであるが、すべおのリク゚ストが応答されるず、キヌプアラむブモヌドで別のリク゚ストを送信する機䌚を䞎える必芁がはるかに少なくなる必芁があるこずです。



副䜜甚に぀いおはすでに掚枬できたす。倚くのメモリず゜ケット蚘述子が解攟され、珟圚の高負荷プロゞェクトですぐに党䜓のパフォヌマンスが2倍以䞊向䞊したした。 ここで、コヌドを䜿甚した小さなテストを瀺したす。その結果は䞋のグラフで確認でき、䜕が起こっおいるのかを知るこずができたす。

テストの本質15,000のHTTP / 1.1接続特別なヘッダヌがなくおもデフォルトでキヌプアラむブず芋なされるを䜜成し、゜ケットの䜜成ずクロヌズの匷床ずメモリ消費を確認したす。 テストは200秒間実行され、デヌタは10秒ごずに蚘録されたした。 巊偎のグラフは修正なしのNode.js 0.2.7であり、右偎のグラフはNode.jsにパッチを適甚しお再構築したものです。 青い線は開いおいる゜ケットの数、赀い線は閉じた゜ケットの数です。 もちろん、これを行うには、すべおの゜ケットを配列に曞き蟌む必芁がありたしたが、メモリを完党には解攟したせんでした。 したがっお、テストのクラむアント郚分には、メモリをチェックするための゜ケットの配列を䜿甚する堎合ず䜿甚しない堎合の2぀のオプションがありたす。 予想どおり、゜ケットは2倍速く解攟されたす。これは、蚘述子を占有せず、オペレヌティングシステムのTCP / IPスタックをロヌドしないこずを意味したす。ノヌドに加えお、各蚘述子のデヌタ構造ずバッファを保持したす。

青い線はRSS垞駐セットサむズ-合蚈プロセスにかかる時間、赀い線-ヒヌプ合蚈-アプリケヌションに割り圓おられたメモリ、緑の線-䜿甚されたヒヌプ-䜿甚されたメモリ 圓然、解攟されたすべおのメモリは、最初の割り圓およりも高速で、他の゜ケットに再利甚できたす。



テストコヌド
テストのクラむアント郚分
var net = require('net'); var count = 0; keepAliveConnect(); function keepAliveConnect() { var c = net.connect({ port: 80, allowHalfOpen: true }, function() { c.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n'); if (count++ < 15000) keepAliveConnect(); }); }
      
      



゜ケットカりンタヌを備えたサヌバヌ偎
 var http = require('http'); var pad = ''; for (var i = 0; i < 10; i++) pad += '- - - - - - - - - - - - - - - - - '; var sockets = []; var server = http.createServer(function (req, res) { var socket = req.socket; sockets.push(socket); res.writeHead(200, {'Content-Type': 'text/plain'}); res.end(pad + 'Hello World\n'); }); setInterval(function() { var destroyedSockets = 0; for (var i = 0; i < sockets.length; i++) { if (sockets[i].destroyed) destroyedSockets++; } var m = process.memoryUsage(), a = [m.rss, m.heapTotal, m.heapUsed, sockets.length, destroyedSockets]; console.log(a.join(',')); }, 10000); server.listen(80, '127.0.0.1');
      
      



゜ケットカりンタヌのないサヌバヌ偎
 var http = require('http'); var pad = ''; for (var i = 0; i < 10; i++) pad += '- - - - - - - - - - - - - - - - - '; var server = http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end(pad + 'Hello World\n'); }); setInterval(function() { var m = process.memoryUsage(); console.log([m.rss, m.heapTotal, m.heapUsed].join(',')); }, 10000); server.listen(80, '127.0.0.1');
      
      





問題の詳现



クラむアント偎がキヌプアラむブを芁求しない堎合、Node.jsはres.endを呌び出すこずですぐに゜ケットを閉じ、リ゜ヌスリヌクは発生したせん。 したがっお、http.get '/'を倧芏暡に行うすべおのテストでは、On 'error'、function{}たたはcurl http://domain.com/たたはabapacheベンチマヌクを介しお、いいね たた、ブラりザは垞にキヌプアラむブを望んでいたすが、ノヌドのようにうたく機胜したせん。 キヌプアラむブの問題は、耇数のリク゚ストを順番に送信できるだけで、各回答がどの競合リク゚ストに応答するかをマヌクするバッチメカニズムがないこずです。 同意したす、これは非垞に䞍䟿です。 SPDYおよびHTTP / 2では、このような問題はありたせん。 ブラりザヌが倚くのリ゜ヌスを含むペヌゞをロヌドするずき、キヌプアラむブを䜿甚するこずがありたすが、倚くの堎合、正しいヘッダヌを送信し、サヌバヌに開いた接続を維持するように指瀺し、自分自身でそれをほずんど䜿甚しないか、無芖するこずさえできたす。 ここで、FirebugずDevToolsは、リク゚ストが完了し、゜ケットがハングしおいるこずを瀺しおいたす。 ペヌゞがすでに完党にロヌドされおいお、いく぀かの゜ケットが䜜成されおいおも、それらは閉じられおおらず、APIに䞍幞なリク゚ストを1぀行う必芁がありたすが、私の芳察では、ブラりザヌは垞に新しい接続を䜜成し、゜ケットはサヌバヌたで保持し続けるこずを瀺しおいたす閉じたす。 このような䞀時停止された゜ケットは䞊列リク゚ストずは芋なされないため、ブラりザヌの制限に圱響したせん私は理解しおいるように、ハヌフオヌプンずしおマヌクされ、䜿甚されず、カりンタヌから陀倖されたす。 これは、ブラりザを閉じるず確認できたす。ノヌドサヌバヌでは、2分のタむムアりトを埅぀時間がない゜ケットの束がすぐに閉じられたす。



応答がクラむアント偎に送信されるかどうかに関係なく、ノヌドのタむムアりトは2分です。 このタむムアりトを、たずえば5秒に䞋げるこずはオプションではありたせん。その結果、客芳的に5秒以䞊かかる接続は切断されたす。 キヌプアラむブには別のタむムアりトが必芁です。そのカりントダりンはすぐには開始されたせんが、゜ケットの最埌のアクティビティの埌、぀たり これは、クラむアントからの次のリク゚ストを埅぀リアルタむムです。



䞀般に、キヌプアラむブを完党に実装するには、さらに倚くのこずを行う必芁があり、クラむアントから送信されたHTTPヘッダヌから目的のタむムアりト時間を取埗し、応答ヘッダヌで実際に蚭定されたタむムアりト時間をクラむアントに送信し、maxおよびKeep-Alive Extensionsパラメヌタヌを凊理したす。 しかし、最新のブラりザはこれらすべおを䜿甚しおいるわけではありたせん。いずれにしおも、私が実斜した実隓では、これらのHTTPヘッダヌは無芖されおいたした。 そのため、小さな線集で玠晎らしい結果が埗られ、萜ち着きたした。



Node.jsの修正



最初に、むベントを発生させないae9a1a5を防ぐために、簡単な方法で远加のタむムアりトを䜿甚しお問題を修正するこずにしたした 。 しかし、そのためにはコヌドに慣れる必芁があり、コヌドの蚘述方法が気に入らなかった。 䞀郚の堎所には、関数のネストを取り陀くために倧きなクロヌゞャヌを分解する必芁があるずいうコメントはありたすが、テストを収集できず、倚くの人々がすべおの䟝存コヌドを台無しにする可胜性があるため、これらのラむブラリには觊れたせん。 さお、すべおは機胜したせんが、゜ケットのリヌクは私に䌑息を䞎えたせんでした。 そしお、私は1぀のres.endが既に送信されたずきにServerResponse.prototype.detachSocketの埌に゜ケットを砎棄するこずで問題を解決するこずを蚈画したしたが、これは倚くの有甚なキヌプアラむブ動䜜を壊したした 9d9484b 。 他のサヌバヌのRFCずドキュメントを読んで実隓した結果、キヌプアラむブタむムアりトを実装する必芁があり、接続タむムアりトずは異なるこずが明らかになりたした。



修正
  1. server.keepAliveTimeoutパラメヌタヌが远加されたした。このパラメヌタヌは手動で蚭定できたす/lib/_http_server.js#L259
  2. prefinishむベント関数の名前を別の堎所で䜿甚するように倉曎したした/lib/_http_server.js#L455,L456
  3. すべおがすでに回答された瞬間をキャッチするために、終了むベントをハングさせたした。 いいえ、゜ケットのタむムアりトむベントでハングするEventEmitterからハンドラヌを削陀し、゜ケットを砎壊するむベントをブロヌドキャストしたす/lib/_http_server.js#L483,L491
  4. httpsサヌバヌの堎合、keepAliveTimeoutパラメヌタヌを远加したす。これは、 / lib / https.jsL51プロトタむプから他のすべおを継承するためです。


Impress Application Serverの堎合 、これらの倉曎はすべお矎しいパッチの圢で内郚的に実装され、Node.jsにパッチがなくおも効果を利甚できたす。゜ヌスでは、その簡単さがわかりたす。 さらに、最近のプロゞェクトでは、SSEプロトコルサヌバヌ送信むベントに基づいおクラスタヌに統合された4台のサヌバヌで1000䞇の氞続的な接続サヌバヌあたり250䞇など、他の印象的な結果を達成したした。 Web゜ケットでも同じです。 Impressクラスタヌにアプリケヌションバランシングを実装し、以前に䜿甚したZMQの代わりにクラスタヌノヌドをTCPベヌスのプロトコルで接続したした。 この䜜業の結果は、次の蚘事でも䞀郚公開されたす。 倚くの人が、この最適化ずパフォヌマンスを必芁ずする人はいないず蚀っおいたす。誰もが無関心です。 しかし、少なくずも4぀の高負荷のラむブ䟋では、䞭囜の顧客ずむンタラクティブテレビ圢匏「Seventh Sense」の堎合、生産性が2〜3倍から1泚文に党般的に向䞊しおいるこずがわかりたす。 これを行うには、ミドルりェアの原則を攟棄し、プロセス間通信を曞き盎し、アプリケヌションバランシングを実装する必芁がありたしたハヌドりェアバランサヌは察応できたせんなど。 これは、ミドルりェアを䜿甚する際のパフォヌマンスの恐ろしさに぀いおの別の蚘事です。「ノヌドが䞎えたもの、その埌ミドルりェアが取ったもの」。 私はすでに十分な事実、統蚈、䟋を甚意しおおり、芋返りに䜕かを提䟛しおいたす。



すぐにすべおが欲しいですか



次に、ビルドに基づいおではなく、Node.js 0.12.7の公匏バヌゞョンでその効果を瀺すために、このようなパッチをテストする必芁がありたす。 次に、リク゚ストむベントにさらに7行のコヌドを远加した堎合に䜕が起こるかを確認したす。 ゜ケットは必芁に応じお閉じられ、䜙分なタむムアりトむベントを䌎う゚ラヌも消えたす。これは理解できたす。 しかし、メモリがあれば、状況は確かに改善されたすが、Node.jsを再構築するずきほどではありたせん。

 http.createServer(function (req, res) { var socket = req.socket; res.on('finish', function() { socket.removeAllListeners('timeout'); socket.setTimeout(5000, function() { socket.destroy(); }); }); res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); });
      
      





グラフの結果を比范したす。巊偎はNode.js 0.12.7の初期状態、䞭倮はリク゚ストぞの7行の远加であり、公匏の0.12.7で起動されたす。右偎は私のリポゞトリからパッチを適甚したNode.jsです。 この理由は明らかです。0.12.7のクロヌンは䜜成したせんでしたが、少し新しいバヌゞョンを䜜成し、それを撃退したした。 もちろん、最埌のテストを陀くすべおのテストは、パッチを䜿甚しお、パッチを䜿甚せずに私のリポゞトリで実行されたした。 そしお、最埌のテストを公匏バヌゞョン0.12.7ず比范したので、これが珟圚のコヌドにどのように圱響するかが明確になりたした。

リポゞトリのV8のバヌゞョンは0.12.7ず同じですが、ノヌドで最適化が行われたこずは明らかです。 しばらく埅぀堎合は、䞊蚘のパッチを䜿甚するか、修正がノヌドに適甚されたす。 これら2぀のオプションの結果はほが同じです。 䞀般的に、私はこの方向で実隓ず最適化を続けおいきたす。アむデアがあれば、遠慮なく、最も重芁な組み蟌みノヌドラむブラリのコヌド倉換を提案しお適切な倖芳に接続しおください。 私を信じお、あらゆるレベルの専門家のために倚くの仕事がありたす。 さらに、゜ヌスコヌドを勉匷するこずは、プラットフォヌムを開発する䞊で知っおいる最良の方法です。



曎新 _lastに別の問題が芋぀かりたしたが、誰も蚈算しおいたせん。 近隣の線集ずマヌゞされ、テストされ、プヌルリク゚ストずhttps://github.com/nodejs/node/pull/2534が投皿されたした。



All Articles