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

1 cluster多进程

cluster经过好几代的发展,现在已经比较好使了。利用cluster,可以自动完成子进程worker分配request的事情,就不再需要自己写代码在master进程中robin式给每个worker分配任务了。

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呢,还是一个核对应2、3个worker呢,就视情况而定了。如果项目中,等待其他服务器(例如数据库)响应特别长时间,设置2个以上worker应该会更好。

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

那么,整个架构就类似这样:

Master进程,需要做的就是监控worker的生命周期,如果发现worker挂掉了,就重启worker,并做好相应的log。

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

多进程的架构,相对原始的单进程+pm2重启好处肯定多很多,整个node服务会更稳定,不会突然彻底挂了。

另外,对比pm2多进程,也有优势,主要是master的逻辑掌握在开发自己手中,可以做好自定义的log和邮件、短信告警。

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

例如xxx传入reload,可以作为服务器重启的指令。

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')); 
    } 
});

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

3 平滑重启

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

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

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

那么我们需要平滑重启,实现重启过程中,服务不中断。

策略并不复杂,但非常有效:

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

2、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) {
      }

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏IMWeb前端团队

从零开始学web安全(1)

本文作者:IMWeb 刘志龙 原文出处:IMWeb社区 未经同意,禁止转载 一直对web安全很感兴趣,却一直只是略懂一点。决定从现在开始学起web安全...

1798
来自专栏Java进阶架构师

闪电侠:2018.3 IntelliJ IDEA 重大更新:支持CPU火焰图,新增酷炫主题

就在上周,JetBrain 公司发布了 Java 集成开发环境 IntelliJ IDEA 最新版本 2018.3Beta,本篇文章,我将根据官方博客以及自己的...

1061
来自专栏维C果糖

Hello World · GitHub 指南

简介 Hello World 项目是计算机编程历史中悠久的传统。在我们学习新知识的时候,她也是一个简单的练习。现在,就让我们一起了解 GitHub 吧! 你将学...

1769
来自专栏Ksher

Kubernetes的服务网格(第4部分):通过流量切换持续部署

翻译人:Ksher,该成员来自云+社区翻译社

3378
来自专栏云计算

Kubernetes的服务网格(第4部分):通过流量切换持续部署

除了服务发现,重要指标和TLS之外,linkerd还具有强大的路由语言,称为dtabs,可以用来改变请求的方式 - 甚至是单个请求 - 流经应用程序拓扑。在本文...

4237
来自专栏13blog.site

Spring+SpringMVC+MyBatis+easyUI整合基础篇(六)maven整合SSM

前言   承接前文《Spring+SpringMVC+MyBatis+easyUI整合基础篇(五)讲一下maven》,本篇所讲述的是如何使用maven与原s...

4955
来自专栏MoeLove

关于 webpack 你所忽略的细节(附源码分析)

随着前端技术的火热发展,工程化,模块化和组件化的思想已逐步成为主流,与之相应的,就需要有一整套工具流可以支撑起它。

953
来自专栏前端杂货铺

提升node.js中使用redis的性能

某基于node.js开发的业务系统向外提供了一个dubbo服务,提供向第三方缓存查询、设置多项业务数据并聚合操作结果。在QPS达到800时(两台虚拟机,每台机器...

1692
来自专栏Crossin的编程教室

如何安装 Python 的第三方模块

正所谓“人生苦短,我用 Python”。Python 的一大优势就是有丰富且易用的第三方模块,省去了大量重复造轮子的时间,节约了众多开发者的生命。对于已经熟悉 ...

2709
来自专栏编程

从w命令开始,系统状态命令哪个最强大?哪个你最熟悉?

最快的时间内,通过不同命令对Linux系统状态的把控,也是运维的基本功。今天一起来汇总一下,看看都有哪些。 1 使用w查看系统负载 相信所有的linux管理员最...

1978

扫码关注云+社区