Архитектура кластера Node.js: как масштабировать мастер-воркер

Я создал встроенную архитектуру Node.js кластера с конфигурацией master/worker. Приложение использует express для обслуживания API и статических файлов и развертывается с помощью Docker:

[D O C K E R: 8080] --- N ---> [W O R K E R: 3001 ]  --- 1 ---> [M A S T E R: 3000]

У меня есть N рабочих в Worker.js и 1 мастер в master.js. Мастер и рабочий используют общие модули, в то время как у мастера есть основной модуль, который загружает основные службы и предоставляет API на PORT=3001, рабочий загружает другой API на PORT=3000, где контейнер Docker был привязан. В то время как прокси-сервер маршрутизации на рабочем сервере будет перенаправлять запросы на мастер для обслуживания запросов к основным модулям, другие запросы обрабатываются сервером на 3000 напрямую.

Стартовый скрипт выглядит так

'use strict';
(function() {

/// node clustering
const cluster = require('cluster');
const numCPUs = require('os').cpus().length;

if (cluster.isMaster) { // master node
    var masterConfig=require('./config/masterconfig.json');

    // Fork workers.
    var maxCPUs = process.env.WORKER_NUM || masterConfig.cluster.worker.num;
    maxCPUs=(maxCPUs>numCPUs)?numCPUs:maxCPUs;

    for (let i = 0; i < maxCPUs; i++) {
        const worker=cluster.fork();
    }

    var MasterNode=require('./lib/master');
    var master= new MasterNode(masterConfig);
    master.start()
    .then(done=> {
        console.log(`Master ${process.pid} running on ${masterConfig.pubsub.node}`);
    })
    .catch(error=> { // cannot recover from master error
        console.error(`Master ${process.pid} error`,error.stack);
        process.exit(1);
    });
}
else if (cluster.isWorker) { // worker node
    var workerConfig=require('./config/workerconfig.json');
    var WorkerNode=require('./lib/worker');
    var worker= new WorkerNode(workerConfig);
    worker.start()
    .then(done=> {
        console.log(`Worker ${process.pid} running on ${workerConfig.pubsub.node}`);
    })
    .catch(error=> { // worker error is recoverable
        console.error(`Worker ${process.pid} error`,error.stack);
    });
}

}).call(this);

У меня следующий вопрос.

1) По умолчанию модуль cluster совместно использует подчеркивающее HTTP-соединение, используя подход циклический. для обслуживания запросов — см. здесь, где рабочие процессы создаются с помощью child_process.fork(). Я не знаю, смогу ли я настроить этот метод для распределения входящих подключений.

2) До сих пор я обслуживаю статические файлы, шаблоны (например, pig/swig) в экспресс-веб-приложении на каждом рабочем сервере на PORT=3000, что означает, что я запускаю статические маршруты для веб-приложения на каждом созданном рабочем экземпляре. Я не уверен, что это лучший подход с точки зрения занятия памяти.

3) Другой подход к кластеризации. Я спросил о переносе этой архитектуры на PM2, несмотря на то, что это кажется многообещающим, я не уверен, что это лучший вариант — см. здесь для более подробной информации.


person loretoparisi    schedule 19.10.2018    source источник


Ответы (1)


Мастер должен заботиться только о том, чтобы запускать рабочих и правильно их останавливать/следить за сигналами от хоста и реагировать соответствующим образом. По моему опыту, у меня были хитрые ошибки, потому что я открывал API на мастере, который должен был быть на рабочем.

Если вы планируете перейти на PM2, PM2 будет обрабатывать вашего мастера, и вам все равно нужно будет переместить этот код на рабочий (по крайней мере, так было раньше)

Что касается ваших вопросов;

  1. Если вам нужно переопределить циклический перебор или настроить его, я думаю, у вас есть цель направить один и тот же клиентский трафик на одного и того же работника, известного как Sticky Sessions. Есть способы сделать это, но есть ограничения; если вы используете обратный прокси-сервер, такой как nginx или haproxy, перед узлом (что вам следует), а также хотите, чтобы сокеты работали должным образом (и имели Docker в игре), вы не можете действительно разветвляться на рабочих, потому что IP вы видите (на котором вы будете вычислять липкий идентификатор сеанса) всегда будет одним из вашего прокси или вашего хоста докера (даже с заголовком x-forwarded-for), что в первую очередь противоречит цели кластеризации. -> Мое решение состояло в том, чтобы запустить каждого рабочего на новом порту (например, 3001, 3002... 300N) и позволить nginx обрабатывать липкие сеансы.
  2. Это не проблема, но не идеально — и да, память немного увеличится, потому что каждый рабочий процесс загружает маршруты и модули. Но nginx намного быстрее обрабатывает статические файлы (и обрабатывает кеш для них с множеством http-заголовков), чем node. Таким образом, вы должны полагаться на nginx, обслуживающий статику, и оставить узел для динамических запросов (например, /api /login и т. д.).
  3. PM2 — это хорошее решение, которое имеет множество расширенных функций, таких как отчетность по статистике и управление развертыванием с нулевым временем простоя, но также стоит денег в зависимости от того, какие функции вы хотите использовать.
person japrescott    schedule 19.10.2018
comment
благодарю вас. Что касается пункта 2, я забыл упомянуть, что на самом деле я работаю с веб-приложением (шаблоны, логин и т. д.), так что это не только статические вещи, поэтому я использую экспресс как для API, так и для WebApp. - person loretoparisi; 19.10.2018
comment
@loretoparisi это не проблема! вы можете использовать nginx для своих статических файлов (все в /img/css/js/fonts и т. д.) и передавать остальной трафик на узел для динамической обработки. Если вы генерируете html из шаблонов, подумайте о том, чтобы сделать шаг сборки с помощью gruntjs/gulpjs/webpack, чтобы вы могли обслуживать эти файлы также с помощью nginx. - person japrescott; 20.10.2018