背景
Node.js を使用して Web アプリケーションを開発した学生は、Node.js のプロセスを更新する前に、新しく変更したコードを再起動する必要があるという問題に悩まされたと思います。開発に PHP を使用することに慣れている学生にとっては、予想通り、PHP は世界で最高のプログラミング言語であると感じるでしょう。手動でプロセスを再起動することは、非常に面倒な作業の重複であるだけでなく、アプリケーションの規模が大きくなると、起動時間は徐々に無視できなくなります。
もちろん、プログラマーとして、どの言語を使用するとしても、そのようなことで苦しむことはありません。この種の問題を解決する最も直接的かつ普遍的な方法は、ファイルの変更を監視し、プロセスを再起動することです。この方法は、放棄されたノードスーパーバイザー、現在普及している PM2、または比較的軽量のノード開発など、多くの成熟したソリューションによっても提供されており、これらはすべてこのアイデアに基づいています。
この記事では、少し変更するだけで、真のゼロリスタート ホット アップデート コードを実現し、Node.js で Web アプリケーションを開発する際の煩わしいコード アップデートの問題を解決できる別のアイデアを提供します。
一般的な考え方
コードのホット アップデートと言えば、現時点で最も有名なのは Erlang 言語のホット アップデート機能です。この言語は、高い同時実行性と分散プログラミングを特徴としており、主なアプリケーション シナリオは証券取引やゲーム サーバーなどです。 。これらのシナリオでは多かれ少なかれ、サービスには運用中の運用とメンテナンスの手段が必要であり、コードのホット アップデートはその非常に重要な部分であるため、最初に Erlang のアプローチを簡単に見てみましょう。
私は Erlang を使用したことがないため、以下の内容はすべて伝聞です。Erlang のコード ホット アップデート実装について詳しく正確に理解したい場合は、公式ドキュメントを参照するのが最善です。
Erlang のコードのロードは code_server というモジュールによって管理されます。起動時に必要なコードを除いて、ほとんどのコードは code_server によってロードされます。
code_server は、モジュール コードが更新されたことを検出すると、モジュールを再ロードします。その後、新しいリクエストは新しいモジュールを使用して実行されますが、まだ実行中のリクエストは引き続き古いモジュールを使用して実行されます。
新しいモジュールがロードされると、古いモジュールには「old」というラベルが付けられ、新しいモジュールには「current」というラベルが付けられます。次回のホット アップデート中に、Erlang はまだ実行中の古いモジュールをスキャンして強制終了し、このロジックに従ってモジュールの更新を続けます。
Erlang のすべてのコードでホット アップデートが許可されるわけではありません。カーネル、stdlib、コンパイラ、その他の基本モジュールなどの基本モジュールは、デフォルトでは更新できません。
Node.js にも code_server に似たモジュール (require システム) があることがわかります。そのため、Erlang のアプローチを Node.js でも試す必要があります。 Erlang のアプローチを理解することで、Node.js のコードのホット アップデートを解決する際の重要な問題を大まかに要約できます
モジュールコードを更新する方法
新しいモジュールを使用してリクエストを処理する方法
古いモジュールのリソースを解放する方法
それでは、これらの問題点を一つずつ分析していきましょう。
モジュールコードの更新方法
モジュール コードの更新の問題を解決するには、Node.js のモジュール マネージャー実装を読み取り、module.js に直接リンクする必要があります。簡単に読むと、コア コードが Module._load にあることがわかります。コードを単純化して投稿してみましょう。
// Check the cache for the requested file. // 1. If a module already exists in the cache: return its exports object. // 2. If the module is native: call `NativeModule.require()` with the // filename and return the result. // 3. Otherwise, create a new module for the file and save it to the cache. // Then have it load the file contents before returning its exports // object. Module._load = function(request, parent, isMain) { var filename = Module._resolveFilename(request, parent); var cachedModule = Module._cache[filename]; if (cachedModule) { return cachedModule.exports; } var module = new Module(filename, parent); Module._cache[filename] = module; module.load(filename); return module.exports; }; require.cache = Module._cache;
それを検証するための小さなプログラムを書いてください
// main.js function cleanCache (module) { var path = require.resolve(module); require.cache[path] = null; } setInterval(function () { cleanCache('./code.js'); var code = require('./code.js'); console.log(code); }, 5000); // code.js module.exports = 'hello world';
新しいモジュールを使用してリクエストを処理する方法
すべてのユーザーの使用習慣に合わせて、この問題を拡張する例として Express を直接使用します。実際、同様の考え方を使用して、ほとんどの Web アプリケーションに適用できます。まず第一に、私たちのサービスが Express のデモのようなもので、すべてのコードが同じモジュール内にある場合、モジュールをホットロードすることはできません
var express = require('express'); var app = express(); app.get('/', function(req, res){ res.send('hello world'); }); app.listen(3000);
// app.js 基础代码 var express = require('express'); var app = express(); var router = require('./router.js'); app.use(router); app.listen(3000); // router.js 业务代码 var express = require('express'); var router = express .Router(); // 此处加载的中间件也可以自动更新 router.use(express.static('public')); router.get('/', function(req, res){ res.send('hello world'); }); module.exports = router;
然而很遗憾,经过这样处理之后,虽然成功的分离了核心代码, router.js 依然无法进行热更新。首先,由于缺乏对更新的触发机制,服务无法知道应该何时去更新模块。其次, app.use 操作会一直保存老的 router.js 模块,因此即使模块被更新了,请求依然会使用老模块处理而非新模块。
那么继续改进一下,我们需要对 app.js 稍作调整,启动文件监听作为触发机制,并且通过闭包来解决 app.use 的缓存问题
// app.js var express = require('express'); var fs = require('fs'); var app = express(); var router = require('./router.js'); app.use(function (req, res, next) { // 利用闭包的特性获取最新的router对象,避免app.use缓存router对象 router(req, res, next); }); app.listen(3000); // 监听文件修改重新加载代码 fs.watch(require.resolve('./router.js'), function () { cleanCache(require.resolve('./router.js')); try { router = require('./router.js'); } catch (ex) { console.error('module update failed'); } }); function cleanCache(modulePath) { require.cache[modulePath] = null; }
再试着修改一下 router.js 就会发现我们的代码热更新已经初具雏形了,新的请求会使用最新的 router.js 代码。除了修改 router.js 的返回内容外,还可以试试看修改路由功能,也会如预期一样进行更新。
当然,要实现一个完善的热更新方案需要更多结合自身方案做一些改进。首先,在中间件的使用上,我们可以在 app.use 处声明一些不需要热更新或者说每次更新不希望重复执行的中间件,而在 router.use 处则可以声明一些希望可以灵活修改的中间件。其次,文件监听不能仅监听路由文件,而是要监听所有需要热更新的文件。除了文件监听这种手段外,还可以结合编辑器的扩展功能,在保存时向 Node.js 进程发送信号或者访问一个特定的 URL 等方式来触发更新。
如何释放老模块的资源
要解释清楚老模块的资源如何释放的问题,实际上需要先了解 Node.js 的内存回收机制,本文中并不准备详加描述,解释 Node.js 的内存回收机制的文章和书籍很多,感兴趣的同学可以自行扩展阅读。简单的总结一下就是当一个对象没有被任何对象引用的时候,这个对象就会被标记为可回收,并会在下一次GC处理的时候释放内存。
那么我们的课题就是,如何让老模块的代码更新后,确保没有对象保持了模块的引用。首先我们以 如何更新模块代码 一节中的代码为例,看看老模块资源不回收会出现什么问题。为了让结果更显著,我们修改一下 code.js
// code.js var array = []; for (var i = 0; i < 10000; i++) { array.push('mem_leak_when_require_cache_clean_test_item_' + i); } module.exports = array; // app.js function cleanCache (module) { var path = require.resolve(module); require.cache[path] = null; } setInterval(function () { var code = require('./code.js'); cleanCache('./code.js'); }, 10);
好~我们用了一个非常笨拙但是有效的方法,提高了 router.js 模块的内存占用,那么再次启动 main.js 后,就会发现内存出现显著的飙升,不到一会 Node.js 就提示 process out of memory。然而实际上从 app.js 与 router.js 的代码中观察的话,我们并没发现哪里保存了旧模块的引用。
我们借助一些 profile 工具如 node-heapdump 就可以很快的定位到问题所在,在 module.js 中我们发现 Node.js 会自动为所有模块添加一个引用
function Module(id, parent) { this.id = id; this.exports = {}; this.parent = parent; if (parent && parent.children) { parent.children.push(this); } this.filename = null; this.loaded = false; this.children = []; }
因此相应的,我们可以调整一下cleanCache函数,将这个引用在模块更新的时候一并去除。
// app.js function cleanCache(modulePath) { var module = require.cache[modulePath]; // remove reference in module.parent if (module.parent) { module.parent.children.splice(module.parent.children.indexOf(module), 1); } require.cache[modulePath] = null; } setInterval(function () { var code = require('./code.js'); cleanCache(require.resolve('./code.js')); }, 10);
再执行一下,这次好多了,内存只会有轻微的增长,说明老模块占用的资源已经正确的释放掉了。
使用了新的 cleanCache 函数后,常规的使用就没有问题,然而并非就可以高枕无忧了。在 Node.js 中,除了 require 系统会添加引用外,通过 EventEmitter 进行事件监听也是大家常用的功能,并且 EventEmitter 有非常大的嫌疑会出现模块间的互相引用。那么 EventEmitter 能否正确的释放资源呢?答案是肯定的。
// code.js var moduleA = require('events').EventEmitter(); moduleA.on('whatever', function () { });
code.js モジュールが更新され、すべての参照が移動されると、内部イベント リスナーを含む他の未リリースのモジュールによって参照されない限り、moduleA も自動的に解放されます。
このシステムでは対処できない、不正な形式の EventEmitter アプリケーション シナリオが 1 つだけあります。つまり、code.js は実行されるたびにグローバル オブジェクト上のイベントをリッスンし、グローバル オブジェクト上で継続的にイベントをマウントすることになります。同時に、Node.js は、検出されたイベント バインディングが多すぎることをすぐに通知します。これはメモリ リークの可能性があります。
この時点で、require システム内の Node.js によって自動的に追加された参照が処理されている限り、Erlang のような次のホット アップデートは実現できませんが、古いモジュールのリソースのリサイクルは大きな問題ではないことがわかります。残りの古いモジュールはスキャンなどのきめ細かい制御の対象となりますが、古いモジュールのリソース解放の問題は合理的な回避方法で解決できます。
Web アプリケーションのもう 1 つの参照問題は、未リリースのモジュールまたはコア モジュールに、app.use などのホット アップデートが必要なモジュールへの参照があることです。その結果、古いモジュールのリソースが解放されず、新しいモジュールが解放されません。リクエストを正しく処理できません。処理には新しいモジュールを使用してください。この問題の解決策は、グローバル変数または参照の公開されたエントリを制御し、ホット アップデートの実行中にエントリを手動で更新することです。たとえば、「新しいモジュールを使用してリクエストを処理する方法」のルーターのカプセル化は、このエントリの制御を通じて、router.js で他のモジュールをどのように参照しても、エントリのリリースとともに解放される例です。 。
リソースの解放を引き起こす可能性のあるもう 1 つの問題は、オブジェクトのライフサイクルを解放しないようにする setInterval のような操作です。ただし、この種のテクノロジを Web アプリケーションで使用することはほとんどないため、Web アプリケーションでは注意を払いません。計画。
エピローグ
これまで、Web アプリケーションにおける Node.js コードのホット アップデートに関する 3 つの主要な問題を解決してきました。ただし、Node.js 自体には保持されたオブジェクトに対する効果的なスキャン メカニズムがないため、setInterval によって引き起こされる問題を 100% 排除することはできません。古いモジュールのリソースを解放できません。現在当社が提供しているYOG2フレームワークでは、ホットアップデートによる迅速な開発を実現するため、主に開発・デバッグ期にこの技術を利用しています。実稼働環境でのコード更新では、オンライン サービスの安定性を確保するために、引き続き再起動または PM2 のホット リロード機能が使用されます。
ホット アップデートは実際にはフレームワークやビジネス アーキテクチャと密接に関係しているため、この記事では一般的な解決策を提供しません。参考までに、YOG2 フレームワークでこのテクノロジーがどのように使用されているかを簡単に紹介しましょう。 YOG2 フレームワーク自体はフロントエンド サブシステムとバックエンド サブシステムの間でのアプリの分割をサポートしているため、更新戦略はアプリの粒度でコードを更新することです。同時に、fs.watch のような操作には互換性の問題があり、fs.watchFile などの代替操作にはより多くのパフォーマンスが消費されるため、YOG2 のテスト マシン デプロイメント機能を組み合わせて、更新する必要があることをフレームワークに通知しました。新しいコードのアップロードとデプロイ。アプリの粒度でモジュール キャッシュを更新すると、ルーティング キャッシュとテンプレート キャッシュが更新され、すべてのコードの更新が完了します。
Express や Koa などのフレームワークを使用している場合は、この記事の方法に従い、独自のビジネス ニーズとメイン ルートへのいくつかの変更を組み合わせるだけで、このテクノロジをうまく適用できます。

JavaScript文字列置換法とFAQの詳細な説明 この記事では、javaScriptの文字列文字を置き換える2つの方法について説明します:内部JavaScriptコードとWebページの内部HTML。 JavaScriptコード内の文字列を交換します 最も直接的な方法は、置換()メソッドを使用することです。 str = str.replace( "find"、 "置換"); この方法は、最初の一致のみを置き換えます。すべての一致を置き換えるには、正規表現を使用して、グローバルフラグGを追加します。 str = str.replace(/fi

それで、あなたはここで、Ajaxと呼ばれるこのことについてすべてを学ぶ準備ができています。しかし、それは正確には何ですか? Ajaxという用語は、動的でインタラクティブなWebコンテンツを作成するために使用されるテクノロジーのゆるいグループ化を指します。 Ajaxという用語は、もともとJesse Jによって造られました

記事では、JavaScriptライブラリの作成、公開、および維持について説明し、計画、開発、テスト、ドキュメント、およびプロモーション戦略に焦点を当てています。

この記事では、ブラウザでJavaScriptのパフォーマンスを最適化するための戦略について説明し、実行時間の短縮、ページの負荷速度への影響を最小限に抑えることに焦点を当てています。

この記事では、ブラウザ開発者ツールを使用した効果的なJavaScriptデバッグについて説明し、ブレークポイントの設定、コンソールの使用、パフォーマンスの分析に焦点を当てています。

マトリックスの映画効果をあなたのページにもたらしましょう!これは、有名な映画「The Matrix」に基づいたクールなJQueryプラグインです。プラグインは、映画の古典的な緑色のキャラクター効果をシミュレートし、画像を選択するだけで、プラグインはそれを数値文字で満たされたマトリックススタイルの画像に変換します。来て、それを試してみてください、それはとても面白いです! それがどのように機能するか プラグインは画像をキャンバスにロードし、ピクセルと色の値を読み取ります。 data = ctx.getimagedata(x、y、settings.greasize、settings.greasize).data プラグインは、写真の長方形の領域を巧みに読み取り、jQueryを使用して各領域の平均色を計算します。次に、使用します

この記事では、jQueryライブラリを使用してシンプルな画像カルーセルを作成するように導きます。 jQuery上に構築されたBXSLiderライブラリを使用し、カルーセルをセットアップするために多くの構成オプションを提供します。 今日、絵のカルーセルはウェブサイトで必須の機能になっています - 1つの写真は千の言葉よりも優れています! 画像カルーセルを使用することを決定した後、次の質問はそれを作成する方法です。まず、高品質の高解像度の写真を収集する必要があります。 次に、HTMLとJavaScriptコードを使用して画像カルーセルを作成する必要があります。ウェブ上には、さまざまな方法でカルーセルを作成するのに役立つ多くのライブラリがあります。オープンソースBXSLiderライブラリを使用します。 BXSLiderライブラリはレスポンシブデザインをサポートしているため、このライブラリで構築されたカルーセルは任意のものに適合させることができます

データセットは、APIモデルとさまざまなビジネスプロセスの構築に非常に不可欠です。これが、CSVのインポートとエクスポートが頻繁に必要な機能である理由です。このチュートリアルでは、Angular内でCSVファイルをダウンロードおよびインポートする方法を学びます


ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

AI Hentai Generator
AIヘンタイを無料で生成します。

人気の記事

ホットツール

mPDF
mPDF は、UTF-8 でエンコードされた HTML から PDF ファイルを生成できる PHP ライブラリです。オリジナルの作者である Ian Back は、Web サイトから「オンザフライ」で PDF ファイルを出力し、さまざまな言語を処理するために mPDF を作成しました。 HTML2FPDF などのオリジナルのスクリプトよりも遅く、Unicode フォントを使用すると生成されるファイルが大きくなりますが、CSS スタイルなどをサポートし、多くの機能強化が施されています。 RTL (アラビア語とヘブライ語) や CJK (中国語、日本語、韓国語) を含むほぼすべての言語をサポートします。ネストされたブロックレベル要素 (P、DIV など) をサポートします。

SublimeText3 英語版
推奨: Win バージョン、コードプロンプトをサポート!

MinGW - Minimalist GNU for Windows
このプロジェクトは osdn.net/projects/mingw に移行中です。引き続きそこでフォローしていただけます。 MinGW: GNU Compiler Collection (GCC) のネイティブ Windows ポートであり、ネイティブ Windows アプリケーションを構築するための自由に配布可能なインポート ライブラリとヘッダー ファイルであり、C99 機能をサポートする MSVC ランタイムの拡張機能が含まれています。すべての MinGW ソフトウェアは 64 ビット Windows プラットフォームで実行できます。

ZendStudio 13.5.1 Mac
強力な PHP 統合開発環境

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ホットトピック



