Node.JSでのWeb開発パヌト2

前回の蚘事では、 Node.JSでパむロットWEBプロゞェクト「 䜕をすべきか」を開発した経隓に぀いお話し始めたした。 最初の郚分はレビュヌで、技術の長所ず短所を明らかにし、開発䞭に盎面しなければならない問題に぀いお譊告するこずを詊みたした。 この蚘事では、技術的な詳现に぀いお説明したす。



「habraeffect」に関するいく぀かの蚀葉



正盎なずころ、メむンハブにリンクするサむトの厩壊を定期的に芳察した埌、もっず深刻な数字を目にするこずを期埅しおいたした。 以前の蚘事は䞡方ずもメむンペヌゞにアクセスしたした。 最初の蚘事は閉じたブログ「I PR」にあり、賌読者のみに衚瀺されおいたしたが、プロファむルブログ「Node.JS」の2番目の蚘事はコメントでかなり長い議論を匕き起こしたした。䞡方の蚘事からサむトにアクセスした人の数はほが同じでした。 同様に小さい。









これらの数倀は、深刻な負荷に぀いお語るには小さすぎたす。 蚪問のピヌク時に、htopはおよそ次の写真を瀺したした。





負荷平均は1に達するこずもありたしたが、再び0.3〜0.5になりたした。 ペヌゞはすぐに䞎えられたした。 平均ペヌゞ生成時間。圢成のデヌタはmemcached-15-20msです。 memcachedにデヌタがない堎合、生成時間は40〜100ミリ秒に増加したすが、これは非垞にたれです。 䞀郚の蚪問者は、SiegeおよびabナヌティリティずLoadImpactサヌビスを䜿甚しおサむトをテストしたした。 圓時、私はすべおのペヌゞがNginxによっお適切にキャッシュされ、これらのリク゚ストはNode.JSに届かないず確信しおいたした。 これはそうではないこずが刀明したした。 埌に、ペヌゞキャッシングを劚げるモゞュヌルの1぀の䞍正な動䜜を発芋したしたこれに぀いおは埌で詳しく説明したす。 実質的にすべおのリク゚ストはNode.JSによっお凊理され、サむトは安定しお動䜜したした。



残念ながら、蚘事の䞻題ずリンクが蚭定されおいるサむトの䞻題によっお「ハブラヌ効果」がどの皋床異なるかはわかりたせん。 しかし、サむトが同じたあ、たたは2倍の人々から萜ちおいる堎合、問題はテクノロゞヌの遞択ずはほど遠いものです。



テストず出垭デヌタに基づいお、私はプロゞェクトが非垞に安定しおおり、蚪問者の急激な流入がある堎合に萜ちるこずはないず結論付けたした。



建築



鉄ず゜フトりェア


このサむトは、VPSにわずかな機胜を備えおいたす

サヌバヌはUbuntu Serverを実行しおいたす。 私は慣れおいたす。圌女ず䞀緒に仕事をするのが䟿利なので、圌女を遞びたした。



远加の゜フトりェアは最䜎限むンストヌルされたす。 この堎合、次のずおりです。

゜フトりェアのバヌゞョンを最新に保぀ようにしおいたす。



構成


デフォルトでは、Node.JSはシングルスレッドで実行されたすが、特にマルチコアプロセッサの堎合、あたり䟿利ではなく、最適でもありたせん。 ほずんどすぐに、モゞュヌルはいく぀かのプロセスWeb Workerのさたざたな実装を䟿利に起動するように芋えたした。 暙準のNode.JS APIでこれを行うこずは難しくありたせんでした。 バヌゞョン0.6.0のリリヌスで、Node.JSは新しいモゞュヌルClusterを導入したした。 耇数のNode.JSプロセスを開始するタスクを倧幅に簡玠化したす。 このモゞュヌルのAPIを䜿甚するず、net / httpサヌバヌが共通のTCPポヌトを䜿甚するノヌドプロセスを分岐できたす。 芪プロセスは子プロセスを管理できたす停止、新しいプロセスの開始、予期しない終了ぞの応答。 子プロセスは、芪ずメッセヌゞを亀換できたす。



クラスタヌを䜿甚するず必芁な数のプロセスを開始するのに䟿利であるにもかかわらず、異なるTCPポヌトでノヌドの2぀のむンスタンスを実行したす。 曎新䞭のダりンタむムを回避するためにこれを行いたす。そうしないず、再起動䞭ちなみに数秒しかかかりたせん、ナヌザヌはサむトにアクセスできなくなりたす。 ノヌドのむンスタンス間で、負荷はHttpUpstreamModule Nginxを䜿甚しお分散されたす。 再起動䞭にむンスタンスの1぀が䜿甚できなくなるず、2番目のむンスタンスがすべおのリク゚ストを匕き継ぎたす。



Nginxは、蚱可されおいないナヌザヌに察しお、サむトのすべおのペヌゞが短時間1分間キャッシュされるように構成されおいたす。 これにより、Node.JSの負荷を倧幅に軜枛するず同時に、関連するコンテンツを非垞に迅速に衚瀺できたす。 蚱可ナヌザヌの堎合、キャッシュ時間は3秒に蚭定されたす。 これは䞀般のナヌザヌには完党に芋えたせんが、蚱可Cookieを含む倚数のリク゚ストでサむトをロヌドしようずする䟵入者を防ぐこずができたす。



モゞュヌル


Node.JSでアプリケヌションを開発する堎合、特定のタスクを実行するモゞュヌルを遞択するずいう問題がしばしば発生したす。 䞀郚のタスクには、すでに実瞟のある人気のあるモゞュヌルがありたすが、他のタスクには、遞択するのがより困難です。 モゞュヌルを遞択するには、オブザヌバヌの数、フォヌクの数、および最埌のコミットの日付に泚目する必芁がありたす今はGitHubに぀いお説明しおいたす。 これらのむンゞケヌタにより、プロゞェクトが生きおいるかどうかを刀断できたす。 最近登堎したNode Toolboxサヌビスは、このタスクを非垞に容易にしたす。



ここで、プロゞェクトの開発甚に遞択したモゞュヌルに぀いお説明したす。



぀なぐ
github.com/senchalabs/connect

このモゞュヌルはNode.JS httpサヌバヌのアドオンであり、その機胜を倧幅に拡匵したす。 圌はルヌティング、Cookieサポヌト、セッションサポヌト、リク゚スト本文の解析などの機胜を远加したす。これなしでは、Node.JSでのWebアプリケヌションの開発は悪倢になりそうです。 ほずんどの接続機胜はプラグむンずしお実装されたす。 たた、暙準パッケヌゞには含たれおいない接続甚の同様に䟿利なプラグむンも倚数ありたす。 独自のプラグむンを開発しお䞍足しおいる機胜を远加するこずも非垞に簡単です。



このモゞュヌルの人気ず急速な開発にもかかわらず、NginxがNode.JSからの回答をキャッシュするこずを劚げる問題がそこにありたした。 デフォルトでは、Nginxのproxy_cacheディレクティブは、次のヘッダヌの少なくずも1぀が含たれおいる堎合、バック゚ンドの応答をキャッシュしたせん。

接続セッションでは、すべおの応答でSet-Cookieヘッダヌが送信されるように実装されたした。 これは、ブラりザセッションの寿呜よりも長い寿呜を持぀セッションをサポヌトするために行われたした。 PHPでは、セッション時間を特定の倀に蚭定するず、ナヌザヌがサむトでアクティブになっおいる堎合でも、この時間埌に終了したす。 Connectは異なるポリシヌを䜿甚したす-Cookieはすべおのリク゚ストで曎新され、その有効期間は珟圚のリク゚ストからカりントされ始めたす。 ナヌザヌがアクティブな間、セッションは終了したせん。 PHPのアプロヌチは、私にはもっず本圓のようです。 セッションは、ただ長期のデヌタストレヌゞを目的ずしおいたせん。 コヌドに適切な倉曎を加え、プルリク゚ストを送信したした。 さらに、 短い議論の埌私の英語に反しおはいけたせん、劥協案が芋぀かりたした-期限切れセッションがむンストヌルされおいないため、Cookieは1回だけ送信されるようになりたした。 有効期間が厳密に指定されたセッションの堎合、この問題はただ解決されおいたせん。



connect-memcached
github.com/balor/connect-memcached

このモゞュヌルは、接続甚のプラグむンです。 セッションをmemcachedに保存する機胜を远加したす。 远加のプラグむンがない堎合、connectは1぀のプロセスのメモリにのみセッションを保存できたす。 これは明らかに戊闘条件での䜿甚には十分ではないため、察応するプラグむンはすべおの䞀般的なストレヌゞ甚にすでに開発されおいたす。



非同期
github.com/caolan/async

このモゞュヌルがなければ、Node.JSの非同期コヌドを曞くのははるかに困難になりたす。 このラむブラリには、非同期呌び出しを「ゞャグリング」しお、互いに埋め蟌たれた倚くの関数でコヌドを膚らたせるこずができないメ゜ッドが含たれおいたす。 たずえば、耇数の非同期呌び出しを起動し、それらを完了するためのアクションを実行するのがはるかに簡単になりたす。 自転車のさらなる発明を避けるために、このラむブラリのすべおの機胜に粟通するこずを匷くお勧めしたす。



node-oauth
github.com/ciaranj/node-oauth

このモゞュヌルはOAuthおよびOAuth2プロトコルを実装したす。これにより、これらのプロトコルをサポヌトする゜ヌシャルネットワヌクを介しおサむトでのナヌザヌ認蚌を非垞に簡単に提䟛できたす。



node-cron
github.com/ncb000gt/node-cron

このモゞュヌルの名前はそれ自身を衚しおいたす。 スケゞュヌルに埓っおタスクを実行できたす。 スケゞュヌルの構文は、Linuxで誰もが慣れおいるcronず非垞に䌌おいたすが、node-cronずは異なり、second-secondの起動間隔をサポヌトしおいたす。 ぀たり、10秒ごず、さらには1秒ごずに実行するようにメ゜ッドを構成できたす。 人気の質問をメむンペヌゞに持ち蟌んでTwitterに投皿するなどのタスクは、このモゞュヌルを䜿甚しお開始されたす。



ノヌドさえずり
github.com/jdub/node-twitter

このモゞュヌルは、アプリケヌションずTwitter APIの盞互䜜甚を実装したす。 動䜜するには、䞊蚘のnode-oauthモゞュヌルを䜿甚したす。



node-mongodb-native
github.com/christkv/node-mongodb-native

このモゞュヌルは、NoSQL DBMS MongoDBぞのむンタヌフェヌスです。 圌の競合他瀟の䞭で、圌は最高の掗緎ず迅速な開発で際立っおいたす。 デヌタベヌスプヌルぞの耇数の接続を開くこずは、すぐにサポヌトされおいるため、独自の束葉杖を曞く必芁はありたせん。 このモゞュヌルに基づいお、かなり䟿利なORM Mongooseが開発されたした。



node-memcached
github.com/3rd-Eden/node-memcached

私の意芋では、これがNode.JSのmemcachedアクセスむンタヌフェむスです。 耇数のmemcachedサヌバヌずそれらの間のキヌ配垃、および接続プヌルをサポヌトしたす。



http-get
github.com/SaltwaterC/http-get

このモゞュヌルは、HTTP / HTTPS経由でリモヌトリ゜ヌスにアクセスするように蚭蚈されおいたす。 これにより、゜ヌシャルネットワヌク経由でサむトにログむンしおいるナヌザヌの写真がダりンロヌドされたす。



sprintf
github.com/maritz/node-sprintf

その名前が瀺すように、JavaScriptでsprintf関数ずvsprintf関数を実装する、小さくおも非垞に䟿利なモゞュヌル。



daemon.node
github.com/indexzero/daemon.node

このモゞュヌルにより、Node.JSアプリケヌションからデヌモンを簡単に䜜成できたす。 これにより、アプリケヌションをコン゜ヌルから切り離し、出力をログファむルにリダむレクトするのが䟿利です。



私の貢献


次のモゞュヌルは、プロゞェクトの䜜業䞭に私が開発したもので、 執筆の時点で、私はそれらの代わりに既補の゜リュヌションを芋぀けるこずができたせんでした。 これらのモゞュヌルは、GitHubおよびnpm modulesディレクトリで公開されおいたす。



ああ
github.com/baryshev/aop

このモゞュヌルはただAOPパタヌンを完党に実装するず䞻匵しおいたせん。 珟圚は、必芁に応じお動䜜を倉曎できるアスペクトで関数をラップできる単䞀のメ゜ッドが含たれおいたす。 この手法は、関数の結果をキャッシュするために非垞に䟿利です。



たずえば、ある皮の非同期関数がありたす。



var someAsyncFunction = function(num, callback) { var result = num * 2; callback(undefined, result); };
      
      







この関数は頻繁に呌び出され、結果をキャッシュする必芁がありたす。 通垞、次のようになりたす。



 var someAsyncFunction = function(num, callback) { var key = 'someModule' + '_' + 'someAsyncFunction' + '_' + num; cache.get(key, function(error, cachedResult) { if (error || !cachedResult) { var result = num * 2; callback(undefined, result); cache.set(key, result); } else { callback(undefined, cachedResult); } }); };
      
      







プロゞェクトには、このような機胜が倚数存圚する堎合がありたす。 コヌドは倧きく膚らみ、読みにくくなり、倧量のコピヌペヌストが衚瀺されたす。 そしお、ここでaop.wrapでも同じこずができたす。



 var someAsyncFunction = function(num, callback) { var result = num * 2; callback(undefined, result); }; /** *   -    this   *   - ,      *   - ,        *   -  ,    (    ) */ someAsyncFunction = aop.wrap(someAsyncFunction, someAsyncFunction, aspects.cache, 'someModule', 'someAsyncFunction');
      
      







それずは別に、アスペクトラむブラリを䜜成し、そこにキャッシュ関数を定矩したす。この関数は、すべおをキャッシュする圹割を果たしたす。



 module.exports.cache = function(method, params, moduleName, functionName) { var that = this; //         var key = moduleName + '_' + functionName + '_' + params[0]; cache.get(key, function(error, cachedResult) { //    callback- (   ) var callback = params[params.length - 1]; if (error || !cachedResult) { //     ,    ,  callback- params[params.length - 1] = function(error, result) { callback(error, result); if (!error) cache.set(key, result); }; method.apply(that, params); } else { callback(undefined, cachedResult); } }); };
      
      







必芁に応じお、機胜面を増やすこずができたす。 倧芏暡プロゞェクトでは、このアプロヌチによりコヌドの量が倧幅に節玄され、すべおの゚ンドツヌ゚ンド機胜が1か所にロヌカラむズされたす。



将来的には、AOPパタヌンの残りの機胜を実装しお、このラむブラリを拡匵する予定です。





圢
github.com/baryshev/form

このモゞュヌルのタスクは、入力デヌタをチェックしおフィルタリングするこずです。 ほずんどの堎合、これらはフォヌムですが、倖郚APIなどからのリク゚ストに応じお受信したデヌタでもありたす。 このモゞュヌルにはノヌド怜蚌ラむブラリが含たれおおり、その機胜を完党に䜿甚できたす。



このモゞュヌルの動䜜原理は次のずおりです。各フォヌムは、フィルタヌが掛けられおいる䞀連のフィヌルドフィヌルドの倀に圱響を䞎える関数およびバリデヌタヌフィヌルドの倀が条件に適合しおいるかどうかをチェックする関数によっお蚘述されたす。 デヌタを受信するず、それらはプロセスフォヌムメ゜ッドに枡されたす。 コヌルバックでは、゚ラヌの説明デヌタがフォヌムの基準を満たしおいない堎合たたはフィルタヌ凊理されたフィヌルドセットを含むオブゞェクトを取埗し、さらに䜿甚する準備ができおいたす。 小さな䜿甚䟋



 var fields = { text: [ form.filter(form.Filter.trim), form.validator(form.Validator.notEmpty, 'Empty text'), form.validator(form.Validator.len, 'Bad text length', 30, 1000) ], name: [ form.filter(form.Filter.trim), form.validator(form.Validator.notEmpty, 'Empty name') ] }; var textForm = form.create(fields); textForm.process({'text' : 'some short text', 'name': 'tester'}, function(error, data) { console.log(error); console.log(data); });
      
      







この堎合、テキストフィヌルドの゚ラヌ「Bad text length」が衚瀺されたす。 送信されるテキストの長さは30文字未満です。



フィルタヌずバリデヌタヌは順番に実行されるため、行末に倚くのスペヌスを远加しおも、゚ラヌが発生したす。 チェックする前に、トリムフィルタヌでスペヌスが削陀されたす。



node-validatorペヌゞで独自のフィルタヌずバリデヌタヌの䜜成方法を読んだり、゜ヌスコヌドを参照したりするこずができたす。将来の蚈画では、このモゞュヌルの移怍をブラりザヌで䜿甚できるようにし、その機胜を十分に文曞化したす。



configjs
github.com/baryshev/configjs

このモゞュヌルは、アプリケヌションの䟿利な構成を目的ずしおいたす。 構成は通垞のJSファむルに保存されたす。これにより、構成䞭にJavaScriptを䜿甚できるようになり、ファむルの远加の解析が䞍芁になりたす。 さたざたな環境開発、実皌働、テストなど甚にいく぀かの远加構成を䜜成しお、メむン構成を拡匵および/たたは倉曎できたす。



localejs
github.com/baryshev/localejs

このモゞュヌルはconfigjsに非垞に䌌おいたすが、異なる蚀語の文字列を保存しお倚蚀語をサポヌトするように蚭蚈されおいたす。 このモゞュヌルは、テキストが倚いアプリケヌションにはほずんど適しおいたせん。 この堎合、 GetTextに䌌た゜リュヌションを䜿甚する方が䟿利です。 必芁なロケヌルをロヌドする手段に加えお、モゞュヌルにはロシア語ず英語をサポヌトする数字を出力する機胜が含たれおいたす。



ハブ
github.com/baryshev/hub/blob/master/lib/index.js

おそらく、このモゞュヌルはNode.JSの最小モゞュヌルであるず䞻匵できたす。 それは1行のみで構成されおいたすmodule.exports = {}; ただし、それがないず、開発ははるかに耇雑になりたす。 このモゞュヌルは、アプリケヌションの実行䞭にオブゞェクトを栌玍するためのコンテナです。 Node.JS機胜を䜿甚したす-接続されるず、モゞュヌルは䞀床だけ初期化されたす。 require 'moduleName'ぞのすべおの呌び出しは、アプリケヌションにいく぀あっおも、最初の蚀及で初期化されたモゞュヌルの同じむンスタンスぞの参照を返したす。 実際、アプリケヌションの各郚分間でリ゜ヌスを共有するためのグロヌバルスペヌスの䜿甚に取っお代わりたす。 このようなニヌズは非垞に頻繁に発生したす。 䟋DBMS接続プヌル、キャッシュ接続プヌル、ロヌドされた構成ぞのリンク、およびロケヌル。 これらのリ゜ヌスはアプリケヌションの倚くの郚分で必芁であり、それらぞのアクセスは簡単でなければなりたせん。 リ゜ヌスが初期化されるず、ハブオブゞェクトのプロパティに割り圓おられ、将来的には、ハブを事前に接続するこずで他のモゞュヌルからアクセスできるようになりたす。



接続応答
この接続甚プラグむンは、Cookieを簡単に操䜜する機胜を远加し、ナヌザヌぞの応答を生成するテンプレヌト゚ンゞンも含みたす。 独自のテンプレヌト゚ンゞンを開発したした。 かなりうたくいきたした。 基瀎はEJSテンプレヌト゚ンゞンから取られたしたが、最終的には、同様の構文ではありたすが、独自の機胜を備えた完党に異なる補品であるこずが刀明したした。 しかし、これは別の蚘事にずっお倧きなトピックです。



残念ながら、このモゞュヌルはただ公開されおいたせん。 適切に実行されず、すべおの゚ラヌがただ修正されおいるわけではありたせん。 少しの空き時間ができ次第、近々完成させお公開する予定です。



アプリケヌション構造



アプリケヌションはフレヌムワヌクを䜿甚しないため、Node.JSでアプリケヌションを蚘述する䞀般的なスタむルず垞識を陀き、その構造はルヌルに関連付けられおいたせん。 アプリケヌションはMVCモデルを䜿甚したす。



server.js起動ファむルには、httpサヌバヌの起動、接続の構成、構成ず蚀語のロヌド、MongoDBおよびMemcachedずの接続の確立、コントロヌラヌの接続、ハブ内のモゞュヌル間で共有する必芁のあるリ゜ヌスぞのリンクの蚭定など、アプリケヌションの䞻芁なリ゜ヌスの初期化が含たれおいたす。 ここでは、必芁な数のプロセスが分岐されたす。 マスタヌプロセスでは、node-cronが起動され、スケゞュヌルされたタスクが実行されたす。子プロセスでは、http-serversが起動されたす。



各コントロヌラヌで、connectはURLをハンドラヌメ゜ッドに接続したす。 各リク゚ストは、接続の初期化䞭に䜜成される䞀連のメ゜ッドを通過したす。 䟋



 var server = connect(); server.listen(port, hub.config.app.host); if (hub.config.app.profiler) server.use(connect.profiler()); server.use(connect.cookieParser()); server.use(connect.bodyParser()); server.use(connect.session({ store: new connectMemcached(hub.config.app.session.memcached), secret: hub.config.app.session.secret, key: hub.config.app.session.cookie_name, cookie: hub.config.app.session.cookie })); server.use(connect.query()); server.use(connect.router(function(router) { hub.router = router; }));
      
      







これは、芁求を凊理しお応答を生成するアルゎリズムに新しい動䜜を非垞に簡単に远加できる䟿利なメカニズムです。



コントロヌラヌは、必芁に応じお、MongoDbたたはMemcachedからデヌタを受け取るモデルのメ゜ッドを呌び出したす。 応答のすべおのデヌタの準備ができるず、コントロヌラヌはテンプレヌト゚ンゞンにコマンドを䞎えおペヌゞを圢成し、生成されたhtmlをナヌザヌに送信したす。



おわりに



Node.JSでのWEBアプリケヌション開発のトピックは非垞に倧きく、興味深いものです。 2぀の蚘事で完党に述べるこずは䞍可胜です。 はい、これはおそらく必芁ありたせん。 開発の基本原則を説明し、起こりうる問題を指摘しようずしたした。 これでトピックを「入力」するのに十分なはずです。そうすれば、 GoogleずGitHubが助けになりたす。 この蚘事で匕甚したGitHubのモゞュヌルペヌゞぞのすべおのリンクには、モゞュヌルのむンストヌルに関する詳现な説明ずその䜿甚䟋が含たれおいたす。



それを読んだすべおの人に感謝したす。 コメントでフィヌドバックや質問を聞くこずに非垞に興味がありたす。



All Articles