Перейти к содержанию

Node JS

6 Темы 9 Сообщения
  • Node.js и Балансировщик Нагрузки: Полное Руководство для Новичков

    2
    1 Голоса
    2 Сообщения
    13 Просмотры
    AladdinA
    Node.js Load Balancer: Быстрый Старт - Готовые Примеры Вариант 1: Nginx + 3 Node.js сервера (самый простой способ) Шаг 1. Создайте простой Node.js app Файл app.js: const http = require('http'); const PORT = process.env.PORT || 3000; const server = http.createServer((req, res) => { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ message: 'Hello from Node.js', port: PORT, timestamp: new Date().toISOString() })); }); server.listen(PORT, () => { console.log(`Server running on port ${PORT}`); }); Шаг 2. Запустите 3 копии # Терминал 1 PORT=3000 node app.js # Терминал 2 PORT=3001 node app.js # Терминал 3 PORT=3002 node app.js Шаг 3. Установите Nginx # macOS brew install nginx # Ubuntu sudo apt-get update && sudo apt-get install nginx Шаг 4. Настройте Nginx Отредактируйте /usr/local/etc/nginx/nginx.conf (macOS) или /etc/nginx/nginx.conf (Ubuntu): http { upstream backend { server localhost:3000; server localhost:3001; server localhost:3002; } server { listen 8080; location / { proxy_pass http://backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } } } Шаг 5. Запустите Nginx # macOS nginx # или перезагрузить nginx -s reload # Ubuntu sudo systemctl start nginx Шаг 6. Тестируйте # Откройте браузер и несколько раз перейдите на # http://localhost:8080 # Или используйте curl for i in {1..9}; do curl http://localhost:8080 echo "" done Видите меняющиеся порты (3000, 3001, 3002)? Балансировка работает! Вариант 2: Собственный балансировщик на Node.js Файл: load-balancer.js const express = require('express'); const axios = require('axios'); const app = express(); // Backend серверы const servers = [ 'http://localhost:3000', 'http://localhost:3001', 'http://localhost:3002' ]; let currentIndex = 0; // Middleware для балансировки app.use(async (req, res) => { try { // Round-robin: выбираем сервер по порядку const server = servers[currentIndex]; currentIndex = (currentIndex + 1) % servers.length; // Отправляем запрос на backend сервер const response = await axios.get(server); res.json(response.data); } catch (error) { console.error('Backend error:', error.message); res.status(503).json({ error: 'Service unavailable' }); } }); app.listen(8080, () => { console.log('Load balancer running on port 8080'); }); Установите зависимости: npm init -y npm install express axios Запустите: node load-balancer.js Тестируйте: curl http://localhost:8080 Вариант 3: PM2 Cluster Mode (для одной машины) Файл: server.js const express = require('express'); const app = express(); const PORT = process.env.PORT || 3000; app.get('/', (req, res) => { res.json({ message: 'Hello from Node.js', port: PORT, processId: process.pid }); }); app.listen(PORT, () => { console.log(`Server running on port ${PORT}, PID: ${process.pid}`); }); Файл: ecosystem.config.js module.exports = { apps: [{ name: 'myapp', script: './server.js', instances: 4, // 4 копии приложения exec_mode: 'cluster', env: { NODE_ENV: 'development' } }] }; Установите PM2: npm install -g pm2 npm install express Запустите: pm2 start ecosystem.config.js Полезные команды: pm2 status # Статус всех процессов pm2 logs # Логи в реальном времени pm2 stop all # Остановить всё pm2 delete all # Удалить из PM2 Тестируйте на порту 3000: for i in {1..8}; do curl http://localhost:3000 echo "" done Видите разные PID? PM2 распределяет между 4 воркерами! Вариант 4: Sticky Sessions (если нужны сессии) Backend сервер с Express-session: const express = require('express'); const session = require('express-session'); const app = express(); const PORT = process.env.PORT || 3000; // Сессии в памяти (для локальной разработки) app.use(session({ secret: 'keyboard cat', resave: false, saveUninitialized: true, cookie: { maxAge: 60000 } })); app.get('/login', (req, res) => { req.session.userId = 123; req.session.username = 'john_doe'; res.json({ message: 'Logged in', port: PORT }); }); app.get('/profile', (req, res) => { if (req.session.userId) { res.json({ username: req.session.username, port: PORT }); } else { res.status(401).json({ error: 'Not logged in' }); } }); app.listen(PORT); Nginx config с sticky sessions: upstream backend { ip_hash; # Один IP адрес — один сервер server localhost:3000; server localhost:3001; server localhost:3002; } server { listen 8080; location / { proxy_pass http://backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } } Тестируйте: # Логинитесь curl -c cookies.txt http://localhost:8080/login # Проверяете профиль (должен вернуть ваше имя) curl -b cookies.txt http://localhost:8080/profile # Повторяйте несколько раз - всегда один и тот же port! curl -b cookies.txt http://localhost:8080/profile Вариант 5: Least Connections для лучшей балансировки Node.js балансировщик с Least Connections: const express = require('express'); const axios = require('axios'); const app = express(); // Серверы с отслеживанием соединений const servers = [ { url: 'http://localhost:3000', connections: 0 }, { url: 'http://localhost:3001', connections: 0 }, { url: 'http://localhost:3002', connections: 0 } ]; function selectServer() { // Выбираем сервер с наименьшим количеством активных соединений return servers.reduce((prev, curr) => prev.connections < curr.connections ? prev : curr ); } app.use(async (req, res) => { const server = selectServer(); server.connections++; try { const response = await axios.get(server.url); res.json(response.data); } catch (error) { res.status(503).json({ error: 'Service unavailable' }); } finally { server.connections--; } }); app.listen(8080, () => { console.log('Load balancer (least connections) on port 8080'); }); Вариант 6: Health Checks (проверка живых серверов) Backend сервер с health endpoint: const express = require('express'); const app = express(); const PORT = process.env.PORT || 3000; // Health check endpoint app.get('/health', (req, res) => { res.json({ status: 'OK', uptime: process.uptime(), timestamp: new Date().toISOString() }); }); app.get('/', (req, res) => { // Иногда симулируем ошибку if (Math.random() > 0.8) { res.status(500).json({ error: 'Internal error' }); } else { res.json({ port: PORT }); } }); app.listen(PORT); Балансировщик с health checks: const express = require('express'); const axios = require('axios'); const app = express(); const servers = [ { url: 'http://localhost:3000', healthy: true }, { url: 'http://localhost:3001', healthy: true }, { url: 'http://localhost:3002', healthy: true } ]; // Периодически проверяем здоровье серверов setInterval(async () => { for (const server of servers) { try { await axios.get(server.url + '/health', { timeout: 2000 }); server.healthy = true; } catch (error) { server.healthy = false; } } }, 5000); let currentIndex = 0; app.use(async (req, res) => { // Находим живой сервер const healthyServers = servers.filter(s => s.healthy); if (healthyServers.length === 0) { return res.status(503).json({ error: 'All backends down' }); } // Round-robin только среди живых const server = healthyServers[currentIndex % healthyServers.length]; currentIndex++; try { const response = await axios.get(server.url); res.json(response.data); } catch (error) { res.status(503).json({ error: 'Service unavailable' }); } }); app.listen(8080); Вариант 7: Docker Compose (полная локальная setup) docker-compose.yml: version: '3.8' services: app1: image: node:18 working_dir: /app volumes: - ./app.js:/app/app.js environment: PORT: 3000 command: node app.js ports: - "3000:3000" app2: image: node:18 working_dir: /app volumes: - ./app.js:/app/app.js environment: PORT: 3000 command: node app.js ports: - "3001:3000" app3: image: node:18 working_dir: /app volumes: - ./app.js:/app/app.js environment: PORT: 3000 command: node app.js ports: - "3002:3000" nginx: image: nginx:latest volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro ports: - "8080:80" depends_on: - app1 - app2 - app3 nginx.conf: events { worker_connections 1024; } http { upstream backend { server app1:3000; server app2:3000; server app3:3000; } server { listen 80; location / { proxy_pass http://backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } } } Запустите: docker-compose up Тестируйте: curl http://localhost:8080 Сравнение вариантов Вариант Сложность Масштабируемость Когда использовать Nginx Низкая На 1 машину Для простых случаев, production Node.js балансировщик Средняя На 1 машину Когда нужна кастомная логика PM2 Низкая На 1 машину Быстрый старт, development Docker Средняя На несколько машин Микросервисы, облако Частые ошибки и как их избежать Ошибка 1: Забыли установить зависимости npm install express axios # Для Node.js балансировщика Ошибка 2: Порты уже заняты # Найти процесс на порту 8080 lsof -i :8080 # Убить процесс kill -9 <PID> Ошибка 3: Nginx ошибка при перезагрузке # Проверить конфиг nginx -t # Если ошибка - смотреть её и исправлять Ошибка 4: Соединение отказано (Connection refused) # Убедитесь что все backend серверы работают! curl http://localhost:3000 curl http://localhost:3001 curl http://localhost:3002 Команды для тестирования нагрузки Простый тест через curl: for i in {1..20}; do curl http://localhost:8080 echo "" done Apache Bench (если установлен): ab -n 1000 -c 10 http://localhost:8080/ Установка Apache Bench: # macOS brew install httpd # Ubuntu sudo apt-get install apache2-utils wrk (продвинутый инструмент): # Установка git clone https://github.com/wg/wrk.git cd wrk && make # Тест: 4 потока, 100 соединений, 30 секунд ./wrk -t4 -c100 -d30s http://localhost:8080/ Логирование и отладка PM2 логи: pm2 logs myapp --lines 100 pm2 logs myapp --err # Только ошибки Nginx логи: # Все запросы tail -f /var/log/nginx/access.log # Ошибки tail -f /var/log/nginx/error.log Node.js логирование: // Добавьте в app.js const fs = require('fs'); app.use((req, res, next) => { console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`); next(); }); Дополнительные ресурсы Документация Nginx: https://nginx.org/en/docs/ PM2 документация: https://pm2.io/docs/runtime/ Express.js: https://expressjs.com/ Axios: https://axios-http.com/ Готово! Выбирайте вариант, который вам нравится, и начинайте масштабировать!
  • Node.js v25: ключевые изменения и рекомендации для миграции

    node
    1
    1
    0 Голоса
    1 Сообщения
    88 Просмотры
    Нет ответов
  • Как удалять npm пакеты из package.json

    node
    2
    0 Голоса
    2 Сообщения
    96 Просмотры
    Gleb_OsinG
    А ещё может быть кому-то будет полезно удаление глобальных пакетов (не для конкретного проекта), можно к примеру воспользоваться командой: npm uninstall -g (название_пакета)
  • Сортировать двоичное дерево по уровням

    1
    0 Голоса
    1 Сообщения
    89 Просмотры
    Нет ответов
  • Гайд по работе с TensorFlow.js: от нуля до первого нейрона 🧠

    1
    0 Голоса
    1 Сообщения
    150 Просмотры
    Нет ответов
  • Релиз Node.js 24: Что нового в последней версии?

    2
    1 Голоса
    2 Сообщения
    384 Просмотры
    kirilljsxK
    @Jspi Ого, прям побыстрее стал на 20% ± Главное что бы работал