Как улучшить производительность Node.js-приложения?

Как улучшить производительность Node.js-приложения?

вторник, 26 ноября 2024 г.

Node.js предоставляет мощные инструменты для разработки серверных приложений. Однако высокая производительность требует правильной архитектуры и оптимизации кода. В этой статье разберем основные подходы с примерами на ES6.

1. Используйте асинхронный код
Асинхронность — ключевая особенность Node.js. Синхронные операции блокируют поток и замедляют выполнение. Используйте async/await для чистого и читаемого кода.

Пример:
Плохо:

import { readFileSync } from 'fs';

const readFile = () => {
  const data = readFileSync('./file.txt', 'utf8'); // Блокирует поток
  console.log(data);
};

readFile();

Хорошо:

import { promises as fs } from 'fs';

const readFile = async () => {
  const data = await fs.readFile('./file.txt', 'utf8'); // Асинхронный вызов
  console.log(data);
};

readFile();

2. Оптимизируйте запросы к базе данных
Правильная работа с базой данных включает использование индексов, кэширования и эффективных запросов.

Пример с Redis для кэширования:

import redis from 'redis';
import { promisify } from 'util';

const client = redis.createClient();
const getAsync = promisify(client.get).bind(client);
const setAsync = promisify(client.setex).bind(client);

const getUserData = async (userId) => {
  const cachedData = await getAsync(`user:${userId}`);
  if (cachedData) return JSON.parse(cachedData); // Возвращаем из кэша

  const dbData = await getUserFromDatabase(userId); // Эмуляция обращения к БД
  await setAsync(`user:${userId}`, 3600, JSON.stringify(dbData)); // Кэшируем
  return dbData;
};

3. Сжимайте ответы сервера
Уменьшение размера передаваемых данных ускоряет загрузку. Используйте compression для автоматического сжатия ответов.

Пример:

import express from 'express';
import compression from 'compression';

const app = express();

app.use(compression()); // Включаем компрессию

app.get('/', (req, res) => {
  res.send('This response is compressed!');
});

app.listen(3000, () => console.log('Server running on port 3000'));

4. Перенесите тяжелые вычисления в воркеры
Для выполнения ресурсоемких задач используйте worker_threads, чтобы не блокировать основной поток.

Пример:
Главный файл:

import { Worker } from 'worker_threads';

const runWorkerTask = (workerData) => {
  return new Promise((resolve, reject) => {
    const worker = new Worker('./worker.js', { workerData });
    worker.on('message', resolve);
    worker.on('error', reject);
    worker.on('exit', (code) => {
      if (code !== 0) reject(new Error(`Worker stopped with exit code ${code}`));
    });
  });
};

runWorkerTask({ value: 10 })
  .then((result) => console.log(`Result: ${result}`))
  .catch((err) => console.error(err));

Файл worker.js:

import { parentPort, workerData } from 'worker_threads';

const calculateFactorial = (n) => (n === 1 ? 1 : n * calculateFactorial(n - 1));

parentPort.postMessage(calculateFactorial(workerData.value));

5. Используйте HTTP/2
HTTP/2 улучшает производительность за счет мультиплексирования — одновременной загрузки нескольких ресурсов через одно соединение.

Пример:

import http2 from 'http2';
import { readFileSync } from 'fs';

const server = http2.createSecureServer({
  key: readFileSync('server.key'),
  cert: readFileSync('server.cert'),
});

server.on('stream', (stream) => {
  stream.respond({ ':status': 200 });
  stream.end('Hello over HTTP/2!');
});

server.listen(8443, () => console.log('Server running on https://localhost:8443'));

6. Оптимизируйте использование памяти
Следите за утечками памяти и правильно освобождайте ресурсы.

Пример: Управление потоками данных
Плохо (загрузка больших файлов):

import fs from 'fs';

fs.readFile('./largeFile.txt', (err, data) => {
  if (err) throw err;
  console.log(data.toString());
});

Хорошо (используйте стримы):

import fs from 'fs';

const readStream = fs.createReadStream('./largeFile.txt');
readStream.on('data', (chunk) => {
  console.log(chunk.toString());
});

7. Мониторьте приложение
Используйте инструменты вроде PM2, чтобы отслеживать производительность и выявлять узкие места.

Пример с PM2:

pm2 start app.js --watch --name "my-app"
pm2 monit

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

npm outdated
npm update

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

Есть вопросы или советы? Обращайтесь 🚀

Fullstack-разработчик в Москве
Александр
Fullstack-разработчик в Москве

Профессиональная разработка веб-приложений на Node.js с использованием современных frontend и backend фреймворков. Создание, продвижение, поддержка и обслуживание сайтов. Эффективно, прибыльно.