我差点以为PM2的多进程是假的

发布于 2023-09-08 04:16:08 字数 7079 浏览 52 评论 0

最近一直在接触 Node 服务端相关的东西,前端的知识真的是像大海一样:我太水。

在掘金读到一篇关于 深入理解 Node.js 中的进程与线程 , 里面讲到了线程阻塞,为了让自己印象深刻,我就复现了这个过程,然后用最近学会的 pm2 开启多线程解决这个顽疾时 -> 失败了,当发起计算请求时,再发起其他请求,就一直被阻塞,源码地址见下方。

node 进程,深入理解源码地址

说说为什么

// pm2.json
{
      "name": "koaProcess",
      "script": "npm",
      "args": "start"
}
// package.json
{
     "start": "node index.js"
}

当我在命令行敲下 pm2 start pm2.json 时,得到了下面的结果。看似多进程运行 Node 服务已成功,但。。。

当我在浏览器发起计算请求时(/page/compute),再发起其他请求(/page/helloworld),发现后者被前者阻塞,两个菊花一直在转:

我百思不得其解,难道 PM2,没有开启多进程,难道在 Node 中也是雾霾。我开始对自己看到的文章起了怀疑,我开始怀疑 Node 的多进程,于是我又回过头来用 child_process 的 fork, 用 cluster 的 fork 来体验多线程,和书上讲的一样,多进程是有效果的。于是我在自己的代码中加入了这样一句代码:

function addTime(content) {
  return `${content}, the time is ${Date.now()}, the process pid is: ${process.pid}`
}

然后我发现所有的请求,process.pid 都是一个,且都不在 PM2 list 展示出子进程 pid,然后当我打印出全部的 log,我发现了下面这些信息:嗯, 子进程只是启动,有报错,启起来了,但根本没提供服务,console.log 也没有打印。根据打印的错误,应该是 pm2 执行命令错误。

最后再一次仔细看了 pm2 的文档,发现新版是不支持 npm run server 这样的 npm 命令了;于是我就改了一下:

{
      "name": "koaProcess",
      "script": "index.js"
}

一切正常,多进程起了作用,原来是自己的使用错误。关于其他两种开启多进程,给个大概的代码示例,理论嘛,看文章最后大佬们的讲解:

child_process 的 fork 开启子进程

const server = http.createServer((req, res) => {
    console.log('req', req.url);
    if(req.url == '/page/compute'){
      const compute = fork('./server/fork_compute.js');
        compute.send('开启一个新的子进程');

        // 当一个子进程使用 process.send() 发送消息时会触发 'message' 事件
        compute.on('message', sum => {
            res.end(`Sum is ${sum}`);
            compute.kill();
        });

        // 子进程监听到一些错误消息退出
        compute.on('close', (code, signal) => {
            console.log(`收到 close 事件,子进程收到信号 ${signal} 而终止,退出码 ${code}`);
            compute.kill();
        });
        console.log('end');
    }else{
        res.end(`ok, the time is ${Date.now()}`);
    }
});

cluster 手动开启多进程

//超时
const timeout = null;

// console.log('start', cluster.isWorker, cluster.isMaster ? 'master' : 'fork');
//master 进程
if(cluster.isMaster) {
  //fork 多个工作进程
  for(let i = 0; i < cpusNum; i++) {
    creatServer();
  }
} else {
  //worker 进程
  const angelServer = new AngelServer({
    routerUrl: path.join(process.cwd(), 'server/router.js'),//路由地址
    configUrl: path.join(process.cwd(), 'config/constants.js')  
    //默认读取 config/config.default.js
  });

  //服务器优雅退出
  angelServer.app.on('error', err => {
    //发送一个自杀信号
    process.send({ act: 'suicide' });
    cluster.worker.disconnect();
    angelServer.server.close(() => {
      //所有已有连接断开后,退出进程
      process.exit(1);
    });
    //5 秒后退出进程
    timeout = setTimeout(() => {
      process.exit(1);
    },5000);
  });
}

// master.js
//创建服务进程  
function creatServer() {
  const worker = cluster.fork();
  console.log(`工作进程已经重启 pid: ${worker.process.pid}`);
  //监听 message 事件,监听自杀信号,如果有子进程发送自杀信号,则立即重启进程。
  //平滑重启 重启在前,自杀在后。
  worker.on('message', (msg) => {
    //msg 为自杀信号,则重启进程
    if(msg.act == 'suicide') {
      creatServer();
    }
  });

  //清理定时器。
  worker.on('disconnect', () => {
    clearTimeout(timeout);
  });
}

最后

如果对上面有疑惑,可直接下载源码,运行调试,感受多进程。关于 Node 多进程,一张来自于阿里大佬们的图画的很清楚:

一张图片

淘宝大佬们的三篇古董:关于 Node:


你好,我也遇到了一样的问题,先上代码
node 14.18.2

const Koa = require('koa');
const cluster = require("cluster")

const PORT = 4000

const clusterWorkerSize = 2

if (clusterWorkerSize > 1) {
  if (cluster.isMaster) {
    console.log(`master ${process.pid}`)
    for (let i=0; i < clusterWorkerSize; i++) {
      cluster.fork()
    }
    cluster.on("exit", function(worker) {
      console.log("Worker", worker.id, " has exitted.")
    })
  } else {
    console.log(`worker ${process.pid}`)
    createServer()
  }
} else {
  createServer()
}
function createServer(){
  const app = new Koa();
  app.use(async (ctx) => {
   const url = ctx.request.url;
   if (url === '/') {
   ctx.body = {name: 'xxx', age: 14}
   }
   if(url==='/compute'){
      const sum = compute()
      ctx.body={sum}
    }
  })
  app.listen(PORT, () => {
    console.log(`listening on port ${PORT} with the single worker ${process.pid}`)
  })
}

function compute(){
  let sum=0
  for (let i = 0; i <100000000000 ; i++) {
    sum+=i  
  }
  return sum
}

运行后,输出

master 19489
worker 19490
worker 19491
listening on port 4000 with the single worker 19490
listening on port 4000 with the single worker 19491

创建了一个 master 和两个 worker,worker 分别是两个 http 服务,
然后我请求 /compute/ 后者还是被阻塞了
难道我一定要把 createServer 代码写到另一个文件里,然后 fork(这个文件) 吗?


尴尬,代码没问题,是测试方法有问题。

第二次请求阻塞,是因为 cluster 的调度策略。

当集群创建后,master 会随机选一个进程作为优先进程来处理所有请求,所以 compute 请求进来后,a 进程阻塞了, / 请求进来 master 还是会分配给 a 进程,并不会直接分配给 b 进程,当再请求一次 / ,master 才会交给 b 进程来处理。

而且即使在 compute 后,停顿 20 秒后请求还是会阻塞,也就是说 cluster 模块缺少了一种场景,根据 worker 的返回超时来判断这个进程已经不能使用了

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

感受沵的脚步

暂无简介

文章
评论
28 人气
更多

推荐作者

櫻之舞

文章 0 评论 0

弥枳

文章 0 评论 0

m2429

文章 0 评论 0

野却迷人

文章 0 评论 0

我怀念的。

文章 0 评论 0

更多

友情链接

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文