Advanced Node.js: углубленный анализ модуля кластера

Node.js
Advanced Node.js: углубленный анализ модуля кластера

Обзор модуля кластера

Экземпляры узлов — это однопоточные задания. В программировании на стороне сервера обычно создается несколько экземпляров узла для обработки клиентских запросов с целью повышения пропускной способности системы. Для таких экземпляров с несколькими узлами мы называем это кластером.

С помощью модуля кластера узла разработчики могут получить преимущества кластерных служб без изменения исходного кода проекта.

Кластер имеет следующие две распространенные схемы реализации, и модуль кластера, поставляемый с узлом, использует вторую схему.

Вариант 1: несколько экземпляров узла + несколько портов

Экземпляры узлов в кластере прослушивают разные порты, а затем обратный прокси-сервер реализует распределение запросов на несколько портов.

  • Преимущества: Простота реализации, каждый экземпляр относительно независим, что хорошо для стабильности службы.
  • Недостатки: увеличивается занятость портов, а связь между процессами становится более проблематичной.

Вариант 2. Основной процесс перенаправляет запрос дочернему процессу.

В кластере создайте основной процесс (мастер) и несколько подпроцессов (воркер). Мастер прослушивает запросы клиентов на подключение и перенаправляет их рабочим в соответствии с определенными политиками.

  • Преимущества: обычно занят только один порт, связь относительно проста, а стратегия пересылки более гибкая.
  • Недостатки: Реализация относительно сложная, требуется высокая стабильность основного процесса.

Пример начала работы

В модуле кластера главный процесс называется мастером, а дочерний процесс — рабочим.

Ниже приведен пример создания экземпляра сервера с таким же количеством ЦП для обработки клиентских запросов. Обратите внимание, что все они прослушивают один и тот же порт.

// server.js
var cluster = require('cluster');
var cpuNums = require('os').cpus().length;
var http = require('http');

if(cluster.isMaster){
  for(var i = 0; i < cpuNums; i++){
    cluster.fork();
  }
}else{
  http.createServer(function(req, res){
    res.end(`response from worker ${process.pid}`);
  }).listen(3000);

  console.log(`Worker ${process.pid} started`);
}

Создайте пакетный скрипт: ./req.sh.

#!/bin/bash

# req.sh
for((i=1;i<=4;i++)); do   
  curl http://127.0.0.1:3000
  echo ""
done 

Вывод следующий. Как видите, ответы приходят из разных процессов.

response from worker 23735
response from worker 23731
response from worker 23729
response from worker 23730

Принцип реализации кластерного модуля

Чтобы понять модуль кластера, мы в основном понимаем 2 вопроса:

  1. Как общаются мастера и рабочие?
  2. Как реализовать совместное использование портов для нескольких экземпляров сервера?
  3. Как при наличии нескольких экземпляров сервера запросы от клиентов распределяются между несколькими рабочими процессами?

Следующее будет представлено вместе со схематической диаграммой.Для ознакомления на уровне исходного кода вы можете обратиться к github автора.

Вопрос 1: Как общаются мастера и рабочие

Этот вопрос относительно прост. Главный процесс использует cluster.fork() для создания рабочих процессов. Cluster.fork() внутренне использует child_process.fork() для создания дочерних процессов.

То есть:

  1. Главный процесс и рабочий процесс являются отношениями между родительским и дочерним процессами.
  2. Главный процесс и рабочий процесс могут взаимодействовать через канал IPC. (важный)

Вопрос 2: Как реализовать совместное использование портов

В предыдущем примере серверы, созданные в нескольких рабочих процессах, прослушивают один и тот же порт 3000. Вообще говоря, если несколько процессов прослушивают один и тот же порт, система сообщит об ошибке.

Почему наш пример не проблема?

Секрет в том, что в модуле net метод listen() обрабатывается особым образом. В зависимости от того, является ли текущий процесс мастер-процессом или рабочим процессом:

  1. основной процесс: обычно прослушивайте запросы на этом порту. (без специальной обработки)
  2. Рабочий процесс: Создайте экземпляр сервера. Затем отправьте сообщение главному процессу через канал IPC, пусть главный процесс также создаст экземпляр сервера и прослушивает запросы на этом порту. Когда приходит запрос, главный процесс перенаправляет запрос серверному экземпляру рабочего процесса.

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

Как показано ниже:

Вопрос 3: Как распределить запросы по нескольким воркерам

Всякий раз, когда рабочий процесс создает экземпляр сервера для прослушивания запросов, он регистрируется на главном сервере через канал IPC. Когда поступает запрос клиента, мастер отвечает за пересылку запроса соответствующему работнику.

Какому работнику его следует направить? Это определяется политикой переадресации. Его можно установить через переменную среды NODE_CLUSTER_SCHED_POLICY или передать при вызове cluster.setupMaster(options).

Стратегия переадресации по умолчанию — циклический алгоритм (SCHED_RR).

Когда приходит запрос клиента, мастер опрашивает список рабочих процессов, находит первого свободного рабочего процесса и перенаправляет запрос рабочему процессу.

Советы по внутреннему общению между мастером и работником

Во время разработки мы будем использовать process.on('message', fn) для реализации межпроцессного взаимодействия.

Как упоминалось ранее, главный процесс и рабочий процесс также взаимодействуют через канал IPC во время создания экземпляра сервера. Не помешает ли это нашему развитию? Например, получить кучу сообщений, о которых вам на самом деле не нужно заботиться?

Ответ однозначно нет? Итак, как это делается?

Когда отправленное сообщение содержитcmdполе и измените поле наNODE_как префикс, сообщение обрабатывается как внутренне зарезервированное сообщение и не будет проходить черезmessageСобытие выдается, но его можно перехватить, прослушав 'internalMessage'.

В качестве примера возьмем рабочий процесс, уведомляющий главный процесс о создании экземпляра сервера. Рабочий псевдокод выглядит следующим образом:

// woker进程
const message = {
  cmd: 'NODE_CLUSTER',
  act: 'queryServer'
};
process.send(message);

Главный псевдокод выглядит следующим образом:

worker.process.on('internalMessage', fn);

Ссылки по теме

Официальная документация:узел будет .org/API/cluster…

Примечания к изучению узла:GitHub.com/извлечение жесткого диска/nod…

Об авторе: Сяока, бывший старший инженер Tencent, в настоящее время возглавляет отдел интерфейсных технологий в Qianhai Yunhan Financial Technology. Сосредоточьтесь на технической архитектуре, обмене технологиями, управлении проектами.