closertb / closertb.github.io

浏览issue 或 我的网站,即可查看我的所有博客
https://closertb.site
32 stars 0 forks source link

可怕,我差点以为PM2的多进程是假的 #36

Open closertb opened 5 years ago

closertb commented 5 years ago

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

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

说说为什么

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

当我在命令行敲下pm2 start pm2.json时,得到了下面的结果。看似多进程运行Node服务已成功,但。。。 image 当我在浏览器发起计算请求时(/page/compute),再发起其他请求(/page/helloworld),发现后者被前者阻塞,嗯,两个菊花一直在转: image
我百思不得其解,难道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执行命令错误。 image 最后再一次仔细看了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手动开启多进程
```js
//超时
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:

MalavitaC commented 2 years ago

你好,我也遇到了一样的问题,先上代码 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(这个文件)吗?

MalavitaC commented 2 years ago

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

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

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

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