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/
Готово! Выбирайте вариант, который вам нравится, и начинайте масштабировать!