惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - Franky
N
Netflix TechBlog - Medium
宝玉的分享
宝玉的分享
Google DeepMind News
Google DeepMind News
腾讯CDC
G
Google Developers Blog
Martin Fowler
Martin Fowler
Microsoft Security Blog
Microsoft Security Blog
Recent Announcements
Recent Announcements
爱范儿
爱范儿
Engineering at Meta
Engineering at Meta
Microsoft Azure Blog
Microsoft Azure Blog
A
About on SuperTechFans
aimingoo的专栏
aimingoo的专栏
有赞技术团队
有赞技术团队
Jina AI
Jina AI
人人都是产品经理
人人都是产品经理
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
M
MIT News - Artificial intelligence
罗磊的独立博客
博客园 - 三生石上(FineUI控件)
美团技术团队
WordPress大学
WordPress大学
阮一峰的网络日志
阮一峰的网络日志

博客园 - 莫耶

学习RxJS:Cycle.js 学习RxJS: 导入 [Node.js] Node.js项目的持续集成 [Node.js] 使用node-forge保障Javascript应用的传输安全 [Node.js] 对称加密、公钥加密和RSA [Node.js] DSL in action [Node.js] 使用TypeScript编写Node项目 [Node.js] Node.js中的流 [Node.js] 基于Socket.IO 的私聊 [Node.js] 闭包和高阶函数 [Node.js] Promise,Q及Async [Node.js] Express的测试覆盖率 [Node.js] BDD和Mocha框架 [Node.js] 也说this [Node.js] ECMAScript 6中的生成器及koa小析 [Node.js] 使用File API 异步上传文件 [Node.js] OAuth 2 和 passport框架 [Node.js] Node + Redis 实现分布式Session方案 Python开源框架Scrapy安装及使用
[Node.js] Cluster,把多核用起来
莫耶 · 2014-09-17 · via 博客园 - 莫耶

2014-09-17 22:09  莫耶  阅读(2904)  评论()    收藏  举报

原文地址: http://www.moye.me/?p=496

引子

众所周知,虽然Node的底层有一个IO线程池,但其应用层默认是单线程运行的,对于多核CPU环境来说,是一种资源的浪费。

所幸Node提供了child_process 模块,让开发者得以开多个进程,实现每个进程各自利用一个CPU,以实现多核的利用。

child_process 模块给予Node 可以随意创建子进程的能力。因为 child_process 类本身是一个 EventEmitter,所以进程间通信很容易;且父子进程间通信并不通过网络层,而是在内核中完成,高效。

但 child_process 对于开发者来说,编程模型还是过于复杂,需要操心的细节过多,比如:父进程崩溃了,子进程回收是需要开发者提供代码来处理的——如果开发者只是想单纯利用多核模型,对具体工作进程的控制粒度并没有太多设想,那这种开发模型无疑是令人头疼的。

针对这个问题,Node 提供了 Cluster 模块。Cluster 简化了父子模型编程模型,只区分:当前进程是不是 Master,是 Master 就可以fork子进程,不是那就请行使Worker 职责。至于什么资源的回收,负载的调配,uncaughtException的处理,它自有安排。

本质上, Cluster 是 child_process 和 net 模块的组合应用。它不仅简化了编程模型,还使得共享监听同一端口成为可能。

更多关于Cluster的原理这里不表,感兴趣可以移步 → 解读Nodejs多核处理模块cluster

场景

用Node快速搭站呢,当然就用 express 咯 :) 所以,需求就是用 Cluster 做个单机集群,多进程跑 express,提升站点的吞吐量。

实验

先用 express-generator 搭个框子,模板引擎还是使 ejs 吧:

express-generator 默认会把 app.js 生成好,一行代码不写,一个完备的 http server 是已经实现的了。So,我们可以实现一个 Cluster 单机集群,clusterMaster.js:

//CPU几核?
var cpus = require('os').cpus().length;
 
//子进程监听消息处理函数
var workerListener = function (msg) {
    if (msg.access)
        console.log('user access %s, worker [%d]', 
                           msg.access, msg.workerid);
};
//fork新的子进程函数
var forkWorker = function(listener){
    var worker = cluster.fork();
    console.log('worker [%d] has been created', 
                                 worker.process.pid);
    worker.on('message', listener);
    return worker;
};
 
//Cluster处理
var cluster = require('cluster');
if (cluster.isMaster) {
    for (var i = 0; i < cpus; i++) {
        forkWorker(workerListener);
    }
} else {
    var app = require('./app');
    return app.listen(3000);
}
 
//Cluster收到子进程退出消息
cluster.on('exit', function (worker, code, signal) {
    console.log('worker [%d] died %s, fork a new one.',
        worker.process.pid, code || signal);
    forkWorker(workerListener);
});
//Cluster收到子进程运行消息
cluster.on('online', function(worker){
    console.log('worker [%d] is running.', worker.process.pid);
});

可以看到:

  • Cluster 是通过 isMaster 来区分父子进程的。父进程中处理创建逻辑:根据 CPU的核数,创建相应数量的子进程;子进程中运行具体的 Server 创建代码。够简单的模型~
  • 在 Cluster 父进程端,是始终能获知 当前worker 的。而当前 worker 的 pid 呢,在有worker 句柄情况下,对应的是 woker.process.pid;在子进程运行的上下文中,就是 process.pid。这不难理解~
  • 我们为新fork出来的子进程添加了消息处理函数,使得任何子进程运行代码,都可以随时利用消息触发它:用途挺广泛的,稍后会演示一个例子
  • 在子进程退出时,Cluster 父进程会收到 exit 消息,这时会重fork一个新子进程来补缺

假设用户访问这个站点: localhost:3000/,我希望告诉他,是哪个子进程在为他渲染页面。那么,在 /routes/ 添加一个express 路由 index.js:

var express = require('express');
var router = express.Router();
 
/* GET home page. */
router.get('/', function (req, res) {
    res.render('index', { title: 'Express', 
                              workerid: process.pid });
    process.send({access: '/', workerid: process.pid});
});
 
module.exports = router;

可以看到,除了路由,还使用 process.send 发送了消息,它实际上会触发之前代码中子进程注册的 workerListener,向它汇报路径和进程号;由于使用了 render,那自然是少不了 ejs 模板, /views/index.js:

<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <h1><%= title %></h1>
    <p>Welcome to <%= title %></p>
    <p>[<%= workerid %>] worker is serving for you.</p>
  </body>
</html>

模板会将 render 携带的子进程 pid 渲染到页面上; 当然少不了在 app.js 中添加路由映射:

var routes = require('./routes/index');
 
app.use('/', routes);

运行程序,在 shell 下 看看进程生成情况:

processes of Cluster

一父四子,试着用 kill 64163杀死一个子进程。可以在console看到,主进程收到了 exit消息,并重新fork一个新的进程:

fork a new process

测试一下负载,用 Chrome 重复访问几次站点,似乎 worker 子进程 一直未变:

access via chrome

用 ab 来模拟一下并发,让它一个核忙不过来:

simulate concurrency via ab
换 Safari 浏览器访问一下站点,这次换了一个 worker 进程来服务,速度还不慢 :)

access site root via safari

更多文章请移步我的blog新地址: http://www.moye.me/ 

Creative Commons License