• [Node.js] Cluster,把多核用起来


    原文地址: 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 -e myapp2

    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/ 

  • 相关阅读:
    lnmp分离及其迁移数之一---数据库迁移
    lnmp wordpress...
    LNMP安装
    rpm 强制卸载
    ss ifconfig工具
    nginx--日志
    nginx--模块2--基于用户
    python-网络编程
    基本数据之-字典
    Python【day 9】函数入门1
  • 原文地址:https://www.cnblogs.com/moye/p/nodejs_cluster.html
Copyright © 2020-2023  润新知