hustxiaoc / node.js

Node.js学习笔记
325 stars 85 forks source link

使用module-unique优化你的Node.js项目 #4

Open hustxiaoc opened 9 years ago

hustxiaoc commented 9 years ago

Node.js发展到今天如此的受欢迎,除了自身强劲的性能优势外,也离不开node社区热情的开发者们贡献的超过100,000个模块,基本涵盖了项目开发中的方方面面,npm也让模块的开发与使用变的非常的方便,几行命令便能代替以前使用其他人开发的模块时复制粘贴等繁琐的操作。

使用Node.js开发项目时,会把用到的所有第三方模块写进package.json文件中。

"dependencies": {
    "address": "0.0.3",
    "body-parser": "~1.3.0",
    "bookshelf": "^0.7.7",
    "cookie-parser": "~1.1.0",
    "es6-promise": "^1.0.0",
    "express": "~4.4.3",
    "graceful": "~0.1.0",
    "knex": "^0.6.22",
    "lodash": "^2.4.1",
    "midway": "~1.3.4",
    "module-unique": "^1.0.7",
    "moment": "^2.8.1",
    "mysql": "^2.4.1",
    "node-acl": "~2.2.1",
    "recluster": "~0.3.6",
    "serve-favicon": "~2.0.1",
    "tair": "~0.9.8",
    "urllib": "~1.3.0"
  }

这样我们提交代码时也不用提交那么多第三方node_modules到服务器上,直接在服务器上使用npm install便能下载所有的第三方模块。

问题来了

最开始你开发的不到10M的项目最后发布上线可能就变成了100M+, 更不能接受的是服务器内存也跟着到了100M+, 而且你会发现大部分内存不是被你自己的脚本占用,其实你自己写的脚本仅仅只占了很小的一部分内存。

为何一个小小的应用会如此的耗内存?当执行npm install时,安装到本地的第三方模块远远的超出你的package.json中所表明的依赖模块,因为每个模块也都有自己的依赖模块需要安装,于是不可避免的会出现一个模块在整个项目中被多次安装的情况,被多次加载到内存那也是当然的事情了,比如说npm上被依赖最多的模块underscore, 相信大家自己写的模块中不少也会依赖它。假设这个模块占用内存1M,项目中有20个模块依赖它,占用内存便直接升到20M,19M的内存就这样白白被浪费掉了。像这样的模块一多, 然后再来个多核,只能呵呵了,启动node程序后内存占用不多才怪呢。

如何优化

那么这个时候你就可以使用module-unique了,module-unique是一个专门用来解决相同的模块被多次加载的模块。这里提的相同模块不仅仅是模块名相同,版本也必须一致。module-unique能够保证你的项目中,相同版本的模块始终只会被加载一次,不管它被多少模块依赖,那么内存浪费问题就迎刃而解了。下面是我自己的项目中使用module-unique优化前后的内存占用情况。

优化效果

before using module-unique

http://gtms02.alicdn.com/tps/i2/TB1OBmVGFXXXXaZXXXXhN4IGpXX-930-98.png

after using module-unique

http://gtms03.alicdn.com/tps/i3/TB1nU9QGFXXXXb4XFXXDeGnJXXX-886-144.png

差不多减少了30%+的内存使用,优化空间巨大,你值得尝试!

module-unique的使用也非常的简单, `require('module-unique').init()`` , 详细的使用方法可以参考module-unique文档https://github.com/hustxiaoc/module-unique

#!/usr/bin/env node
'use strict';

require('module-unique').init();//一定要在你代码的最前面执行

var app = require('../app'),
    logger = require('midway').getLogger(),
    graceful = require('graceful');

app.set('port', process.env.PORT || 6001);

var server = app.listen(app.get('port'), function() {
    logger.info('Midway server listening on port ' + server.address().port);
});

graceful({
    server: server,
    killTimeout: 30 * 1000,
    error: function(err, throwErrorCount) {
        if (err.message) {
            err.message +=
                ' (uncaughtException throw ' + throwErrorCount +
                ' times on pid:' + process.pid + ')';
        }
        logger.error(err);
    }
});

module-unique还有一个非常有用的信息,列出整个Node.js项目中被使用到的所有模块信息。

#!/usr/bin/env node
'use strict';

var unique = require('module-unique');
unique.init();

var app = require('../app'),
    graceful = require('graceful');

app.set('port', process.env.PORT || 6001);

var server = app.listen(app.get('port'), function() {
    console.log('server listening on port ' + server.address().port);
});

var info = {}
for(var m in unique.module) {
    var versions = Object.keys(unique.module[m]);
    if(versions.length) {
        info[m] = versions;
    }
}
console.log(unique.module);

最后输出的内容如下:

{
    "midway": [
        "2.0.3"
    ],
    ...
    "escape-html": [
        "1.0.1"
    ],
    "debug": [
        "1.0.2",
        "2.1.0",
        "1.0.4",
        "2.0.0",
        "0.7.4",
        "0.8.1"
    ],
    "ms": [
        "0.6.2"
    ],
    "methods": [
        "1.0.1"
    ],
    "urllib": [
        "1.4.1",
        "0.5.11",
        "1.3.0",
        "0.5.17"
    ],
    "midway-security": [
        "1.0.0"
    ],
    "midway-xtpl": [
        "0.10.5"
    ],
    "midway-tms": [
        "0.8.5"
    ],
    "midway-vmcommon": [
        "0.4.3"
    ],
    "cookie-parser": [
        "1.1.0"
    ],
    "semver": [
        "2.3.2"
    ],
    "backbone": [
        "1.1.0"
    ],
    "underscore": [
        "1.6.0"
    ],
    "trigger-then": [
        "0.3.0"
    ]
}

有了这些信息,那么更多的问题来了:

是不是发现你的项目其实还有更大的优化空间呢?

优化原理

介绍完了module-unique的使用场景与使用方法,大家可能会好奇module-unique到底对我的node做了什么,为什么它能够最开始提出的问题。

module-unique最核心的部分就是重写了Node.js内部的模块加载。但是大家放心使用,重写的并不多,只是在加载第三方模块时做了点改动。Node.js加载第三方模块时会先读取package.json文件,找到里面的main,main对于的文件就是该模块的入口文件。但是Node.js内部并不读取除了main之外的信息,比如模块名称,模块版本信息等,所以就会出现上面说的同一个版本的模块被多次加载的情况。

module-unique就是在这一步做了些优化,当读取到package.json文件后获取当前模块名称以及版本信息,入口文件路径,并将这些信息做缓存。下次读取同样的模块并且版本号一致时便直接从缓存读取,这样便避免了相同版本模块被多次读取的情况。下面的代码是主要改动点,感兴趣的同学可以直接查看该模块完整源码https://github.com/hustxiaoc/module-unique/blob/master/index.js

function tryPackage(requestPath, exts) {
  var pkg = readPackage(requestPath);

  if (!(pkg && pkg.main)) return false;

  var name = pkg.name,
      version = pkg.version,
      main = pkg.main;

  var cache = moduleCache[name];
  if(!cache){
    cache = moduleCache[name] = {};
  }

  var filename =  cache[version];

  if(!filename) {
     filename = path.resolve(requestPath, main);
     cache[version] = filename;
  }

  return tryFile(filename) || tryExtensions(filename, exts) ||
         tryExtensions(path.resolve(filename, 'index'), exts);
}

尝试过将这个改动提交给Node.js官方从源头解决掉这个问题,但是很遗憾没有被采纳,他们也提出了自己的看法:

What if you have two packages with the same name, but installed from different sources? Or for >example you have one npm linked package that you are live editing and the same package >dependency in another module?

Sorry, but this optimisation can and is performed by npm, when it is installing packages.

至于npm如何从安装模块上解决这个问题的我也一直不是很清楚,记得有个npm-shrinkwrap和这个问题有关,但是也一直没有运行成功。

不过我们依然可以愉快的使用module-unique很轻松的解决这些问题~

相关链接

module-unique github文档https://github.com/hustxiaoc/module-unique

Node.js 内部模块查找加载原理 https://github.com/joyent/node/blob/master/lib/module.js

barretlee commented 8 years ago

不错哦~

AaronLingxi commented 6 years ago

用node.js开发的项目上线发布前 还需不需要像前后端开发的那样前端打个包 ?