Node.js, хоча й відомий своєю архітектурою на основі подій, що не блокується (non-blocking, event-driven architecture), є inherently (по суті) одно-нитковим (single-threaded). Це означає, що він може обробляти лише одне завдання за раз в межах Event Loop (циклу подій). Хоча така архітектура є ідеальною для додатків, що працюють з великим обсягом I/O, вона може неефективно використовувати багатоядерні процесори для обчислювальних завдань. Щоб вирішити це обмеження, Node.js надає потужний модуль: cluster
. У цій статті ми глибше розглянемо кластери в Node.js, пояснивши, як вони працюють, коли їх використовувати, та як за допомогою них можна ефективно масштабувати додатки.
Що таке кластери в Node.js?
Кластери в Node.js дозволяють створювати кілька процесів, кожен з яких запускає інстанцію вашого додатку. Ці процеси можуть спільно використовувати той самий серверний порт, що робить їх ідеальними для масштабування додатків на кілька ядер процесора.
Модуль cluster
досягає цього шляхом створення майстер-процесу, який створює робочі процеси. Кожен робочий процес працює в своєму власному циклі подій (Event Loop), що фактично дозволяє Node.js одночасно обробляти кілька запитів. ️
Переваги використання кластерів
- Використання багатоядерних процесорів: Більшість сучасних серверів мають кілька ядер, і модуль
cluster
дозволяє ефективно їх використовувати. - Стійкість до помилок: Якщо один робочий процес зупиняється, це не призводить до падіння всього додатку. Майстер може виявити зупинку і замінити робочий процес.
- Покращена продуктивність: Розподіляючи вхідні запити між робочими процесами, кластери можуть значно покращити пропускну здатність вашого додатку.
Коли потрібні кластери?
Хоча Node.js чудово підходить для одно-ниткових додатків, які інтенсивно працюють з I/O, є певні сценарії, коли використання кластерів є необхідним.
1. Додатки з високим трафіком
Коли вашому додатку потрібно обробляти велику кількість одночасних запитів, один потік може стати вузьким місцем (bottleneck). Кластери можуть розподіляти вхідні запити між кількома робочими процесами, що забезпечує кращу ефективність використання системних ресурсів та покращує час відгуку.
2. Обчислювальні завдання
Node.js не є оптимізованим для ресурсномістких обчислювальних операцій, таких як обробка зображень, аналіз даних чи криптографічні обчислення. Кластери дозволяють вивантажити ці обчислювальні завдання на окремі робочі процеси, що запобігає блокуванню основного потоку.
3. Максимізація використання апаратних ресурсів
Сучасні сервери мають багатоядерні процесори, але одно-нитковий додаток Node.js може використовувати лише одне ядро. Використовуючи кластери, ви можете забезпечити ефективне використання всіх ядер процесора, максимізуючи потенціал сервера.
4. Покращена стійкість до помилок
У виробничих умовах стабільність додатку критична. Якщо одно-нитковий додаток Node.js зупиняється, весь додаток припиняє роботу. Кластери допомагають ізолювати збої до окремих робочих процесів, дозволяючи майстер-процесу перезапустити їх без впливу на весь додаток.
Проблеми, які вирішують кластери
- Вузькі місця (Bottlenecks) одно-ниткової архітектури: Одно-ниткова природа Node.js може обмежувати його здатність ефективно обробляти обчислювальні завдання або високий трафік. Кластери усувають це обмеження, дозволяючи здійснювати паралельну обробку.
- Не використані багатоядерні системи: Без кластерів додаток Node.js використовує лише одне ядро процесора, залишаючи інші без роботи. Кластери забезпечують використання всіх ядер, покращуючи продуктивність і пропускну здатність.
- Час простою додатку: Кластери покращують стійкість, ізолюючи збої робочих процесів.
Майстер-процес може виявити і замінити зупинені робочі процеси, що зменшує час простою.
Налаштування кластерів Node.js
Ось покрокова інструкція з імплементації кластеризації в додатку Node.js:
Крок 1: Імпорт необхідних модулів
const cluster = require('cluster');
const os = require('os');
Крок 2: Перевірка, чи є процес майстром
if (cluster.isMaster) {
const numCPUs = os.cpus().length;
console.log(`Майстер-процес працює з PID: ${process.pid}`);
// Створення робочих процесів
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
} // Прослуховування подій завершення робочих процесів
cluster.on('exit', (worker, code, signal) => {
console.log(`Робочий процес ${worker.process.pid} зупинився. Перезапускаємо...`);
cluster.fork();
});
} else {
// Робочі процеси виконують серверний код
const http = require('http');
const port = 3000; http.createServer((req, res) => {
res.writeHead(200);
res.end(`Відповідь від робочого процесу ${process.pid}`);
}).listen(port, () => {
console.log(`Робочий процес ${process.pid} запущений і слухає порт ${port}`);
});
}
Основні моменти в коді
- Майстер-процес: Відповідає за створення робочих процесів та їх моніторинг. Якщо один з робочих процесів зупиняється, майстер створює новий для його заміни.
- Робочі процеси: Виконують фактичну логіку додатку та використовують той самий порт для обробки вхідних запитів.
Комунікація між майстром і робочими процесами
Майстер та робочі процеси можуть обмінюватися повідомленнями за допомогою IPC каналів. Наприклад:
Надсилання повідомлень від майстра до робочих процесів
if (cluster.isMaster) {
for (const id in cluster.workers) {
cluster.workers[id].send('Привіт, Робочий процес!');
}
} else {
process.on('message', (msg) => {
console.log(`Робочий процес ${process.pid} отримав повідомлення: ${msg}`);
});
}
Надсилання повідомлень від робочих процесів до майстра
if (!cluster.isMaster) {
process.send(`Привіт, Майстре, від Робочого процесу ${process.pid}`);
}
Балансування навантаження за допомогою кластерів
За замовчуванням, модуль Node.js cluster
використовує алгоритм балансування навантаження за принципом round-robin (кругове розподілення). Майстер-процес слухає вхідні з'єднання і розподіляє їх серед робочих процесів за круговим принципом. Це забезпечує рівномірний розподіл запитів серед усіх робочих процесів.
Для більш складного балансування навантаження ви можете розглянути використання інструментів, таких як Nginx або HAProxy, разом з кластерами Node.js.
Обробка збоїв робочих процесів
Хоча кластери покращують стійкість до збоїв, все ж потрібно реалізувати механізми для обробки збоїв:
- Моніторинг стану робочих процесів:
Використовуйте бібліотеки, такі якpm2
, або інтегруйте інструменти моніторингу для відстеження статусу та продуктивності робочих процесів. - Перезапуск зупинених робочих процесів:
Майстер-процес може слухати подіюexit
та перезапускати робочі процеси при їх зупинці. ️ - Гарне завершення роботи:
Переконайтеся, що робочі процеси виконують необхідні завдання з очищення перед завершенням роботи. Наприклад:
process.on('SIGTERM', () => {
console.log(`Робочий процес ${process.pid} завершує роботу...`);
process.exit();
});
Кращі практики
- Використовуйте менеджер процесів: Інструменти, такі як
pm2
, можуть спростити керування кластерами, забезпечити моніторинг та автоматичний перезапуск. - Утримуйте робочі процеси безстанними: Уникайте спільного використання стану між робочими процесами. Використовуйте зовнішні бази даних або рішення для кешування для спільних даних.
- Моніторте використання ресурсів: Слідкуйте за використанням CPU та пам'яті, щоб уникнути перевантаження робочих процесів.
- Використовуйте стратегії гарного перезапуску: Реалізуйте стратегії перезапуску робочих процесів без впливу на користувацький досвід.
Висновок
Кластери Node.js — це потужний інструмент для масштабування ваших додатків, щоб обробляти більші навантаження. Використовуючи багатоядерні можливості сучасних серверів, ви можете покращити продуктивність, підвищити стійкість до збоїв і забезпечити кращий користувацький досвід.
Незалежно від того, чи створюєте ви додаток з високим трафіком або сервіс з інтенсивними обчисленнями, розуміння та реалізація кластеризації може стати вирішальним фактором для ваших додатків на Node.js.
З прикладами та кращими практиками, поділеними в цьому посібнику, ви добре підготовлені, щоб повністю використати можливості кластерів Node.js.
Перекладено з: From Single Thread to Multi-Core: Node.js Clusters Explained