Не блокуйте цикл подій (або пул робочих потоків)

Чи варто читати цей посібник?

Якщо ви пишете що-небудь складніше за короткий скрипт командного рядка, цей посібник допоможе вам написати більш продуктивні та безпечні додатки на Node.js. Хоча цей документ написаний з урахуванням серверів Node.js, концепції застосовуються широко до складних додатків Node.js. Він зосереджений на системах на базі Linux, де специфікації ОС можуть відрізнятися.

pic

Чому не варто блокувати цикл подій та пул робочих потоків?

Node.js використовує два типи потоків:

  1. Цикл подій (Event Loop): Обробляє ініціалізацію та оркестрацію.
  2. Пул робочих потоків (Worker Pool): Обробляє завдання, що вимагають великих обчислювальних та I/O ресурсів.

Ризики блокування потоків

  • Продуктивність: Якщо важкі завдання блокують потоки, пропускна здатність сервера (кількість запитів за секунду) страждає.
  • Безпека: Шкідливі клієнти можуть надсилати «злісні введення», які блокують потоки, що призводить до атаки відмови в обслуговуванні (DoS).

Швидкий огляд архітектури Node.js

Цикл подій (Event Loop)

Цикл подій:

  • Обробляє вхідні запити клієнтів.
  • Виконує зворотні виклики JavaScript.
  • Керує асинхронними запитами, що не блокують (наприклад, мережеві I/O).

Пул робочих потоків (Worker Pool)

Пул робочих потоків Node.js, реалізований у libuv, обробляє:

Завдання, що вимагають великих I/O ресурсів:

  • dns.lookup()
  • API файлової системи (за винятком синхронних)

Обчислювальні завдання:

  • crypto.pbkdf2()
  • API zlib

Як Node.js вирішує, який код виконувати

Цикл подій (Event Loop)

Цикл подій відстежує дескриптори файлів (наприклад, мережеві сокети), використовуючи механізми ОС, такі як epoll (Linux). Коли дескриптор файлу готовий, цикл подій викликає відповідний зворотний виклик.

Пул робочих потоків (Worker Pool)

Пул робочих потоків використовує чергу. Робочі потоки беруть завдання з черги, обробляють їх і повідомляють цикл подій про завершення.

Кращі практики для проєктування додатків

Node.js проти традиційних багатопоточних серверів

У традиційних серверах, таких як Apache, кожен клієнт отримує свій власний потік. Якщо один потік заблоковано, ОС призначає інший. В Node.js один заблокований потік впливає на всіх клієнтів. Ви повинні забезпечити справедливий планувальник у вашому додатку.

Не блокуйте цикл подій

Цикл подій обробляє всі вхідні та вихідні запити. Якщо він витрачає надто багато часу на будь-яке завдання, всі клієнти страждають.

Керівництво:

Тримайте зворотні виклики маленькими:

  • Уникайте довготривалих синхронних завдань.
  • Мінімізуйте складність зворотних викликів.

Розумійте складність:

  • Виклики з постійним часом (O(1)) є ідеальними.
  • Лінійні (O(n)) виклики прийнятні для малого n.
  • Квадратичні (O(n^2)) або більші виклики слід уникати.

Приклади:

Приклад 1: Виклик з постійним часом

app.get('/constant-time', (req, res) => {  
 res.sendStatus(200);  
});

Приклад 2: Лінійний виклик

app.get('/countToN', (req, res) => {  
 const n = req.query.n;  
 for (let i = 0; i < n; i++) {  
 console.log(`Iter ${i}`);  
 }  
 res.sendStatus(200);  
});

Приклад 3: Квадратичний виклик

app.get('/countToN2', (req, res) => {  
 const n = req.query.n;  
 for (let i = 0; i < n; i++) {  
 for (let j = 0; j < n; j++) {  
 console.log(`Iter ${i}.${j}`);  
 }  
 }  
 res.sendStatus(200);  
});

Уникайте блокування пулу робочих потоків

Керівництво:

  1. Переносьте важкі обчислення: Використовуйте бібліотеки, такі як worker_threads, або зовнішні сервіси для обчислювальних завдань.
  2. Оптимізуйте I/O завдання: Використовуйте стрімінгові API (fs.createReadStream), а не читайте файли повністю одразу.
    3.
    Моніторинг тривалості завдань: Обмежте розмір введення, щоб уникнути довготривалих завдань.

Покращення масштабованості з кращими практиками

Використовуйте робочі потоки (Worker Threads) для паралельної обробки: Робочі потоки можуть обробляти інтенсивні обчислення без блокування циклу подій (Event Loop).

const { Worker } = require('worker_threads');   
app.get('/compute', (req, res) => {   
 const worker = new Worker('./compute-task.js');   
 worker.on('message', result => res.json(result));   
 worker.on('error', err => res.status(500).send(err.message));   
 worker.postMessage({ task: 'heavyComputation' });   
})

Використовуйте кластеризацію для горизонтального масштабування: Використовуйте всі ядра CPU для максимізації продуктивності:

const cluster = require('cluster');   
const os = require('os');   

if (cluster.isMaster) {   
 os.cpus().forEach(() => cluster.fork());   
} else {   
// Логіка сервера тут   
}

Моніторинг продуктивності: Регулярно аналізуйте продуктивність додатка за допомогою таких інструментів, як clinic.js або node --inspect.

Підсумок

Node.js є високомасштабованим завдяки використанню невеликої кількості потоків для обробки багатьох клієнтів. Це досягається завдяки:

  1. Виконанню коду JavaScript у циклі подій (Event Loop) для ініціалізації та зворотних викликів.
  2. Делегуванню важких завдань, таких як файловий I/O, до пулу робочих потоків (Worker Pool).

Чим менше потоків використовує Node.js, тим більше системних ресурсів можна зосередити на обслуговуванні клієнтів, а не на управлінні потоками. Ця ефективність означає, що вам потрібно розробляти ваш додаток таким чином, щоб розумно використовувати ці обмежені потоки.

Основний принцип: Node.js працює швидко, коли робота, пов’язана з кожним клієнтом у будь-який момент часу, є «малою».

Це стосується як:

  • Зворотних викликів на циклі подій (Event Loop).
  • Завдань у пулі робочих потоків (Worker Pool).

Висновок

Масштабованість Node.js приходить з відповідальністю ефективно управляти його обмеженими потоками. Дотримуючись коротких зворотних викликів у циклі подій, використовуючи робочі потоки для обчислювальних завдань і застосовуючи кластеризацію, ви можете створювати високопродуктивні, безпечні додатки, які ефективно обробляють великий трафік.

Дотримуючись цих кроків, ваші додатки на Node.js будуть добре оснащені для обробки великої кількості клієнтів з мінімальними ресурсами.

Перекладено з: Don’t Block the Event Loop (or the Worker Pool)

Leave a Reply

Your email address will not be published. Required fields are marked *