Вступ
Уявіть, що ви відкриваєте невеликий затишний ресторан. Спочатку одного офіціанта достатньо, щоб обслужити кількох клієнтів, які заходять. Але з часом ресторан стає популярнішим, і клієнтів стає більше, а один офіціант вже не встигає, що спричиняє затримки. Як вирішити проблему? Найняти ще кілька офіціантів, щоб забезпечити гладку роботу зростаючої кількості клієнтів.
Ця проста аналогія відображає, як сервери Node.js обробляють трафік. За замовчуванням сервер Node.js працює на одному потоці. Хоча це ефективно для обробки легкого трафіку, під великим навантаженням сервер може почати працювати повільно або навіть збоїти. Тут на допомогу приходить модуль кластеризації, який дозволяє серверу використовувати всі доступні ядра процесора, масштабуючи потужність для обробки кількох запитів одночасно.
Давайте розглянемо, як працює кластеризація, її переваги і як ефективно її реалізувати.
Проблема з однонитковими серверами
Щоб зрозуміти обмеження однониткових серверів Node.js, розглянемо простий приклад:
const http = require('http');
const server = http.createServer((req, res) => {
if (req.url === '/') {
// Головна сторінка
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Ласкаво просимо на головну сторінку!');
} else if (req.url === '/about') {
// Сторінка "Про нас"
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Це сторінка "Про нас"!');
} else if (req.url === '/slow') {
// Повільна сторінка
res.writeHead(200, { 'Content-Type': 'text/plain' });
let count = 0;
for (let i = 0; i < 1_000_000; i++) {
count++;
}
res.end(`Повільна сторінка: Порахував до ${count}`);
} else {
// Сторінка 404
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('404 Не знайдено');
}
});
server.listen(3000, () => {
console.log('Сервер працює на http://localhost:3000/');
});
Вищенаведений код демонструє простий HTTP сервер з кількома маршрутами, зокрема, ресурсом /slow, що вимагає значних обчислень. Така схема добре працює при легкому навантаженні, але оскільки Node.js працює на одному потоці, усі запити обробляються послідовно. Якщо кілька користувачів запитають /slow, це може заблокувати обробку інших запитів, що спричинить затримки або навіть аварії сервера.
Щоб вирішити цю проблему, можна використовувати модуль кластеризації. Створюючи кілька робочих процесів, кожен з яких працює на окремому ядрі процесора, навантаження розподіляється, і жоден маршрут не блокує інші, що підвищує продуктивність і надійність.
Аналогія з рестораном
Знову повернемося до нашої історії про ресторан.
Спочатку в ресторані було лише кілька клієнтів, і один офіціант міг усіх обслужити ефективно. Але з ростом бізнесу цей офіціант почав не встигати, що призводило до затримок і незадоволених клієнтів.
А тепер уявіть, що ви найняли ще кількох офіціантів. Кожен з них міг би обслуговувати свою частину клієнтів, забезпечуючи плавне і своєчасне обслуговування. Так само й Node.js може скористатися «найняттям» кількох робочих процесів для більш ефективної обробки вхідного трафіку.
Модуль кластеризації
Модуль кластеризації в Node.js вирішує цю проблему, дозволяючи створити кілька екземплярів вашого додатка, кожен з яких працює на окремому ядрі процесора. Ці екземпляри, або «робітники», ділять між собою навантаження, що робить додаток більш масштабованим і стійким.
Як працює кластеризація
- Майстер-процес: Відповідає за управління робочими процесами.
- Робочі процеси: Обробляють вхідні запити і ділять навантаження.
Реалізація кластеризації
Ось як ви можете використати модуль кластеризації для масштабування вашого сервера:
const cluster = require('cluster');
const http = require('http');
const os = require('os');
if (cluster.isMaster) {
// Майстер-процес
const numCPUs = os.cpus().length;
console.log(`Майстер-процес працює.`);
Forking ${numCPUs} workers...`);
// Форкуємо робочі процеси для кожного ядра процесора
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
// Слухаємо вихід робочих процесів
cluster.on('exit', (worker, code, signal) => {
console.log(`Робочий процес ${worker.process.pid} вийшов з кодом ${code} (${signal})`);
console.log('Запускаємо новий робочий процес...');
cluster.fork();
});
} else {
// Робочий процес
const server = http.createServer((req, res) => {
if (req.url === '/') {
// Головний маршрут
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Ласкаво просимо на головну сторінку!');
} else if (req.url === '/about') {
// Сторінка "Про нас"
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Це сторінка "Про нас"!');
} else if (req.url === '/slow') {
// Повільна сторінка
res.writeHead(200, { 'Content-Type': 'text/plain' });
let count = 0;
for (let i = 0; i < 1_000_000; i++) {
count++;
}
res.end(`Повільна сторінка: Порахував до ${count}`);
} else {
// 404 маршрут
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('404 Не знайдено');
}
});
server.listen(3000, () => {
console.log(`Робочий процес ${process.pid} запущено, сервер працює на http://localhost:3000/`);
});
}
Примітки щодо кластеризації
- Форкання робочих процесів: Метод cluster.fork() створює новий робочий процес.
- Автоматичний перезапуск: Якщо робочий процес зламається, подія exit забезпечує створення нового.
- Використання процесора: Кількість робочих процесів повинна відповідати кількості доступних ядер процесора.
Переваги кластеризації
- Покращена масштабованість: Обробляє більший обсяг запитів, розподіляючи навантаження між кількома робочими процесами.
- Збільшена надійність: Якщо один робочий процес зламається, інші продовжують обробляти запити.
- Краща ефективність використання ресурсів: Повністю використовує багатоядерні процесори.
Просунута оптимізація
Хоча кластеризація є ефективною, поєднання її з іншими стратегіями може ще більше покращити продуктивність:
- Балансування навантаження: Використовуйте проксі-сервер, наприклад Nginx, для рівномірного розподілу трафіку.
- Кешування: Зменшуйте навантаження на сервер, кешуючи часто запитувані дані.
- Оптимізація бази даних: Оптимізуйте запити для швидшого отримання даних.
Висновок
Модуль кластеризації Node.js пропонує простий та економічний спосіб масштабування вашого додатка. Використовуючи всі доступні ядра процесора, він дозволяє серверу безперебійно обробляти великий трафік. Як і наймання більшої кількості офіціантів покращує обслуговування вашого ресторану, так і кластеризація може значно підвищити продуктивність та надійність вашого сервера.
Проте я не кажу, що використання кластеризації покращить продуктивність вашого додатка загалом. Це, однак, один з найбільш доступних способів оптимізації. Як зазначено в статті, оптимізація вашого додатка в реальному світі вимагає поєднання кількох стратегій, включаючи кластеризацію, балансування навантаження, кешування та оптимізацію бази даних, для досягнення найкращих результатів.
З кластеризацією та додатковими оптимізаціями ваш сервер Node.js буде готовий до обробки навіть найбільшого трафіку!
Основна думка: Не дозволяйте однопоточній проблемі уповільнити ваш додаток. Використовуйте кластеризацію, щоб розкрити весь потенціал вашого сервера на Node.js!
Перекладено з: Boosting Node.js Performance with Clustering: A Story-Based Guide