<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Node.js и Балансировщик Нагрузки: Полное Руководство для Новичков]]></title><description><![CDATA[<p dir="auto">Представьте ситуацию: вы запустили Node.js приложение на боевом сервере, оно работает хорошо при 100 пользователях в день. Но вот приложение набирает популярность, и уже 10,000 пользователей одновременно пытаются получить доступ. Единственный экземпляр вашего приложения начинает медленнее отвечать, потом вообще падает. Запросы теряются, пользователи уходят. Вот здесь и приходит на помощь <strong>балансировщик нагрузки</strong>.</p>
<p dir="auto">Балансировщик нагрузки — это промежуточный слой между вашими пользователями и приложением, который распределяет входящие запросы между несколькими копиями вашего приложения, работающими одновременно.</p>
<hr />
<h2>Часть 1: Основные Концепции</h2>
<h3>Что такое горизонтальное масштабирование?</h3>
<p dir="auto">Есть два способа справиться с растущей нагрузкой:</p>
<p dir="auto"><strong>Вертикальное масштабирование</strong> (upgrade сервера)</p>
<ul>
<li>Вы просто покупаете более мощный сервер (больше RAM, быстрее CPU)</li>
<li>Простой способ, но дорогой и имеет физический предел</li>
<li>Если сервер падает — всё падает</li>
</ul>
<p dir="auto"><strong>Горизонтальное масштабирование</strong> (добавить серверы)</p>
<ul>
<li>Вы запускаете копии приложения на разных машинах или портах</li>
<li>Балансировщик распределяет запросы между ними</li>
<li>Дешевле в долгосроке, нет единой точки отказа, можно масштабировать бесконечно</li>
</ul>
<p dir="auto"><strong>Вывод</strong>: для highload приложений используйте <strong>горизонтальное масштабирование</strong> с балансировщиком нагрузки.</p>
<h3>Зачем нужен балансировщик нагрузки?</h3>
<ol>
<li><strong>Улучшение производительности</strong> — запросы распределяются, ни один сервер не перегружается</li>
<li><strong>Высокая доступность</strong> — если один сервер упадет, остальные продолжат работать</li>
<li><strong>Масштабируемость</strong> — просто добавляете новые серверы при растущей нагрузке</li>
<li><strong>Равномерная нагрузка</strong> — алгоритмы балансировки гарантируют справедливое распределение</li>
</ol>
<hr />
<h2>Часть 2: Алгоритмы Балансировки Нагрузки</h2>
<p dir="auto">Балансировщик должен знать, <strong>на какой сервер отправить запрос</strong>. Это делают алгоритмы:</p>
<h3>1. Round Robin (Круговой Метод)</h3>
<p dir="auto"><strong>Как работает</strong>: Балансировщик отправляет запросы по очереди: первый на сервер 1, второй на сервер 2, третий на сервер 3, потом снова на сервер 1.</p>
<pre><code>Запрос 1 → Сервер 1
Запрос 2 → Сервер 2
Запрос 3 → Сервер 3
Запрос 4 → Сервер 1 (цикл повторяется)
</code></pre>
<p dir="auto"><strong>Когда использовать</strong>: Когда все серверы одинаковой мощности и все запросы примерно одинакового времени обработки.</p>
<p dir="auto"><strong>Преимущества</strong>: Простой, быстрый, справедливый.<br />
<strong>Недостатки</strong>: Если один запрос длится 10 секунд, а другой 1 секунду, сервер с длинным запросом будет перегружен.</p>
<h3>2. Least Connections (Минимум Соединений)</h3>
<p dir="auto"><strong>Как работает</strong>: Балансировщик следит, сколько активных соединений на каждом сервере, и отправляет новый запрос на сервер с наименьшим количеством активных соединений.</p>
<pre><code>Сервер 1: 5 активных запросов
Сервер 2: 2 активных запроса  ← новый запрос пойдет сюда
Сервер 3: 8 активных запросов
</code></pre>
<p dir="auto"><strong>Когда использовать</strong>: Когда запросы имеют разное время обработки, или когда есть долгоживущие соединения (например, WebSocket).</p>
<p dir="auto"><strong>Преимущества</strong>: Более справедливое распределение при нестабильной нагрузке.<br />
<strong>Недостатки</strong>: Требует отслеживания состояния, немного медленнее.</p>
<h3>3. IP Hash (Хеширование IP)</h3>
<p dir="auto"><strong>Как работает</strong>: Балансировщик берет IP-адрес клиента, применяет хеш-функцию и на основе результата всегда отправляет этого клиента на одинаковый сервер.</p>
<pre><code>Клиент с IP 192.168.1.100 → всегда на Сервер 2
Клиент с IP 192.168.1.101 → всегда на Сервер 1
</code></pre>
<p dir="auto"><strong>Когда использовать</strong>: Когда нужны <strong>sticky sessions</strong> — когда данные сессии хранятся в памяти сервера, и клиент должен всегда идти на один и тот же сервер.</p>
<p dir="auto"><strong>Преимущества</strong>: Гарантирует, что один пользователь всегда на одном сервере.<br />
<strong>Недостатки</strong>: Может привести к неравномерной нагрузке, если одни IP адреса требуют больше ресурсов.</p>
<hr />
<h2>Часть 3: Решение 1 — Балансировка через Nginx</h2>
<p dir="auto">Nginx — это надежный, быстрый web-сервер, часто используемый как балансировщик нагрузки.</p>
<h3>Как это работает</h3>
<pre><code>Интернет
  ↓
Nginx (порт 80)
  ↓ (распределяет)
┌─────────────────┬─────────────────┬─────────────────┐
│ Node App        │ Node App        │ Node App        │
│ (порт 3000)     │ (порт 3001)     │ (порт 3002)     │
└─────────────────┴─────────────────┴─────────────────┘
</code></pre>
<h3>Практический пример</h3>
<p dir="auto"><strong>Шаг 1</strong>: Запустите несколько копий вашего приложения</p>
<p dir="auto">Создайте простой Node.js app (<code>server.js</code><img src="https://forum.exlends.ru/assets/plugins/nodebb-plugin-emoji/emoji/android/1f61e.png?v=1bd9ff6b60a" class="not-responsive emoji emoji-android emoji--disappointed" style="height:23px;width:auto;vertical-align:middle" title="):" alt="😞" /></p>
<pre><code class="language-javascript">const express = require('express');
const app = express();

// Получаем порт из переменной окружения или используем 3000
const PORT = process.env.PORT || 3000;

app.get('/', (req, res) =&gt; {
  res.json({
    message: 'Hello from Node.js',
    port: PORT,
    timestamp: new Date().toISOString()
  });
});

app.listen(PORT, () =&gt; {
  console.log(`Server running on port ${PORT}`);
});
</code></pre>
<p dir="auto">Запустите три копии с разными портами:</p>
<pre><code class="language-bash"># Терминал 1
PORT=3000 node server.js

# Терминал 2
PORT=3001 node server.js

# Терминал 3
PORT=3002 node server.js
</code></pre>
<p dir="auto"><strong>Шаг 2</strong>: Настройте Nginx</p>
<p dir="auto">Отредактируйте <code>/etc/nginx/nginx.conf</code> или создайте файл в <code>/etc/nginx/sites-available/myapp</code>:</p>
<pre><code class="language-nginx"># Определяем группу upstream-серверов
upstream nodejs_servers {
  # Round Robin по умолчанию
  server localhost:3000;
  server localhost:3001;
  server localhost:3002;
}

# HTTP сервер (балансировщик)
server {
  listen 80;
  server_name localhost;

  # Все запросы проксируем на upstream группу
  location / {
    proxy_pass http://nodejs_servers;
    
    # Важные заголовки для проксирования
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
  }
}
</code></pre>
<p dir="auto"><strong>Шаг 3</strong>: Проверьте конфигурацию и перезагрузите Nginx</p>
<pre><code class="language-bash"># Проверка синтаксиса
sudo nginx -t

# Если всё OK:
sudo systemctl reload nginx
</code></pre>
<p dir="auto"><strong>Шаг 4</strong>: Тестируйте</p>
<p dir="auto">Откройте браузер и несколько раз зайдите на <code>http://localhost/</code>. Обратите внимание на поле <code>port</code> в ответе — оно меняется между 3000, 3001 и 3002, подтверждая работу балансировки.</p>
<h3>Продвинутые параметры Nginx</h3>
<p dir="auto"><strong>Least Connections вместо Round Robin:</strong></p>
<pre><code class="language-nginx">upstream nodejs_servers {
  least_conn;  # Используем алгоритм least connections
  server localhost:3000;
  server localhost:3001;
  server localhost:3002;
}
</code></pre>
<p dir="auto"><strong>IP Hash для sticky sessions:</strong></p>
<pre><code class="language-nginx">upstream nodejs_servers {
  ip_hash;  # Один IP — один сервер
  server localhost:3000;
  server localhost:3001;
  server localhost:3002;
}
</code></pre>
<p dir="auto"><strong>Взвешенное распределение (если серверы разной мощности):</strong></p>
<pre><code class="language-nginx">upstream nodejs_servers {
  server localhost:3000 weight=3;  # Получает в 3 раза больше запросов
  server localhost:3001 weight=1;
  server localhost:3002 weight=1;
}
</code></pre>
<p dir="auto"><strong>Health checks (только в Nginx Plus, но полезно знать):</strong></p>
<pre><code class="language-nginx">upstream nodejs_servers {
  server localhost:3000;
  server localhost:3001;
  server localhost:3002;
  
  # Проверка здоровья: если сервер не ответит за 3 сек — исключить
  check interval=3000 rise=2 fall=5 timeout=1000 type=http;
  check_http_send "GET / HTTP/1.0\r\n\r\n";
  check_http_expect_alive http_2xx;
}
</code></pre>
<hr />
<h2>Часть 4: Решение 2 — Собственный Балансировщик на Node.js</h2>
<p dir="auto">Если вам нужна большая гибкость, можно написать балансировщик на самом Node.js используя Express и <code>http-proxy-middleware</code>.</p>
<h3>Как это работает</h3>
<pre><code>Интернет
  ↓
Node.js Балансировщик (порт 8080)
  ↓ (проксирует запросы)
┌─────────────────┬─────────────────┬─────────────────┐
│ Node App        │ Node App        │ Node App        │
│ (порт 3000)     │ (порт 3001)     │ (порт 3002)     │
└─────────────────┴─────────────────┴─────────────────┘
</code></pre>
<h3>Пример 1: Простой балансировщик с Round Robin</h3>
<p dir="auto">Установите зависимости:</p>
<pre><code class="language-bash">npm install express axios
</code></pre>
<p dir="auto">Создайте файл <code>load-balancer.js</code>:</p>
<pre><code class="language-javascript">const express = require('express');
const axios = require('axios');

const app = express();

// Список backend серверов
const servers = [
  'http://localhost:3000',
  'http://localhost:3001',
  'http://localhost:3002'
];

// Счетчик для round-robin
let currentServer = 0;

// Обработчик всех запросов
app.use(async (req, res) =&gt; {
  try {
    // Выбираем сервер по очереди (round-robin)
    const server = servers[currentServer];
    currentServer = (currentServer + 1) % servers.length;

    // Формируем URL для backend сервера
    const backendUrl = `${server}${req.originalUrl}`;

    // Делаем запрос к backend серверу
    const response = await axios({
      method: req.method,
      url: backendUrl,
      data: req.body,
      headers: {
        ...req.headers,
        'X-Forwarded-For': req.ip,
        'X-Real-IP': req.ip,
      }
    });

    // Возвращаем ответ клиенту
    res.status(response.status).send(response.data);
  } catch (error) {
    console.error('Error proxying request:', error.message);
    res.status(503).json({ error: 'Service unavailable' });
  }
});

app.listen(8080, () =&gt; {
  console.log('Load balancer running on port 8080');
});
</code></pre>
<p dir="auto">Запустите:</p>
<pre><code class="language-bash">node load-balancer.js
</code></pre>
<p dir="auto">Теперь обращайтесь к <code>http://localhost:8080</code> вместо отдельных серверов.</p>
<h3>Пример 2: Балансировщик с Least Connections</h3>
<pre><code class="language-javascript">const express = require('express');
const axios = require('axios');

const app = express();

// Backend серверы с отслеживанием соединений
const servers = [
  { url: 'http://localhost:3000', activeConnections: 0 },
  { url: 'http://localhost:3001', activeConnections: 0 },
  { url: 'http://localhost:3002', activeConnections: 0 }
];

// Выбираем сервер с наименьшим количеством активных соединений
function selectServer() {
  return servers.reduce((prev, current) =&gt; 
    prev.activeConnections &lt; current.activeConnections ? prev : current
  );
}

app.use(async (req, res) =&gt; {
  const selectedServer = selectServer();
  selectedServer.activeConnections++;

  try {
    const backendUrl = `${selectedServer.url}${req.originalUrl}`;

    const response = await axios({
      method: req.method,
      url: backendUrl,
      data: req.body,
      headers: {
        ...req.headers,
        'X-Forwarded-For': req.ip,
      }
    });

    res.status(response.status).send(response.data);
  } catch (error) {
    console.error('Error proxying request:', error.message);
    res.status(503).json({ error: 'Service unavailable' });
  } finally {
    // Уменьшаем счетчик соединений
    selectedServer.activeConnections--;
  }
});

app.listen(8080, () =&gt; {
  console.log('Load balancer (least connections) running on port 8080');
});
</code></pre>
<h3>Пример 3: Балансировщик с Sticky Sessions</h3>
<p dir="auto">Когда нужно, чтобы один пользователь всегда ходил на один сервер:</p>
<pre><code class="language-javascript">const express = require('express');
const axios = require('axios');
const crypto = require('crypto');

const app = express();

const servers = [
  'http://localhost:3000',
  'http://localhost:3001',
  'http://localhost:3002'
];

// Хранилище сессий: IP адрес -&gt; индекс сервера
const sessions = new Map();

function selectServer(clientIP) {
  if (sessions.has(clientIP)) {
    // Клиент уже был ранее
    return sessions.get(clientIP);
  } else {
    // Новый клиент — распределяем по IP hash
    const hash = crypto
      .createHash('md5')
      .update(clientIP)
      .digest('hex');
    const serverIndex = parseInt(hash, 16) % servers.length;
    sessions.set(clientIP, serverIndex);
    return serverIndex;
  }
}

app.use(async (req, res) =&gt; {
  const clientIP = req.ip;
  const serverIndex = selectServer(clientIP);
  const selectedServer = servers[serverIndex];

  try {
    const backendUrl = `${selectedServer}${req.originalUrl}`;

    const response = await axios({
      method: req.method,
      url: backendUrl,
      data: req.body,
      headers: {
        ...req.headers,
        'X-Forwarded-For': clientIP,
      }
    });

    res.status(response.status).send(response.data);
  } catch (error) {
    console.error('Error proxying request:', error.message);
    res.status(503).json({ error: 'Service unavailable' });
  }
});

app.listen(8080, () =&gt; {
  console.log('Load balancer (sticky sessions) running on port 8080');
});
</code></pre>
<hr />
<h2>Часть 5: Решение 3 — PM2 для Управления Кластером</h2>
<p dir="auto">PM2 — это процесс-менеджер для Node.js, который встроил управление кластером и балансировкой нагрузки.</p>
<h3>Как это работает</h3>
<p dir="auto">PM2 автоматически создает несколько копий вашего приложения (воркеры) и распределяет между ними запросы используя встроенный балансировщик.</p>
<h3>Практический пример</h3>
<p dir="auto">Установите PM2:</p>
<pre><code class="language-bash">npm install -g pm2
</code></pre>
<p dir="auto">Создайте конфиг файл <code>ecosystem.config.js</code>:</p>
<pre><code class="language-javascript">module.exports = {
  apps: [
    {
      name: 'nodejs-app',           // Имя приложения
      script: './server.js',         // Файл приложения
      instances: 4,                   // Количество копий (или 'max' для всех CPU ядер)
      exec_mode: 'cluster',          // Режим кластера
      watch: false,                   // Перезагружать при изменении файлов (false для production)
      max_memory_restart: '500M',     // Перезагружать если память &gt; 500MB
      env: {
        NODE_ENV: 'development'
      },
      env_production: {
        NODE_ENV: 'production'
      },
      // Graceful shutdown timeout
      listen_timeout: 3000,
      // Автоматический перезапуск упавшего процесса
      autorestart: true,
      // Минимальное время жизни процесса перед перезапуском
      min_uptime: '10s',
      // Максимальное количество перезапусков за час
      max_restarts: 10
    }
  ]
};
</code></pre>
<p dir="auto">Запустите приложение:</p>
<pre><code class="language-bash">pm2 start ecosystem.config.js
</code></pre>
<p dir="auto">Полезные команды:</p>
<pre><code class="language-bash"># Просмотр статуса всех процессов
pm2 status

# Просмотр логов
pm2 logs nodejs-app

# Перезагрузка с нулевым downtime
pm2 reload ecosystem.config.js

# Остановка
pm2 stop ecosystem.config.js

# Удаление
pm2 delete ecosystem.config.js

# Автозагрузка при перезагрузке сервера
pm2 startup
pm2 save
</code></pre>
<h3>Как PM2 распределяет запросы?</h3>
<p dir="auto">PM2 использует алгоритм Round Robin по умолчанию. Главный процесс (master) принимает входящие соединения и передает их воркерам по очереди:</p>
<pre><code>Входящий запрос
  ↓
Master процесс PM2
  ↓
Воркер 1, затем Воркер 2, затем Воркер 3, затем Воркер 4, затем снова Воркер 1...
</code></pre>
<h3>Отличие PM2 от Nginx</h3>
<ul>
<li><strong>PM2</strong>: Управляет несколькими копиями одного приложения на одной машине</li>
<li><strong>Nginx</strong>: Распределяет нагрузку между разными машинами (или портами)</li>
</ul>
<p dir="auto">Часто используют вместе: Nginx распределяет между машинами, PM2 на каждой машине управляет кластером.</p>
<hr />
<h2>Часть 6: Обработка Сессий при Горизонтальном Масштабировании</h2>
<h3>Проблема</h3>
<p dir="auto">Когда у вас есть несколько копий приложения, встает вопрос: где хранить данные сессии пользователя?</p>
<pre><code>Пользователь логинится на Сервер 1 → сессия сохраняется на Сервер 1
Пользователь (через балансировщик) попадает на Сервер 2 → где найти сессию?
</code></pre>
<h3>Решение 1: Sticky Sessions (IP Hash)</h3>
<p dir="auto">Используем IP Hash балансировщика, чтобы один пользователь всегда был на одном сервере:</p>
<p dir="auto"><strong>Nginx:</strong></p>
<pre><code class="language-nginx">upstream nodejs_servers {
  ip_hash;
  server localhost:3000;
  server localhost:3001;
  server localhost:3002;
}
</code></pre>
<p dir="auto"><strong>Минусы</strong>: Неравномерная нагрузка, если один сервер упадет — пользователь потеряет сессию.</p>
<h3>Решение 2: Централизованное Хранилище (Рекомендуется)</h3>
<p dir="auto">Храните сессии в отдельной БД или кэше (Redis), доступной всем серверам.</p>
<p dir="auto"><strong>Пример с Redis:</strong></p>
<pre><code class="language-javascript">const express = require('express');
const session = require('express-session');
const RedisStore = require('connect-redis').default;
const redis = require('redis');

const app = express();

// Создаем Redis клиент
const redisClient = redis.createClient({
  host: 'localhost',
  port: 6379
});

redisClient.connect();

// Настраиваем сессии с хранилищем в Redis
app.use(session({
  store: new RedisStore({ client: redisClient }),
  secret: 'your-secret-key',
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: false,  // true для HTTPS
    httpOnly: true,
    maxAge: 24 * 60 * 60 * 1000  // 24 часа
  }
}));

app.get('/login', (req, res) =&gt; {
  req.session.userId = 123;
  req.session.username = 'john';
  res.json({ message: 'Logged in', session: req.session });
});

app.get('/profile', (req, res) =&gt; {
  if (req.session.userId) {
    res.json({ 
      message: 'Welcome back', 
      username: req.session.username 
    });
  } else {
    res.status(401).json({ error: 'Not logged in' });
  }
});

app.listen(3000, () =&gt; {
  console.log('Server with Redis sessions on port 3000');
});
</code></pre>
<p dir="auto">Установите зависимости:</p>
<pre><code class="language-bash">npm install express-session redis connect-redis
</code></pre>
<p dir="auto"><strong>Преимущества</strong>:</p>
<ul>
<li>Один пользователь может обращаться к разным серверам</li>
<li>Если сервер упадет, сессия не потеряется</li>
<li>Масштабируется на множество машин</li>
</ul>
<p dir="auto"><strong>Минусы</strong>:</p>
<ul>
<li>Нужно поддерживать отдельное хранилище (Redis, PostgreSQL)</li>
<li>Небольшое замедление из-за обращения к БД/кэшу</li>
</ul>
<h3>Решение 3: Stateless приложение (Токены)</h3>
<p dir="auto">Вообще не хранить сессии на сервере. Используйте JWT токены:</p>
<pre><code class="language-javascript">const express = require('express');
const jwt = require('jsonwebtoken');

const app = express();
const SECRET = 'your-secret-key';

app.get('/login', (req, res) =&gt; {
  const token = jwt.sign(
    { userId: 123, username: 'john' },
    SECRET,
    { expiresIn: '24h' }
  );
  res.json({ token });
});

app.get('/profile', (req, res) =&gt; {
  const token = req.headers.authorization?.replace('Bearer ', '');
  
  try {
    const decoded = jwt.verify(token, SECRET);
    res.json({ message: 'Welcome', user: decoded });
  } catch (error) {
    res.status(401).json({ error: 'Invalid token' });
  }
});

app.listen(3000);
</code></pre>
<p dir="auto"><strong>Преимущества</strong>:</p>
<ul>
<li>Совсем не нужно хранить состояние на сервере</li>
<li>Легко масштабируется на любое количество машин</li>
<li>Работает отлично для микросервисов</li>
</ul>
<p dir="auto"><strong>Минусы</strong>:</p>
<ul>
<li>Клиент должен отправлять токен в каждом запросе</li>
<li>Токен нельзя аннулировать до истечения (или нужен отдельный список отозванных)</li>
</ul>
<hr />
<h2>Часть 7: Health Checks (Проверки Здоровья Серверов)</h2>
<p dir="auto">Балансировщик должен знать, какие серверы работают, а какие упали.</p>
<h3>Health Check в Nginx</h3>
<pre><code class="language-nginx">upstream nodejs_servers {
  server localhost:3000;
  server localhost:3001;
  server localhost:3002;
  
  # Проверка: отправить GET запрос на /health
  # Если не ответит за 1 сек или вернет ошибку — сервер мертв
}

server {
  listen 80;

  location / {
    proxy_pass http://nodejs_servers;
    
    # Таймауты
    proxy_connect_timeout 3s;
    proxy_send_timeout 5s;
    proxy_read_timeout 5s;
  }
  
  # Endpint здоровья (для балансировщика проверить)
  location /health {
    proxy_pass http://nodejs_servers;
  }
}
</code></pre>
<h3>Endpoint здоровья в приложении</h3>
<pre><code class="language-javascript">const express = require('express');
const app = express();

// Endpoint для проверок здоровья
app.get('/health', (req, res) =&gt; {
  // Проверяем, что БД доступна, нет критических ошибок и т.д.
  const healthcheck = {
    uptime: process.uptime(),
    timestamp: Date.now(),
    status: 'OK'
  };
  
  res.status(200).json(healthcheck);
});

// Используйте этот endpoint в тестах
app.listen(3000);
</code></pre>
<h3>Health Check в PM2</h3>
<p dir="auto">PM2 встроенно мониторит состояние процессов и перезагружает упавшие.</p>
<p dir="auto">Используйте параметры в <code>ecosystem.config.js</code>:</p>
<pre><code class="language-javascript">module.exports = {
  apps: [{
    name: 'app',
    script: './server.js',
    instances: 4,
    exec_mode: 'cluster',
    watch: false,
    max_memory_restart: '500M',
    autorestart: true,
    min_uptime: '10s',
    max_restarts: 10,
    listen_timeout: 3000  // Даем 3 сек на инициализацию
  }]
};
</code></pre>
<hr />
<h2>Часть 8: Полный Пример — Реальная Setup</h2>
<p dir="auto">Объединим всё вместе: Nginx балансировщик + несколько Node.js серверов + PM2 кластер.</p>
<h3>Архитектура</h3>
<pre><code>Интернет (пользователи)
  ↓
Nginx (порт 80) на машине 1
  ↓
┌────────────────────────────────────────────────┐
│ Машина 2: PM2 кластер (4 воркера)             │
│ ├─ Воркер 1 (порт 3000)                       │
│ ├─ Воркер 2 (порт 3001)                       │
│ ├─ Воркер 3 (порт 3002)                       │
│ └─ Воркер 4 (порт 3003)                       │
└────────────────────────────────────────────────┘
  ↓
┌────────────────────────────────────────────────┐
│ Машина 3: PM2 кластер (4 воркера)             │
│ ├─ Воркер 1 (порт 3000)                       │
│ ├─ Воркер 2 (порт 3001)                       │
│ ├─ Воркер 3 (порт 3002)                       │
│ └─ Воркер 4 (порт 3003)                       │
└────────────────────────────────────────────────┘
  ↓
Централизованное хранилище сессий (Redis / PostgreSQL)
</code></pre>
<h3>Шаг 1: Приложение Node.js</h3>
<p dir="auto"><code>server.js</code>:</p>
<pre><code class="language-javascript">const express = require('express');
const session = require('express-session');
const redis = require('redis');
const RedisStore = require('connect-redis').default;

const app = express();
const PORT = process.env.PORT || 3000;

// Redis для сессий
const redisClient = redis.createClient({
  host: 'redis.example.com',  // Централизованный Redis
  port: 6379
});
redisClient.connect();

// Session middleware
app.use(session({
  store: new RedisStore({ client: redisClient }),
  secret: 'production-secret-key',
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: true,
    httpOnly: true,
    maxAge: 24 * 60 * 60 * 1000
  }
}));

// Health check
app.get('/health', (req, res) =&gt; {
  res.json({
    status: 'OK',
    uptime: process.uptime(),
    port: PORT,
    pid: process.pid
  });
});

// API endpoint
app.get('/api/data', (req, res) =&gt; {
  req.session.lastAccess = new Date();
  res.json({
    data: 'Hello from Node.js',
    port: PORT,
    sessionId: req.sessionID,
    sessionData: req.session
  });
});

// Graceful shutdown
process.on('SIGTERM', () =&gt; {
  console.log('SIGTERM received, shutting down gracefully...');
  // PM2 даст нам 3 сек (listen_timeout) на завершение
  redisClient.quit();
  process.exit(0);
});

app.listen(PORT, () =&gt; {
  console.log(`Server running on port ${PORT}, PID: ${process.pid}`);
});
</code></pre>
<h3>Шаг 2: PM2 конфиг на каждой машине</h3>
<p dir="auto"><code>ecosystem.config.js</code> (на машине 2 и машине 3):</p>
<pre><code class="language-javascript">module.exports = {
  apps: [
    {
      name: 'nodejs-app',
      script: './server.js',
      instances: 4,
      exec_mode: 'cluster',
      env: {
        NODE_ENV: 'production',
        PORT: 3000
      },
      watch: false,
      max_memory_restart: '500M',
      autorestart: true,
      min_uptime: '10s',
      max_restarts: 10,
      listen_timeout: 3000,
      error_file: './logs/err.log',
      out_file: './logs/out.log',
      log_date_format: 'YYYY-MM-DD HH:mm:ss Z'
    }
  ]
};
</code></pre>
<p dir="auto">Запустите:</p>
<pre><code class="language-bash">pm2 start ecosystem.config.js
pm2 startup
pm2 save
</code></pre>
<h3>Шаг 3: Nginx конфиг</h3>
<p dir="auto">На машине 1, файл <code>/etc/nginx/sites-available/nodeapp</code>:</p>
<pre><code class="language-nginx">upstream nodejs_servers {
  least_conn;  # Используем least connections для равномерной нагрузки
  
  # Серверы на машине 2
  server 192.168.1.10:3000;
  server 192.168.1.10:3001;
  server 192.168.1.10:3002;
  server 192.168.1.10:3003;
  
  # Серверы на машине 3
  server 192.168.1.11:3000;
  server 192.168.1.11:3001;
  server 192.168.1.11:3002;
  server 192.168.1.11:3003;
}

server {
  listen 80;
  server_name api.example.com;

  # Логи
  access_log /var/log/nginx/nodejs_access.log;
  error_log /var/log/nginx/nodejs_error.log;

  location / {
    proxy_pass http://nodejs_servers;
    
    # Заголовки
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    
    # Таймауты
    proxy_connect_timeout 3s;
    proxy_send_timeout 5s;
    proxy_read_timeout 5s;
    
    # Буферизация
    proxy_buffering on;
    proxy_buffer_size 4k;
    proxy_buffers 8 4k;
  }

  # Health check endpoint
  location /health {
    proxy_pass http://nodejs_servers;
    access_log off;
  }

  # SSL (если нужно)
  # listen 443 ssl http2;
  # ssl_certificate /etc/ssl/certs/cert.pem;
  # ssl_certificate_key /etc/ssl/private/key.pem;
}

# Перенаправление HTTP на HTTPS
server {
  listen 80;
  server_name api.example.com;
  return 301 https://$server_name$request_uri;
}
</code></pre>
<p dir="auto">Включите конфиг:</p>
<pre><code class="language-bash">sudo ln -s /etc/nginx/sites-available/nodeapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
</code></pre>
<h3>Шаг 4: Мониторинг</h3>
<p dir="auto">Проверяем статус:</p>
<pre><code class="language-bash"># На машинах 2 и 3
pm2 status
pm2 logs nodejs-app

# На машине 1 (Nginx)
sudo systemctl status nginx
sudo tail -f /var/log/nginx/nodejs_access.log
</code></pre>
<p dir="auto">Тестируем:</p>
<pre><code class="language-bash"># На локальной машине
for i in {1..10}; do curl http://api.example.com/api/data; echo ""; done
</code></pre>
<p dir="auto">Видите меняющиеся значения <code>port</code> и <code>pid</code> — балансировка работает!</p>
<hr />
<h2>Часть 9: Распространённые Проблемы и Решения</h2>
<h3>Проблема 1: Sticky Sessions не работают</h3>
<p dir="auto"><strong>Симптом</strong>: Пользователь теряет данные между запросами.</p>
<p dir="auto"><strong>Причина</strong>: Используются разные серверы для одного пользователя.</p>
<p dir="auto"><strong>Решение</strong>:</p>
<ul>
<li>Либо используйте IP Hash в балансировщике</li>
<li>Либо сохраняйте сессию в Redis/БД (рекомендуется)</li>
</ul>
<h3>Проблема 2: Высокая задержка при проксировании</h3>
<p dir="auto"><strong>Симптом</strong>: Приложение через балансировщик работает медленнее.</p>
<p dir="auto"><strong>Причина</strong>: Сетевые задержки, неправильная буферизация.</p>
<p dir="auto"><strong>Решение</strong>:</p>
<pre><code class="language-nginx"># Optimize buffering
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
proxy_busy_buffers_size 8k;

# TCP optimization
proxy_tcp_nodelay on;
proxy_tcp_nopush on;
</code></pre>
<h3>Проблема 3: Соединение разрывается при reload сервера</h3>
<p dir="auto"><strong>Симптом</strong>: Клиенты получают ошибку при перезагрузке.</p>
<p dir="auto"><strong>Причина</strong>: Сервер не обрабатывает graceful shutdown.</p>
<p dir="auto"><strong>Решение</strong>: Добавьте обработку SIGTERM:</p>
<pre><code class="language-javascript">process.on('SIGTERM', () =&gt; {
  server.close(() =&gt; {
    console.log('Server shut down gracefully');
    process.exit(0);
  });
});
</code></pre>
<h3>Проблема 4: Памятью утекает на одном сервере</h3>
<p dir="auto"><strong>Симптом</strong>: Один воркер занимает всё больше памяти.</p>
<p dir="auto"><strong>Причина</strong>: Memory leak в коде.</p>
<p dir="auto"><strong>Решение</strong>: Настройте автоматический перезапуск:</p>
<pre><code class="language-javascript">max_memory_restart: '500M'  // PM2 перезагрузит если превышено 500MB
</code></pre>
<h3>Проблема 5: WebSocket не работает через балансировщик</h3>
<p dir="auto"><strong>Симптом</strong>: WebSocket соединение разрывается.</p>
<p dir="auto"><strong>Причина</strong>: Нужен sticky session для WebSocket.</p>
<p dir="auto"><strong>Решение</strong>:</p>
<pre><code class="language-nginx">upstream nodejs_servers {
  ip_hash;  # Обязательно для WebSocket!
  server localhost:3000;
  server localhost:3001;
}

location / {
  proxy_pass http://nodejs_servers;
  proxy_http_version 1.1;
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection "upgrade";
}
</code></pre>
<hr />
<h2>Часть 10: Best Practices и Оптимизация</h2>
<h3>1. Количество воркеров</h3>
<pre><code class="language-javascript">// Оптимально: количество CPU ядер на машине
const numCPUs = require('os').cpus().length;

// PM2:
instances: numCPUs  // или 'max' в ecosystem.config.js
</code></pre>
<h3>2. Graceful Shutdown</h3>
<pre><code class="language-javascript">const server = app.listen(PORT, () =&gt; {
  console.log(`Server on ${PORT}`);
});

process.on('SIGTERM', () =&gt; {
  console.log('SIGTERM received');
  
  server.close(() =&gt; {
    console.log('HTTP server closed');
    process.exit(0);
  });
  
  // Если не закрылось за 10 сек — просто выходим
  setTimeout(() =&gt; {
    console.error('Could not close connections, forcing shutdown');
    process.exit(1);
  }, 10000);
});
</code></pre>
<h3>3. Логирование и Мониторинг</h3>
<pre><code class="language-javascript">const morgan = require('morgan');
const app = express();

// Логируем все запросы
app.use(morgan('combined'));

// Логируем неперехваченные ошибки
process.on('uncaughtException', (error) =&gt; {
  console.error('Uncaught Exception:', error);
  process.exit(1);
});

process.on('unhandledRejection', (reason, promise) =&gt; {
  console.error('Unhandled Rejection at:', promise, 'reason:', reason);
  process.exit(1);
});
</code></pre>
<h3>4. Rate Limiting</h3>
<pre><code class="language-javascript">const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15 минут
  max: 100  // Максимум 100 запросов за окно
});

app.use('/api', limiter);
</code></pre>
<h3>5. Кэширование ответов</h3>
<pre><code class="language-nginx">upstream nodejs_servers {
  server localhost:3000;
  server localhost:3001;
}

proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m;

server {
  location /api/public {
    proxy_pass http://nodejs_servers;
    proxy_cache my_cache;
    proxy_cache_valid 200 10m;  // Кэшировать на 10 минут
  }
}
</code></pre>
<hr />
<h2>Итоги</h2>
<p dir="auto"><strong>Что мы изучили:</strong></p>
<ol>
<li><strong>Зачем нужен балансировщик нагрузки</strong> — распределение запросов, высокая доступность, масштабируемость</li>
<li><strong>Алгоритмы</strong> — Round Robin, Least Connections, IP Hash</li>
<li><strong>Nginx</strong> — надежный, быстрый, конфигурируется просто</li>
<li><strong>Node.js Балансировщик</strong> — гибкий, если нужна кастомная логика</li>
<li><strong>PM2</strong> — управление кластером на одной машине с нулевым downtime</li>
<li><strong>Сессии</strong> — Redis/БД для масштабирования, либо JWT токены</li>
<li><strong>Health Checks</strong> — автоматическое исключение мертвых серверов</li>
<li><strong>Полная архитектура</strong> — Nginx + PM2 кластеры на нескольких машинах</li>
</ol>
<p dir="auto"><strong>Рекомендуемый стек для production:</strong></p>
<pre><code>Интернет
  ↓
Nginx (балансировщик на порт 80/443)
  ↓
Несколько машин (2+) с PM2 кластером (4+ воркера на машину)
  ↓
Централизованное хранилище сессий (Redis)
  ↓
База данных
</code></pre>
<p dir="auto"><strong>Главное правило:</strong> Ваше приложение должно быть <strong>stateless</strong> — не хранить пользовательские данные локально. Тогда балансировка будет работать идеально.</p>
<p dir="auto">Начните с Nginx + 2 простых серверов, потом добавляйте PM2 кластеры по мере роста нагрузки.</p>
]]></description><link>https://forum.exlends.ru/topic/386/node.js-i-balansirovshik-nagruzki-polnoe-rukovodstvo-dlya-novichkov</link><generator>RSS for Node</generator><lastBuildDate>Wed, 20 May 2026 06:36:31 GMT</lastBuildDate><atom:link href="https://forum.exlends.ru/topic/386.rss" rel="self" type="application/rss+xml"/><pubDate>Fri, 09 Jan 2026 20:16:15 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to Node.js и Балансировщик Нагрузки: Полное Руководство для Новичков on Fri, 09 Jan 2026 20:17:22 GMT]]></title><description><![CDATA[<h2>Node.js Load Balancer: Быстрый Старт - Готовые Примеры</h2>
<hr />
<h3>Вариант 1: Nginx + 3 Node.js сервера (самый простой способ)</h3>
<p dir="auto"><strong>Шаг 1. Создайте простой Node.js app</strong></p>
<p dir="auto">Файл <code>app.js</code>:</p>
<pre><code class="language-javascript">const http = require('http');
const PORT = process.env.PORT || 3000;

const server = http.createServer((req, res) =&gt; {
  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, () =&gt; {
  console.log(`Server running on port ${PORT}`);
});
</code></pre>
<p dir="auto"><strong>Шаг 2. Запустите 3 копии</strong></p>
<pre><code class="language-bash"># Терминал 1
PORT=3000 node app.js

# Терминал 2
PORT=3001 node app.js

# Терминал 3
PORT=3002 node app.js
</code></pre>
<p dir="auto"><strong>Шаг 3. Установите Nginx</strong></p>
<pre><code class="language-bash"># macOS
brew install nginx

# Ubuntu
sudo apt-get update &amp;&amp; sudo apt-get install nginx
</code></pre>
<p dir="auto"><strong>Шаг 4. Настройте Nginx</strong></p>
<p dir="auto">Отредактируйте <code>/usr/local/etc/nginx/nginx.conf</code> (macOS) или <code>/etc/nginx/nginx.conf</code> (Ubuntu):</p>
<pre><code class="language-nginx">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;
    }
  }
}
</code></pre>
<p dir="auto"><strong>Шаг 5. Запустите Nginx</strong></p>
<pre><code class="language-bash"># macOS
nginx
# или перезагрузить
nginx -s reload

# Ubuntu
sudo systemctl start nginx
</code></pre>
<p dir="auto"><strong>Шаг 6. Тестируйте</strong></p>
<pre><code class="language-bash"># Откройте браузер и несколько раз перейдите на
# http://localhost:8080

# Или используйте curl
for i in {1..9}; do 
  curl http://localhost:8080 
  echo ""
done
</code></pre>
<p dir="auto">Видите меняющиеся порты (3000, 3001, 3002)? Балансировка работает! <img src="https://forum.exlends.ru/assets/plugins/nodebb-plugin-emoji/emoji/android/2705.png?v=1bd9ff6b60a" class="not-responsive emoji emoji-android emoji--white_check_mark" style="height:23px;width:auto;vertical-align:middle" title="✅" alt="✅" /></p>
<hr />
<h2>Вариант 2: Собственный балансировщик на Node.js</h2>
<p dir="auto"><strong>Файл: <code>load-balancer.js</code></strong></p>
<pre><code class="language-javascript">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) =&gt; {
  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, () =&gt; {
  console.log('Load balancer running on port 8080');
});
</code></pre>
<p dir="auto"><strong>Установите зависимости:</strong></p>
<pre><code class="language-bash">npm init -y
npm install express axios
</code></pre>
<p dir="auto"><strong>Запустите:</strong></p>
<pre><code class="language-bash">node load-balancer.js
</code></pre>
<p dir="auto"><strong>Тестируйте:</strong></p>
<pre><code class="language-bash">curl http://localhost:8080
</code></pre>
<hr />
<h2>Вариант 3: PM2 Cluster Mode (для одной машины)</h2>
<p dir="auto"><strong>Файл: <code>server.js</code></strong></p>
<pre><code class="language-javascript">const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;

app.get('/', (req, res) =&gt; {
  res.json({
    message: 'Hello from Node.js',
    port: PORT,
    processId: process.pid
  });
});

app.listen(PORT, () =&gt; {
  console.log(`Server running on port ${PORT}, PID: ${process.pid}`);
});
</code></pre>
<p dir="auto"><strong>Файл: <code>ecosystem.config.js</code></strong></p>
<pre><code class="language-javascript">module.exports = {
  apps: [{
    name: 'myapp',
    script: './server.js',
    instances: 4,      // 4 копии приложения
    exec_mode: 'cluster',
    env: {
      NODE_ENV: 'development'
    }
  }]
};
</code></pre>
<p dir="auto"><strong>Установите PM2:</strong></p>
<pre><code class="language-bash">npm install -g pm2
npm install express
</code></pre>
<p dir="auto"><strong>Запустите:</strong></p>
<pre><code class="language-bash">pm2 start ecosystem.config.js
</code></pre>
<p dir="auto"><strong>Полезные команды:</strong></p>
<pre><code class="language-bash">pm2 status          # Статус всех процессов
pm2 logs           # Логи в реальном времени
pm2 stop all       # Остановить всё
pm2 delete all     # Удалить из PM2
</code></pre>
<p dir="auto"><strong>Тестируйте на порту 3000:</strong></p>
<pre><code class="language-bash">for i in {1..8}; do 
  curl http://localhost:3000 
  echo ""
done
</code></pre>
<p dir="auto">Видите разные PID? PM2 распределяет между 4 воркерами! <img src="https://forum.exlends.ru/assets/plugins/nodebb-plugin-emoji/emoji/android/2705.png?v=1bd9ff6b60a" class="not-responsive emoji emoji-android emoji--white_check_mark" style="height:23px;width:auto;vertical-align:middle" title="✅" alt="✅" /></p>
<hr />
<h2>Вариант 4: Sticky Sessions (если нужны сессии)</h2>
<p dir="auto"><strong>Backend сервер с Express-session:</strong></p>
<pre><code class="language-javascript">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) =&gt; {
  req.session.userId = 123;
  req.session.username = 'john_doe';
  res.json({ 
    message: 'Logged in',
    port: PORT
  });
});

app.get('/profile', (req, res) =&gt; {
  if (req.session.userId) {
    res.json({
      username: req.session.username,
      port: PORT
    });
  } else {
    res.status(401).json({ error: 'Not logged in' });
  }
});

app.listen(PORT);
</code></pre>
<p dir="auto"><strong>Nginx config с sticky sessions:</strong></p>
<pre><code class="language-nginx">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;
  }
}
</code></pre>
<p dir="auto"><strong>Тестируйте:</strong></p>
<pre><code class="language-bash"># Логинитесь
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
</code></pre>
<hr />
<h2>Вариант 5: Least Connections для лучшей балансировки</h2>
<p dir="auto"><strong>Node.js балансировщик с Least Connections:</strong></p>
<pre><code class="language-javascript">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) =&gt; 
    prev.connections &lt; curr.connections ? prev : curr
  );
}

app.use(async (req, res) =&gt; {
  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, () =&gt; {
  console.log('Load balancer (least connections) on port 8080');
});
</code></pre>
<hr />
<h2>Вариант 6: Health Checks (проверка живых серверов)</h2>
<p dir="auto"><strong>Backend сервер с health endpoint:</strong></p>
<pre><code class="language-javascript">const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;

// Health check endpoint
app.get('/health', (req, res) =&gt; {
  res.json({
    status: 'OK',
    uptime: process.uptime(),
    timestamp: new Date().toISOString()
  });
});

app.get('/', (req, res) =&gt; {
  // Иногда симулируем ошибку
  if (Math.random() &gt; 0.8) {
    res.status(500).json({ error: 'Internal error' });
  } else {
    res.json({ port: PORT });
  }
});

app.listen(PORT);
</code></pre>
<p dir="auto"><strong>Балансировщик с health checks:</strong></p>
<pre><code class="language-javascript">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 () =&gt; {
  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) =&gt; {
  // Находим живой сервер
  const healthyServers = servers.filter(s =&gt; 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);
</code></pre>
<hr />
<h2>Вариант 7: Docker Compose (полная локальная setup)</h2>
<p dir="auto"><strong>docker-compose.yml:</strong></p>
<pre><code class="language-yaml">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
</code></pre>
<p dir="auto"><strong>nginx.conf:</strong></p>
<pre><code class="language-nginx">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;
    }
  }
}
</code></pre>
<p dir="auto"><strong>Запустите:</strong></p>
<pre><code class="language-bash">docker-compose up
</code></pre>
<p dir="auto"><strong>Тестируйте:</strong></p>
<pre><code class="language-bash">curl http://localhost:8080
</code></pre>
<hr />
<h2>Сравнение вариантов</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Вариант</th>
<th>Сложность</th>
<th>Масштабируемость</th>
<th>Когда использовать</th>
</tr>
</thead>
<tbody>
<tr>
<td>Nginx</td>
<td>Низкая</td>
<td>На 1 машину</td>
<td>Для простых случаев, production</td>
</tr>
<tr>
<td>Node.js балансировщик</td>
<td>Средняя</td>
<td>На 1 машину</td>
<td>Когда нужна кастомная логика</td>
</tr>
<tr>
<td>PM2</td>
<td>Низкая</td>
<td>На 1 машину</td>
<td>Быстрый старт, development</td>
</tr>
<tr>
<td>Docker</td>
<td>Средняя</td>
<td>На несколько машин</td>
<td>Микросервисы, облако</td>
</tr>
</tbody>
</table>
<hr />
<h2>Частые ошибки и как их избежать</h2>
<p dir="auto"><strong>Ошибка 1: Забыли установить зависимости</strong></p>
<pre><code class="language-bash">npm install express axios  # Для Node.js балансировщика
</code></pre>
<p dir="auto"><strong>Ошибка 2: Порты уже заняты</strong></p>
<pre><code class="language-bash"># Найти процесс на порту 8080
lsof -i :8080

# Убить процесс
kill -9 &lt;PID&gt;
</code></pre>
<p dir="auto"><strong>Ошибка 3: Nginx ошибка при перезагрузке</strong></p>
<pre><code class="language-bash"># Проверить конфиг
nginx -t

# Если ошибка - смотреть её и исправлять
</code></pre>
<p dir="auto"><strong>Ошибка 4: Соединение отказано (Connection refused)</strong></p>
<pre><code class="language-bash"># Убедитесь что все backend серверы работают!
curl http://localhost:3000
curl http://localhost:3001
curl http://localhost:3002
</code></pre>
<hr />
<h2>Команды для тестирования нагрузки</h2>
<p dir="auto"><strong>Простый тест через curl:</strong></p>
<pre><code class="language-bash">for i in {1..20}; do 
  curl http://localhost:8080 
  echo ""
done
</code></pre>
<p dir="auto"><strong>Apache Bench (если установлен):</strong></p>
<pre><code class="language-bash">ab -n 1000 -c 10 http://localhost:8080/
</code></pre>
<p dir="auto"><strong>Установка Apache Bench:</strong></p>
<pre><code class="language-bash"># macOS
brew install httpd

# Ubuntu
sudo apt-get install apache2-utils
</code></pre>
<p dir="auto"><strong>wrk (продвинутый инструмент):</strong></p>
<pre><code class="language-bash"># Установка
git clone https://github.com/wg/wrk.git
cd wrk &amp;&amp; make

# Тест: 4 потока, 100 соединений, 30 секунд
./wrk -t4 -c100 -d30s http://localhost:8080/
</code></pre>
<hr />
<h2>Логирование и отладка</h2>
<p dir="auto"><strong>PM2 логи:</strong></p>
<pre><code class="language-bash">pm2 logs myapp --lines 100
pm2 logs myapp --err  # Только ошибки
</code></pre>
<p dir="auto"><strong>Nginx логи:</strong></p>
<pre><code class="language-bash"># Все запросы
tail -f /var/log/nginx/access.log

# Ошибки
tail -f /var/log/nginx/error.log
</code></pre>
<p dir="auto"><strong>Node.js логирование:</strong></p>
<pre><code class="language-javascript">// Добавьте в app.js
const fs = require('fs');

app.use((req, res, next) =&gt; {
  console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
  next();
});
</code></pre>
<hr />
<h2>Дополнительные ресурсы</h2>
<ul>
<li>Документация Nginx: <a href="https://nginx.org/en/docs/" target="_blank" rel="noopener noreferrer">https://nginx.org/en/docs/</a></li>
<li>PM2 документация: <a href="https://pm2.io/docs/runtime/" target="_blank" rel="noopener noreferrer">https://pm2.io/docs/runtime/</a></li>
<li>Express.js: <a href="https://expressjs.com/" target="_blank" rel="noopener noreferrer">https://expressjs.com/</a></li>
<li>Axios: <a href="https://axios-http.com/" target="_blank" rel="noopener noreferrer">https://axios-http.com/</a></li>
</ul>
<p dir="auto">Готово! Выбирайте вариант, который вам нравится, и начинайте масштабировать!</p>
]]></description><link>https://forum.exlends.ru/post/1266</link><guid isPermaLink="true">https://forum.exlends.ru/post/1266</guid><dc:creator><![CDATA[Aladdin]]></dc:creator><pubDate>Fri, 09 Jan 2026 20:17:22 GMT</pubDate></item></channel></rss>