node.js cluster多进程、负载均衡和平滑重启

1 cluster多进程

cluster通过好几代的发展,如今已经比较好使了。利用cluster,能够自动完成子进程worker分配request的事情,就再也不须要本身写代码在master进程中robin式给每一个worker分配任务了。node

const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
  // Fork workers.
  for (var i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.on('exit', (worker, code, signal) => {
    console.log(`worker ${worker.process.pid} died`);
  });
} else {
  // Workers can share any TCP connection
  // In this case it is an HTTP server
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end('hello world\n');
  }).listen(80);
}

 

上述简单的代码,就实现了根据CPU个数,建立多个worker。至于实际项目中,是正好一个核对应一个worker呢,仍是一个核对应二、3个worker呢,就视状况而定了。若是项目中,等待其余服务器(例如数据库)响应特别长时间,设置2个以上worker应该会更好。linux

不过通常而言,一个CPU对一个worker就挺好的了。shell

 

那么,整个架构就相似这样:数据库

image

 

Master进程,须要作的就是监控worker的生命周期,若是发现worker挂掉了,就重启worker,并作好相应的log。服务器

整个架构没有太大的难点,重点就是作好一些细节处理,例如重启、日志、5秒心跳包等。架构

 

多进程的架构,相对原始的单进程+pm2重启好处确定多不少,整个node服务会更稳定,不会忽然完全挂了。负载均衡

另外,对比pm2多进程,也有优点,主要是master的逻辑掌握在开发本身手中,能够作好自定义的log和邮件、短信告警。curl

 

为了整个nodejs服务管理方便,在master进程中,咱们通常开启管理端口的监听,例如12701,经过命令行curl 127.0.0.1:12701:xxx发起一个简单的http get请求,轻松管理。ui

例如xxx传入reload,能够做为服务器重启的指令。this

 

2 负载均衡

说到多进程,目的确定是尽量利用多核CPU,提升单机的负载能力。

但每每在实际项目中,受到业务逻辑的处理时间长短和系统CPU调度影响,致使实际上全部进程的负载并非理想的完全均衡。

官方也说了:

In practice however, distribution tends to be very unbalanced due to operating system scheduler vagaries. Loads have been observed where over 70% of all connections ended up in just two processes, out of a total of eight.

翻译一下:70%的请求最终都落到2个worker身上,而这2个worker占用更多的CPU资源。

那么在实际项目部署,咱们能够尝试更进一步的措施:绑定CPU。4核CPU,咱们fork出4个worker,每一个worker分别绑定到#1-#4 CPU。

node并无给咱们提供现成的接口,不过咱们可使用linux的命令:taskset

在node中,咱们可使用child_process执行shell。

cp.exec('taskset -cp ' + (cpu) + ' ' + process.pid,{ 
    timeout: 5000 
},function(err,data,errData){ 
    if(err){ 
        logger.error(err.stack); 
    } 
    
    if(data.length){ 
        logger.info('\n' + data.toString('UTF-8')); 
    } 
    
    if(errData.length){ 
        logger.error('\n' + errData.toString('UTF-8')); 
    } 
});

 

按实际状况来看,效果是不错的。

imageimageimageimage

 

 

3 平滑重启

每次发布新版本,服务器必然须要重启。

简单粗暴的,杀掉主进程,所有重启,必然会有一段时间的服务中断。

image

对于小企业还好,能够安排在凌晨重启,但对于大公司大产品来讲,就不能这么粗暴了。

 

那么咱们须要平滑重启,实现重启过程当中,服务不中断。

策略并不复杂,但很是有效:

一、worker进程轮流重启,间隔时间;

二、worker进程并非直接重启,而是先关闭新请求监听,等当前请求都返回了,再重启。

  try {
        // make sure we close down within 30 seconds
        var killtimer = setTimeout(() => {
          process.exit(1);
        }, 30000);

        // stop taking new requests.
        server.close();

        // Let the master know we're dead.  This will trigger a
        // 'disconnect' in the cluster master, and then it will fork
        // a new worker.
        cluster.worker.disconnect();

      } catch (er2) {
      }

 

实施了平滑重启后,服务器的吞吐率会平滑不少。

image

相关文章
相关标签/搜索