资讯专栏INFORMATION COLUMN

使用node子进程spawn,exec踩过的坑

cppprimer / 3721人阅读

摘要:最后发现使用子进程打开还真的就是使用到一定程度就挂掉。上面的简单流程就是启动一个子进程。逻辑就是,记录子进程的大小,一旦超过就掉子进程。我们在使用时,不知道设置,默认的是当我们子进程日志达到时,自动掉了。

如何在项目中实现热更新中提到的一个坑child_process的exec使用问题,下面文章会详细介绍下,debug到node源码中的详细介绍,不容错过。

child_process介绍

Nodejs是单线程单进程的,但是有了child_process模块,可以在程序中直接创建子进程,并使用主进程和子进程之间实现通信。

对于child_process的使用,大家可以找找其他文章,介绍还是比较多的,本文主要讲一下踩过的坑。

踩过的坑

在使用EHU(esl-hot-update)这个工具时(对于工具的介绍,参考前面的文章如何在项目中实现热更新),发现用子进程启动项目,经常性的挂掉。然后也不知道为什么,甚至怀疑子进程的效率比较低。

最后为了进一步验证,在同样的环境下,一个直接启动服务,一个是使用require("child_process").exec("...") 方式启动。

最后发现使用子进程打开还真的就是使用到一定程度就挂掉。虽然此时也没有什么解决方案,但是至少能把问题定位在子进程上了,而不是其他工具代码导致程序挂掉。

定位问题

定位了问题后,网上查找child_process相关资料,发现exec与spawn方法的区别与陷阱 这篇文章提到几点:

exec与spawn是有区别的

exec是对spawn的一个封装

最重要的exec比spawn多了一些默认的option

基于以上几点有些头绪了,但是还是没有明确的解决方案。

最后一个办法,直接断点到nodejs的child_process.js模块中尝试看看问题出在哪里。

exec和spawn的源码区分

断点进去看后,豁然开朗,exec是对execFile的封装,execFile又是对spawn 的封装。

每一层封装都是加强一些易用性以及功能。

直接看源码:

exports.exec = 
    function(command /*, options, callback*/) {
          var opts = normalizeExecArgs.apply(null, arguments);
          return exports.execFile(opts.file,
                                  opts.args,
                                  opts.options,
                                  opts.callback);
};

exec对于execFile的封装是进行参数处理

处理的函数:

normalizeExecArgs

关键逻辑

if (process.platform === "win32") {
    file = process.env.comspec || "cmd.exe";
    args = ["/s", "/c", """ + command + """];
    // Make a shallow copy before patching so we don"t clobber the user"s
    // options object.
    options = util._extend({}, options);
    options.windowsVerbatimArguments = true;
  } else {
    file = "/bin/sh";
    args = ["-c", command];
  }

将简单的command命名做一个,win和linux的平台处理。

此时execFile接受到的就是一个区分平台的command参数。

然后重点来了,继续debug,execFile中:

var options = {
    encoding: "utf8",
    timeout: 0,
    maxBuffer: 200 * 1024,
    killSignal: "SIGTERM",
    cwd: null,
    env: null
};

有这么一段,设置了默认的参数。然后后面又是一些参数处理,最后调用spawn方法启动子进程。

上面的简单流程就是启动一个子进程。到这里都没有什么问题。

继续看,重点又来了:

用过子进程应该知道这个child.stderr

下面的代码就解答了为什么子进程会挂掉。

child.stderr.addListener("data", function(chunk) {
    stderrLen += chunk.length;

    if (stderrLen > options.maxBuffer) {
      ex = new Error("stderr maxBuffer exceeded.");
      kill();
    } else {
      if (!encoding)
        _stderr.push(chunk);
      else
        _stderr += chunk;
    }
});

逻辑就是,记录子进程的log大小,一旦超过maxBufferkill掉子进程。

原来真相在这里。我们在使用exec时,不知道设置maxBuffer,默认的maxBuffer是200K,当我们子进程日志达到200K时,自动kill()掉了。

exec和spawn的使用区分

不过exec确实比spawn在使用上面要好很多

例如我们执行一个命令

使用exec

require("child_process").exec("edp webserver start");

使用spawn

linux下这么搞

var child = require("child_process").spawn(
   "/bin/sh", 
   ["-c","edp webserver start"],
   {
       cwd: null,
       env: null,
       windowsVerbatimArguments: false
   }
);

win下

var child = require("child_process").spawn(
   "cmd.exe",
   ["/s", "/c", "edp webserver start"],
   {
       cwd: null,
       env: null,
       windowsVerbatimArguments: true
   }
);

可见spawn还是比较麻烦的。

解决方案

知道上面原因了,解决方案就有几个了:

子进程的系统,不再输出日志

maxBuffer这个传一个足够大的参数

直接使用spawn,放弃使用exec

我觉得最优的方案是直接使用spawn,解除maxBuffer的限制。但是实际处理中,发现直接考出normalizeExecArgs这个方法去处理平台问题,在win下还是有些不好用,mac下没有问题。所以暂时将maxBuffer设置了一个极大值,保证大家的正常使用。然后后续在优化成spawn方法。

吐槽

其实没有怎么理解,execFile对于spawn封装加maxBuffer的这个逻辑,而且感觉就算加了,是否也可以给一个方式,去掉maxBuffer的限制。

难道是子进程的log量会影响性能?

感想

其实在解决这个问题时,发现这个差异/坑还比较意外,因为自身对于node其实还不是很熟,这个子进程的使用其实也是在ehu中第一次遇到。

感受比较多的就是有时候正对问题去学习/研究,其实效率特别高。

微信公众号

博客地址

http://tangguangyao.github.io/

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/78332.html

相关文章

  • 记录我开发gpm,Git仓库管理工具的历程

    摘要:前言记录下开发的一些事,加强自己对的应用。雏形参考于的项目管理,发现非常的优雅。尝试通过修改权限为,最后无果不了了之。方式二询问筛选会在终端监听输入的关键字,根据关键字筛选出一系列的仓库。自己也在使用,打算长期维护。 前言 记录下开发的一些事,加强自己对nodejs的应用。共勉! 有让你操蛋的事,就有需求 对于经常参与开源贡献,或者看见某些库,像试试手的人来说,经常需要git clon...

    thursday 评论0 收藏0
  • Node.js中spawnexec的异同比较

    摘要:返回值对象利用给定的命令以及参数执行一个新的进程,如果没有参数数组,那么将默认是一个空数组。当子进程执行完毕后将会执行的回调函数,参数有返回值对象在中运行一个命令,并缓存命令的输出。 前言 众所周知,Node.js在child_process模块中提供了spawn和exec这两个方法,用来开启子进程执行指定程序。这两个方法虽然目的一样,但是既然Node.js为我们提供了两个方法,那它...

    garfileo 评论0 收藏0
  • nodeJS多进程

    摘要:通过将的给出来的进程。恩吞吐率关于吞吐率有多种解读,一种是描绘服务器单位时间处理请求的能力。而根据这个描述的话他的单位就为而这个指标就是上面数据中的当然,肯定是越大越好了吞吐量这个和上面的吞吐率很有点关系的。 首先郑重声明:nodeJS 是一门单线程!异步!非阻塞语言!nodeJS 是一门单线程!异步!非阻塞语言!nodeJS 是一门单线程!异步!非阻塞语言! 重要的事情说3遍。 因为...

    happen 评论0 收藏0
  • nodeJS多进程

    摘要:通过将的给出来的进程。恩吞吐率关于吞吐率有多种解读,一种是描绘服务器单位时间处理请求的能力。而根据这个描述的话他的单位就为而这个指标就是上面数据中的当然,肯定是越大越好了吞吐量这个和上面的吞吐率很有点关系的。 首先郑重声明:nodeJS 是一门单线程!异步!非阻塞语言!nodeJS 是一门单线程!异步!非阻塞语言!nodeJS 是一门单线程!异步!非阻塞语言! 重要的事情说3遍。 因为...

    james 评论0 收藏0
  • Node.js child_process模块解读

    摘要:而且方式创建的子进程与父进程之间建立了通信管道,因此子进程和父进程之间可以通过的方式发送消息。与事件的回调函数有两个参数和,代码子进程最终的退出码,如果子进程是由于接收到信号终止的话,会记录子进程接受的值。 在介绍child_process模块之前,先来看一个下面的代码。 const http = require(http); const longComputation = () =>...

    baiy 评论0 收藏0

发表评论

0条评论

cppprimer

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<