Текст: Zhenglong (веб-интерфейс-инженер Shanghai Internet School)
Оригинальная статья, воспроизведенная, пожалуйста, укажите автор и источник
Предыдущая статья "Реализация анализа в HTTP Node.js
const cluster = require('cluster');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
} else {
require('http').createServer((req, res) => {
res.end('hello world');
}).listen(3333);
}
Обычно модель master-slave включает в себя главный процесс (master) и несколько подчиненных процессов (worker).Главный процесс отвечает за получение запросов на соединение и распределение отдельных задач запросов подчиненным процессам для обработки; ответственностью подчиненного процесса является постоянно отвечать на запросы клиентов, пока не перейдет в состояние ожидания. Как показано на рис. 3-1:
В этой статье мы надеемся прояснить несколько ключевых вопросов, связанных с этим кодом:
-
Из процесса создания;
В Node.jscluster.forkс POSIXforkНемного отличается: хотя подчиненный процесс все же создается форком, он не использует напрямую образ процесса главного процесса, а вызывает системные функцииexecvpПусть подчиненный процесс использует новый образ процесса. Кроме того, каждый подчиненный процесс соответствует объекту Worker, который имеет следующие состояния: нет, онлайн, прослушивание, мертвый и отключенный.
Объект ChildProcess в основном обеспечивает создание процесса (spawn), уничтожение (kill) и управление счетчиком ссылок дескриптора процесса (ref и unref). Помимо инкапсуляции объекта Process (process_wrap.cc), он также сам обрабатывает некоторые детали. Например, в методе порождения, если необходимо установить конвейер IPC между ведущим и подчиненным процессами, переменная среды NODE_CHANNEL_FD используется для информирования подчиненного процесса о связанном с IPC файловом дескрипторе (fd), который должен быть связан. позже снова будет использована специальная переменная окружения.
Три отношения ссылки на объект, упомянутые выше, следующие:
Основной поток выполнения cluster.fork:
-
Вызовите Child_Process.spawn;
-
Создание объекта ChildProcess и инициализация его свойств _handle для объекта Process; process_wrap.cc Процесс публикуется в объект JavaScript, который инкапсулирует рабочие функции libuv процесса. Прикрепите определения объекта Process C++:
interface Process {
construtor(const FunctionCallbackInfo<Value>& args);
void close(const FunctionCallbackInfo<Value>& args);
void spawn(const FunctionCallbackInfo<Value>& args);
void kill(const FunctionCallbackInfo<Value>& args);
void ref(const FunctionCallbackInfo<Value>& args);
void unref(const FunctionCallbackInfo<Value>& args);
void hasRef(const FunctionCallbackInfo<Value>& args);
}
- Вызвать порождение метода ChildProcess._handle и, в конечном итоге, вызвать библиотеку libuv.uv_spawn.
Когда главный процесс выполняет cluster.fork, он указывает две специальные переменные среды NODE_CHANNEL_FD и NODE_UNIQUE_ID, поэтому процесс инициализации подчиненного процесса немного отличается от общего процесса Node.js:
-
bootstrap_node.js — это входной файл JavaScript, включенный во время выполнения, который вызывает internal\process.setupChannel;
-
Если переменная окружения содержит NODE_CHANNEL_FD, вызовите child_process._forkChild, затем удалите это значение;
-
Вызовите internal\child_process.setupChannel, прослушайте сообщения internalMessage о глобальном процессе дочерних объектов и добавьте методы send и _send. При этом пакет для отправки только _send, как правило, только канал записи _send JSON-сериализации после сообщения и, наконец, доставляется получателю.
-
Если переменная среды содержит Node_unique_ID, текущий процесс представляет собой модельный работник, выполняет DECORYINIT при загрузке модуля кластера; Кроме того, он также повлияет на метод прослушивания Net.server, метод прослушивания вызовов Cluster._getserver под рабочим режимом, который по существу Инициированное сообщение к основным процессам {«ACT»: «QueryServer»}, а не настоящий порт прослушивания.
Детали реализации IPC
Как упоминалось выше, процесс Node.js master-slave поддерживает связь только через IPC, поэтому в этом разделе подробно анализируются детали реализации IPC. Во-первых, давайте посмотрим на пример кода:
1-master.js
const {spawn} = require('child_process');
let child = spawn(process.execPath, [`${__dirname}/1-slave.js`], {
stdio: [0, 1, 2, 'ipc']
});
child.on('message', function(data) {
console.log('received in master:');
console.log(data);
});
child.send({
msg: 'msg from master'
});
1-slave.js
process.on('message', function(data) {
console.log('received in slave:');
console.log(data);
});
process.send({
'msg': 'message from slave'
});
node 1-master.js
Результаты приведены ниже:
socketpair
На самом деле можно использоватьsocketpairСоздайте пару полнодуплексных анонимных розетков для отправки сообщений между процессами; подпись функции выглядит следующим образом:
int socketpair(int domain, int type, int protocol, int sv[2]);
При нормальных обстоятельствах мы не можем пропустить дескрипторы файлов через розетки; когда основной процесс устанавливает соединение с клиентом, необходимо проинформировать рабский процесс дескриптора соединения для обработки. Что мне делать? На самом деле, указав первый параметр Socketpair в качестве AF_UNIX, это означает создание анонимного сокета домена Unix (сокет домена Unix), чтобы можно было использовать системные функцииsendmsgа такжеrecvmsgдля передачи/получения файловых дескрипторов.
Когда основной процесс вызывает cluster.fork, соответствующий процесс выглядит следующим образом:
- Создайте объект Pipe (pipe_wrap.cc) и укажите для параметра ipc значение true;
- Вызов uv_spawn, параметры параметров структуры uv_process_options_s, объект хранится в структурированном теле атрибута Pipe stdio;
- Вызовите uv__process_init_stdio, чтобы создать полнодуплексный сокет через socketpair;
- Вызовите uv__process_open_stream и установите значение iowatcher.fd объекта Pipe на один из полнодуплексных сокетов.
В этот момент процесс ведущий-ведомый может обмениваться данными в обоих направлениях. Блок-схема выглядит следующим образом:
Взглянем на переменную окружения node_channel_fd, что сомнительно, ее значение всегда равно 3. В описании документа уровня процесса 0-2 — это стандартный ввод stdin, стандартный вывод stdout и стандартный вывод ошибок stderr, затем первый доступный файловый дескриптор 3, SocketPair, очевидно, занимает первый доступный файл из дескриптора процесса. Таким образом, при записи данных из процесса в fd = 3, основной процесс может получить сообщение, наоборот, тоже аналогично.
Чтение сообщений от IPC в основном является потоковой операцией, которая будет подробно объяснена позже.Основные процессы перечислены ниже:
-
Обратный вызов StreamBase::EditData при чтении;
-
Streamprap :: onreadimpl вызов Streamwrap :: editdata;
-
StreamWrap::ReadStart вызывает uv_read_start Pass StreamWrap::OnRead в качестве третьего параметра:
int uv_read_start(uv_stream_t* stream, uv_alloc_cb alloc_cb, uv_read_cb read_cb)
Используемые отношения диаграммы классов следующие:
Модель master-slave сервера
Выше приведен грубый анализ процесса создания и его особенностей подчиненного процесса; если мы хотим реализовать модель обслуживания ведущий-подчиненный, нам необходимо решить основную проблему: как подчиненный процесс получает дескриптор соединения с клиентом? Начнем с сигнатуры функции process.send (метод send доступен только для глобального объекта процесса подчиненного устройства, и мастер может получить к нему доступ через worker.process или worker):
void send(message, sendHandle, callback)
Параметры сообщения и его значение могут быть явными обратными вызовами, функции обратного вызова относятся соответственно после окончания сообщения объектов и операций, подлежащих передаче. Что его второй параметр sendHandle для каких целей?
Как было сказано выше, системная функция socketpair может создавать пару двусторонних сокетов, которые можно использовать для отправки сообщений JSON, в этом блоке в основном задействованы потоковые операции, кроме того, когда sendHandle имеет значение, они также могут использоваться для передачи файловые дескрипторы.Этот процесс относительно немного сложнее, но в конечном итоге он вызовет системные функции sendmsg и recvmsg.
Передать дескриптор соединения клиенту
В сервисной модели master-slave главный процесс отвечает за установление соединения с клиентом, а затем передачу дескриптора соединения черезsendmsgпередан подчиненному процессу. Давайте посмотрим на процесс:
подчиненный процесс
-
Вызов метода http.server.listen (унаследован до net.server);
-
Вызов cluster._getServer, инициируйте сообщение основному процессу:
{
"cmd": "NODE_HANDLE",
"msg": {
"act": "queryServer"
}
}
основной процесс
- При получении и обработке этого сообщения будет создан новый объект RoundRobinHandle, который является дескриптором переменной. Каждый дескриптор соответствует конечной точке соединения и соответствует нескольким экземплярам подчиненного процесса; в то же время он открывает сервисный сокет TCP, соответствующий конечной точке соединения.
class RoundRobinHandle {
construtor(key, address, port, addressType, fd) {
// 监听同一端点的从进程集合
this.all = [];
// 可用的从进程集合
this.free = [];
// 当前等待处理的客户端连接描述符集合
this.handles = [];
// 指定端点的TCP服务socket
this.server = null;
}
add(worker, send) {
// 把从进程实例加入this.all
}
remove(worker) {
// 移除指定从进程
}
distribute(err, handle) {
// 把连接描述符handle存入this.handles,并指派一个可用的从进程实例开始处理连接请求
}
handoff(worker) {
// 从this.handles中取出一个待处理的连接描述符,并向从进程发起消息
// {
// "type": "NODE_HANDLE",
// "msg": {
// "act": "newconn",
// }
// }
}
}
-
Вызовите метод handle.add, чтобы добавить рабочий объект в коллекцию handle.all;
-
Когда handle.server начнет прослушивать клиентские запросы, сбросьте его функцию обратного вызова onconnection на RoundRobinHandle.distribute, чтобы главному процессу не нужно было фактически обрабатывать клиентские соединения, а нужно было только распределять соединения подчиненным процессам для обработки. Он сохраняет дескриптор соединения в коллекции handle.handles и, когда рабочий процесс доступен, отправляет ему сообщение { "act": "newconn" }. Если назначенный рабочий процесс не отвечает подтверждающим сообщением { "ack": message.seq, accept: true }, он попытается назначить соединение другому рабочему процессу.
Блок-схема выглядит следующим образом:
Позвоните, чтобы послушать из процесса
Обработка клиентских соединений
Как ведомый процесс прослушивает тот же порт, что и главный процесс?
Есть две основные причины:
** I. Инициализация среды выполнения Node.js немного отличается от внутрипроцессной**
-
Поскольку имеется переменная среды Node_unique_ID из процесса, метод DETORYINIT выполняется, когда модуль кластера загружен в bootstrap_node.js. Метод Masterinit, реализованный этим местом, и основным процессом, является: 1. В процессе нельзя создать метод Cluster. Объект Отключение и уничтожение реализации также различны.: Мы используем вызывающую рабочую. Процесс из процесса завершающий процесс затем выходит из-за того, что его третий модуль кластера добавляется из процесса. Монитор указанного адреса порта; затем сам использует аналоговый дескриптор TCP для продолжения выполнения;
-
internalMessage
-
Поскольку в переменной окружения процесса есть NODE_CHANNEL_FD, при вызове internal\process.setupChannel он будет подключаться к двунаправленному сокету, созданному системной функцией socketpair, и отслеживатьinternalMessage, обрабатывать типы сообщений: NODE_HANDLE_ACK и NODE_HANDLE.
** II.Метод listen выполняет немного отличающийся код в главном и подчиненном процессах. **
В методе listen для net.Server (net.js), если это ведущий процесс, выполняется стандартный процесс привязки портов, если подчиненный процесс, то будет вызываться cluster._getServer, описание метода см. выше .
Наконец, прилагается сервисная модель Master-Slave версии C, основанная на libuv,Адрес GitHub.
После запуска сервера доступаhttp://localhost:3333Результаты приведены ниже:
Я считаю, что после знакомства с этой статьей у всех появилось полное представление о кластере Node.js. В следующий раз автор поделится с вами подробным анализом доступности управления процессами Node.js в производственной среде, так что следите за обновлениями.
Серия 1 | Node.js в начало процесса анализа
Рекомендация: Самоотчет мастера переводческого проекта:
1. Галантерея|Каждый является мастером переводческих проектов
2. Обучение апплету WeChat от iKcamp состоит из 5 глав и 16 подразделов (включая видео).
3. Начать сериализацию бесплатно ~ 11 уроков iKcamp обновляются два раза в неделю | Создание практического проекта Node.js на основе Koa2 (включая видео) | Введение в план курса
В 2019 году оригинальная новая книга iKcamp «Практика разработки Koa и Node.js» была продана на JD.com, Tmall, Amazon и Dangdang!