Nodejs搭建多进程Web服务器实现过程

目录
  • 前言
  • 父子进程间通信
    • 负载均衡
    • 句柄传递
  • 集群
    • 子进程事件
    • 自动重启
  • 总结

前言

上节我们讲到,通过 fork() 或者其他API,创建子进程之后,可以通过 send()process.on('message') 进行父子进程间的通信。这样就实现了主进程代理请求到工作进程,实现了 Nodejs集群

父子进程间通信

负载均衡

通过代理,可以避免端口不能重复监听的问题,甚至可以在代理进程上做适当的负载均衡,使得每个子进程可以较为均衡地执行任务。下面我们构建了一个简单的 Web 服务器,并实现在两个工作进程之间做简单的负载均衡。

主进程,负责代理到对应进程中:

// main.js
const { fork } = require('child_process');
const normal = fork('subprocess.js', ['normal']);
const special = fork('subprocess.js', ['special']);
// Open up the server and send sockets to child. Use pauseOnConnect to prevent
// 套接字在发送给子进程之前不会被读取
const server = require('net').createServer({ pauseOnConnect: true });
let flag = 0;
server.on('connection', (socket) => {
  flag++;
  // this is special priority.
  if (flag % 2 === 0) {
    special.send('socket', socket);
    return;
  }
  // This is normal priority.
  normal.send('socket', socket);
});
server.listen(1337);

这是工作进程,接收socket对象并做出响应:

// subprocess.js
process.on('message', (m, socket) => {
  if (m === 'socket') {
    // Check that the client socket exists.
    // It is possible for the socket to be closed between the time it is
    if (socket) {
      // console.log(`Request handled with ${process.argv[2]} priority`);
      socket.end(`Request handled with ${process.argv[2]} priority, running on ${process.pid}`);
    }
  }
});

然后我又编写了一个 Nodejs 脚本,来发出十个 HTTP 请求:

const cp = require("child_process");
for (let i = 0; i < 10; i++) {
  cp.exec(`curl --http0.9 "http://127.0.0.1:1337"`, (err, stdout, stderr) => {
    console.log(`finished: ${i}, and received: `, stdout);
  })
}

最后运行结果如下:

句柄传递

在使用 send() 方法时,我们注意到,除了能通过IPC发送数据外,还能发送句柄。第二个可选参数就是一个句柄:

child.send(message, [sendHandle]);

句柄是一种可以用来标识资源的引用,它的内部包含了指向对象的文件描述符。比如句柄可以用来标识一个服务器端socket对象、一个客户端socket对象、一个UDP套接字、一个管道等。

在主进程将句柄发送给子进程之后,工作模型就从主进程响应用户请求变成了子进程监听用户活动:

进程对象send()方法可以发送的句柄类型包括如下几种:

  • net.Socket。TCP套接字。
  • net.Server。TCP服务器,任意建立在TCP服务上的应用层服务都可以享受到它带来的好处。
  • net.Native。C++层面的TCP套接字或IPC管道。
  • dgram.Socket。UDP套接字。
  • dgram.Native。C++层面的UDP套接字。

另外要注意,send()方法能发送消息和句柄并不意味着它能发送任意对象,message 参数和文件句柄都要先通过 JSON.stringfy() 进行序列化后再放入IPC通道中:

集群

通过 child_process模块,我们完成了父子进程的创建和通信,已经初步搭建了一个Node集群。还有一些问题需要考虑:

  • 性能问题。
  • 多个工作进程的存活状态管理。
  • 工作进程的平滑重启。
  • 配置或者静态数据的动态重新载入。
  • 其他细节。

这其中最重要的便是集群的稳定性,这决定了该服务模型能否真正用于实践生成中。虽然我们创建了很多工作进程,但每个工作进程依然是在单线程上执行的,它的稳定性还不能得到完全的保障。我们需要建立起一个健全的机制来保障Node应用的健壮性。

子进程事件

父进程能监听到的,与子进程相关的事件:

  • error:当子进程无法被复制创建、无法被杀死、无法发送消息时会触发该事件。
  • exit:子进程退出时触发该事件。如果是正常退出,这个事件的第一个参数为退出码,否则为null。如果进程是通过kill()方法被杀死的,会得到第二个参数,它表示杀死进程时的信号。
  • close:在子进程的标准输入输出流中止时触发该事件,参数与exit相同。
  • disconnect:在父进程或子进程中调用disconnect()方法时触发该事件,在调用该方法时将关闭监听IPC通道

除了 send() 外,还能通过 kill() 方法给子进程发送消息。kill() 方法并不能真正地将通过IPC相连的子进程杀死,它只是给子进程发送了一个系统信号。默认情况下,父进程将通过 kill() 方法给子进程发送一个 SIGTERM信号

// 子进程
child.kill([signal]);
// 当前进程
process.kill(pid, [signal]);
// 监听
process.on(signal, callback)

在POSIX标准中,有一套完备的信号系统,在命令行中执行kill -l可以看到详细的信号列表,如下所示:

而 Node 提供了这些信号对应的信号事件,每个进程都可以监听这些信号事件。这些信号事件是用来通知进程的,每个信号事件有不同的含义,进程在收到响应信号时,应当做出约定的行为:

process.on('SIGTERM', () => {
    console.log("got sigterm, exiting...");
    process.exit(1);
});
console.log("process running on: ", process.pid);
process.kill(process.pid, "SIGTERM");

自动重启

有了父子进程之间的相关事件之后,就可以在这些关系之间创建出需要的机制了,至少我们能够通过监听子进程的 exit事件 来获知其退出的信息。接着前文的多进程架构,我们在主进程上要加入一些子进程管理的机制,比如重新启动一个工作进程来继续服务:

主进程代码:

// master.js
// master.js
const { fork } = require('child_process');
const cpus = require('os').cpus();
const server = require('net').createServer();
server.listen(1337);
const workers = {};
// process.on('uncaughtException', function (err) {
//   console.log(`Master uncaughtException:\r\n`);
//   console.log(err);
// });
const createWorker = () => {
  const worker = fork('./worker.js');
  // 收到信号后立即重启新进程
  worker.on('message', function (message) {
    if (message.act === 'suicide') {
      createWorker();
    }
  });
  // 某个进程终止时重新启动新的进程
  worker.on('exit', () => {
    console.log('Worker ' + worker.pid + ' exited.');
    delete workers[worker.pid];
    // createWorker();
  });
  // 句柄转发
  worker.send('server', server);
  workers[worker.pid] = worker;
  console.log('Create worker. pid: ' + worker.pid);
};
for (let i = 0; i < cpus.length; i++) {
  createWorker();
}
// server.close();
// 进程自己退出时,让所有工作进程退出
process.on('exit', () => {
  for (let pid in workers) {
    workers[pid].kill();
  }
});

子进程代码:

// worker.js
const http = require('http');
const server = http.createServer((req, res) => {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('handled by child, pid is ' + process.pid + '\n');
  // 抛出异常,捕获后终止进程
  throw new Error('throw exception');
});
var worker;
process.on('message', (m, tcp) => {
  if (m === 'server') {
    worker = tcp;
    worker.on('connection', (socket) => {
      server.emit('connection', socket);
    });
  }
});
// 捕获异常后终止进程
process.on('uncaughtException', (err) => {
  // 主动发出信号,避免等待连接断开时收到新请求而缺少进程无法响应
  process.send({
    act: 'suicide'
  });
  // 停止接收新的连接
  worker.close(function () {
    // 所有已有连接断开后,退出进程
    process.exit(1);
  });
  // 避免长连接请求长时间无法终止,5s后自动终止
  setTimeout(() => {
    process.exit(1);
  }, 5000)
});

运行父进程 master.js,控制台中会打印出开启的进程 PID

在 Linux 中,你可以直接使用 kill -9 [pid] 来终止进程。在 Windows 中,你需要打开任务管理器,找到 node.exe 的进程,终止其中某个。此时命令行会显示该进程被终止了,然后重新开启一个新的进程。

当然,你也可以使用我们之前写的 run.js 脚本,每发起一个请求,子进程响应请求之后会抛出一个异常,异常在捕获之后会终止该进程。

我们之前写的 run.js 脚本是并行执行的,此时会存在多个请求被分配到同一个 socket ,即分配到同一个进程中执行。那么就会存在互斥的问题,即某个请求结束后就终止该进程,导致其他请求无法获得响应而终止。此时你需要将 exec 方法改为同步方法:

const cp = require("child_process");
const cpus = require("os").cpus();
const sleep = (delay) => {
  const now = Date.now();
  while (Date.now() - now < delay);
  return;
}
for (let i = 0; i < cpus.length; i++) {
  const out = cp.execSync(`curl --http0.9 "http://127.0.0.1:1337"`);
  sleep(1000);
  console.log(out.toString());
}

该模型一旦有异常出现,主进程会创建新的工作进程来为用户服务,旧的进程一旦处理完已有连接就自动断开。整个过程使得我们的应用的稳定性和健壮性大大提高:

总结

至此,我们完成了一个简单的基于父子进程通信、具备异常重启进程功能的 Web服务器 就已经搭建完成了。对于 Nodejs 多进程编程你也有了初步的了解。接下来我们将介绍 cluster模块,并介绍一下在 Nodejs 中进行多线程编程。

以上就是Nodejs搭建多进程Web服务器实现过程的详细内容,更多关于Nodejs搭建多进程Web服务器的资料请关注我们其它相关文章!

(0)

相关推荐

  • Nodejs中解决cluster模块的多进程如何共享数据问题

    前述 nodejs在v0.6.x之后增加了一个模块cluster用于实现多进程,利用child_process模块来创建和管理进程,增加程序在多核CPU机器上的性能表现.本文将介绍利用cluster模块创建的多线程如何共享数据的问题. 进程间数据共享 首先举个简单的例子,代码如下: var cluster = require('cluster'); var data = 0;//这里定义数据不会被所有进程共享,各个进程有各自的内存区域 if (cluster.isMaster) { //主进程

  • nodejs基础之多进程实例详解

    本文实例讲述了nodejs基础之多进程.分享给大家供大家参考,具体如下: Node.js 多进程 我们都知道 Node.js 是以单线程的模式运行的,但它使用的是事件驱动来处理并发,这样有助于我们在多核 cpu 的系统上创建多个子进程,从而提高性能. 每个子进程总是带有三个流对象:child.stdin, child.stdout 和child.stderr.他们可能会共享父进程的 stdio 流,或者也可以是独立的被导流的流对象. Node 提供了 child_process 模块来创建子进程

  • 深入理解NodeJS 多进程和集群

    进程和线程 "进程" 是计算机系统进行资源分配和调度的基本单位,我们可以理解为计算机每开启一个任务就会创建至少一个进程来处理,有时会创建多个,如 Chrome 浏览器的选项卡,其目的是为了防止一个进程挂掉而应用停止工作,而 "线程" 是程序执行流的最小单元,NodeJS 默认是单进程.单线程的,我们将这个进程称为主进程,也可以通过 child_process 模块创建子进程实现多进程,我们称这些子进程为 "工作进程",并且归主进程管理,进程之间默

  • 详解如何利用Nodejs构建多进程应用

    目录 前言 进程的创建和使用 多核利用率 创建子进程 进程间通信 IPC 总结 前言 JavaScript 主线程运行在单个进程的单个线程上.这样做的好处是: 程序状态是单一的,在没有多线程的情况下没有锁.线程同步问题, 操作系统在调度时因为较少上下文的切换,可以很好地提高CPU的使用率. 但是单进程单线程并非完美的结构,一旦线程中某段代码发生异常阻塞,会阻塞代码执行而浪费资源.同时,如今CPU基本均是多核的,服务器往往还有多个CPU. 因此 Nodejs 不可避免的面临两个问题:如何充分利用多

  • Nodejs搭建多进程Web服务器实现过程

    目录 前言 父子进程间通信 负载均衡 句柄传递 集群 子进程事件 自动重启 总结 前言 上节我们讲到,通过 fork() 或者其他API,创建子进程之后,可以通过 send() 和 process.on('message') 进行父子进程间的通信.这样就实现了主进程代理请求到工作进程,实现了 Nodejs集群: 父子进程间通信 负载均衡 通过代理,可以避免端口不能重复监听的问题,甚至可以在代理进程上做适当的负载均衡,使得每个子进程可以较为均衡地执行任务.下面我们构建了一个简单的 Web 服务器,

  • 浅谈使用nodejs搭建web服务器的过程

    使用 Node 创建 Web 服务器 什么是 Web 服务器? Web服务器一般指网站服务器,是指驻留于因特网上某种类型计算机的程序,Web服务器的基本功能就是提供Web信息浏览服务.它只需支持HTTP协议.HTML文档格式及URL,与客户端的网络浏览器配合. 大多数 web 服务器都支持服务端的脚本语言(php.python.ruby)等,并通过脚本语言从数据库获取数据,将结果返回给客户端浏览器. 目前最主流的三个Web服务器是Apache.Nginx.IIS. Node.js 提供了 htt

  • nodejs创建简易web服务器与文件读写的实例

    web服务器至少有以下几个特点: 1.24小时不停止的工作,也就是说这个进程要常驻在内存中 2.24小时在某一端口监听,如: http://localhost:8080, www服务器默认端口80 3.要能够处理基本的请求:如get, post 在node js中创建一台服务器非常的简单,因为node自带http模块,该模块可以帮助我们非常快速搭建一台web服务器,来处理一个简单的请求. const http = require("http"); var server = http.c

  • golang搭建静态web服务器的实现方法

    我胡汉三又回来啦.好久没发文了,为保持平台上的活跃度,我今天就分享下个刚学到的知识,使用golang搭建静态web服务器,亲测可用,附代码! 使用过golang语言的程序猿都应该知道,在使用golang开发的时候,我们是不需要诸如iis,apache,nginx,kangle等服务器支持的. 为什么呢? 原因是,golang的net/http包中已经提供了HTTP的客户端与服务端实现方案. 网上言论都说golang不适合做web开发,相对php.java..net.nodejs等各类后端语言来说

  • Centos5.4+Nginx-0.8.50+UWSGI-0.9.6.2+Django-1.2.3搭建高性能WEB服务器

    之前一直使用Nginx+Fastcgi来搭建python web服务器,本文介绍Nginx+UWSGI组合来实现.uWSGI 是一个快速的.纯C语言开发的.自维护的.对开发者友好的WSGI服务器,旨在提供专业的 Python web应用发布和开发.它更符合python web的标准协议,速度要比Fastcgi要快.性能更加稳定. 一.安装平台 1.安装pcre 复制代码 代码如下: cd /home mkdir -p /home/install/nginx && cd /home/inst

  • Centos8搭建本地Web服务器的实现步骤

    1 概述 系统centos8,利用httpd搭建本地web服务器. 2 安装httpd sudo yum install -y httpd 3 启动服务 service httpd start 4 设置开机启动 先查看有没有设置开机启动: systemctl list-unit-files | grep httpd 没有的话设置: chkconfig httpd on 再确认一下: systemctl list-unit-files | grep httpd 5 访问 再浏览器输入内网ip地址(

  • 利用node.js搭建简单web服务器的方法教程

    前言 使用Nodejs搭建Web服务器是学习Node.js比较全面的入门教程,因为要完成一个简单的Web服务器,你需要学习Nodejs中几个比较重要的模块,比如:http协议模块.文件系统.url解析模块.路径解析模块.以及301重定向问题,下面我们就简单讲一下如何来搭建一个简单的Web服务器. 早先不使用web服务器的情况下想要在浏览器端访问本地资源,可以利用firefox浏览器,其可以自己启动一个小型web服务器. 为了让刚接触node的人也能大体看懂,本文的代码我将尽量简化. 准备 首先,

  • nodejs搭建本地http服务器教程

    由于不做php相关的东西,懒得装apache,干脆利用nodejs搭建一个本地的服务器用于测试. nodejs这玩意儿吧,对做前端的介入后端简直就是一把利器.而且目前,nodejs也越来越有商用价值. nodejs其实是非常底层的,从功能上说,它既是apache也是php.像搭建http服务器这种功能,本来是apache已经封装好的,但nodejs需要我们手动来搭建.其实在实际应用中,我们可以使用现成的框架.但这里,我想手动搭建,也加深一下对http服务器的理解. 我们node执行下面这个文件,

  • Nginx + php 搭建 超性能 WEB 服务器

    Nginx 是由 Igor Sysoev 为俄罗斯访问量第二的 Rambler.ru 站点开发的,它已经在该站点运行超过两年半了.Igor 将源代码以类BSD许可证的形式发布. 在高并发连接的情况下,Nginx是Apache服务器不错的替代品.Nginx同时也可以作为7层负载均衡服务器来使用.根据我的测试结果,Nginx 0.6.31 + PHP 5.2.6 (FastCGI) 可以承受3万以上的并发连接数,相当于同等环境下Apache的10倍. 根据我的经验,4GB内存的服务器+Apache(

  • win2008下搭建属于自己的web服务器(wamp)

    这次需要记录一下我搭建web服务器的过程. 我们小编注:因为图片格式问题,如果图片不能显示请使用chrome浏览器浏览. 第一步,确定自己要使用的平台:这次我用的是windows2008 server版本 第二步,计划是想要纯手工的安装apache.php等.但是我们可以下载一个wamp集成版(即windows系统下apache.mysql .php). 安装wamp过程如下:(类似于默认安装,但是要注意自己的安装) 等待安装完成之后,分别在桌面和状态栏生成快捷方式. 注意:我第一次运行的时候,

随机推荐