Пошук уроків, статей та іншого контенту
Порівняєте збільшення ресурсів одного вузла з додаванням вузлів і навчитеся обирати стратегію масштабування.
Масштабування — це зміна обсягу ресурсів системи, щоб вона могла обробляти більше навантаження або залишалася доступною під час зростання кількості користувачів.
Основні стратегії:
вертикальне масштабування — збільшення ресурсів одного вузла;
горизонтальне масштабування — додавання нових вузлів до системи.
Вузлом може бути сервер застосунку, база даних, черга повідомлень або інший компонент інфраструктури.
Вертикальне масштабування, або scale up, означає збільшення ресурсів існуючого сервера:
більше оперативної пам’яті;
потужніший процесор;
швидший диск;
вища пропускна здатність мережі.
Наприклад, сервер із 2 vCPU та 4 ГБ RAM можна замінити на сервер із 8 vCPU та 32 ГБ RAM.
проста реалізація;
не потрібно змінювати архітектуру застосунку;
не потрібен балансувальник навантаження;
зберігається один екземпляр стану системи;
менше складності під час розробки та підтримки.
Вертикальне масштабування часто є першим кроком, коли система ще працює на одному сервері.
ресурси одного сервера мають фізичну або тарифну межу;
потужніші сервери можуть коштувати непропорційно дорожче;
заміна або перезапуск сервера може спричинити простій;
відмова єдиного вузла може зупинити всю систему;
вертикальне масштабування не усуває єдину точку відмови.
Наприклад, якщо весь застосунок працює на одному сервері, збільшення його RAM не захистить систему від відмови цього сервера.
Горизонтальне масштабування, або scale out, означає додавання нових вузлів із приблизно однаковою роллю.
Замість одного сервера застосунку можна запустити три екземпляри:
Клієнти
|
Балансувальник
/ | \
API API APIБалансувальник розподіляє запити між доступними екземплярами.
Щоб додати потужність, зазвичай запускають ще один екземпляр. Коли навантаження зменшується — частину екземплярів зупиняють. Це називається автоматичним масштабуванням, якщо кількість вузлів змінюється на основі метрик.
можна поступово збільшувати потужність;
відмова одного вузла не обов’язково зупиняє систему;
простіше реалізувати високу доступність;
можна використовувати багато стандартних серверів замість одного дуже потужного;
зручно поєднувати з автоматичним масштабуванням.
потрібен балансувальник або інший механізм розподілу запитів;
виникають складнощі із синхронізацією стану;
локальні файли та пам’ять одного вузла недоступні іншим;
зростає кількість компонентів для моніторингу;
потрібні продумані процеси запуску, зупинки та оновлення вузлів.
Горизонтальне масштабування найпростіше для stateless-застосунків. Такий застосунок не зберігає важливий стан користувача в локальній пам’яті конкретного екземпляра.
Наприклад, небажано зберігати сесію лише в пам’яті одного процесу:
Перший запит -> сервер A, сесія створена в пам’яті A
Другий запит -> сервер B, сесії в пам’яті B немаєУ результаті користувач може втратити авторизацію або дані сесії.
Поширені рішення:
зберігати сесії у спільному сховищі;
передавати стан у базу даних;
використовувати зовнішній кеш;
будувати API так, щоб кожен запит містив необхідний контекст;
уникати залежності від локальної файлової системи.
Застосунок може мати локальний кеш, але він не повинен бути єдиним джерелом критично важливих даних.
Нижче наведено мінімальний HTTP-застосунок без зовнішніх бібліотек. Він може працювати у двох режимах:
app — запускає екземпляр API;
proxy — запускає простий балансувальник із почерговим розподілом запитів.
const http = require('node:http');
const mode = process.argv[2] || 'app';
const port = Number(process.env.PORT || 3001);
const instanceId = process.env.INSTANCE_ID || `instance-${port}`;
if (mode === 'app') {
const server = http.createServer((req, res) => {
if (req.url === '/health') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ status: 'ok', instanceId }));
return;
}
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
message: 'Запит оброблено',
instanceId,
path: req.url
}));
});
server.listen(port, () => {
console.log(`Екземпляр ${instanceId} слухає порт ${port}`);
});
} else if (mode === 'proxy') {
const backends = [
{ host: 'localhost', port: 3001 },
{ host: 'localhost', port: 3002 }
];
let nextBackend = 0;
const proxy = http.createServer((req, res) => {
const backend = backends[nextBackend];
nextBackend = (nextBackend + 1) % backends.length;
const proxyRequest = http.request(
{
host: backend.host,
port: backend.port,
path: req.url,
method: req.method,
headers: req.headers
},
(proxyResponse) => {
res.writeHead(
proxyResponse.statusCode || 502,
proxyResponse.headers
);
proxyResponse.pipe(res);
}
);
proxyRequest.on('error', () => {
res.writeHead(502, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
error: 'Екземпляр застосунку недоступний'
}));
});
req.pipe(proxyRequest);
});
proxy.listen(port, () => {
console.log(`Балансувальник слухає порт ${port}`);
});
} else {
console.error('Режим має бути app або proxy');
process.exit(1);
}Збережіть код у файл server.js, а потім у трьох терміналах запустіть:
PORT=3001 INSTANCE_ID=api-1 node server.js app
PORT=3002 INSTANCE_ID=api-2 node server.js app
PORT=3000 node server.js proxyТепер виконуйте запити до балансувальника:
curl http://localhost:3000/
curl http://localhost:3000/
curl http://localhost:3000/Відповіді будуть почергово надходити від api-1 та api-2.
Це спрощена демонстрація. У реальній системі балансувальник також перевіряє стан вузлів, повторює невдалі запити за потреби, підтримує правила маршрутизації та коректно виводить вузли з роботи.
Важлива властивість прикладу — API не зберігає стан запиту в пам’яті конкретного екземпляра. Тому будь-який вузол може обробити наступний запит.
Балансувальник розподіляє трафік між вузлами та може виконувати такі функції:
перевіряти доступність вузлів;
вилучати несправні вузли з пулу;
повертати їх після відновлення;
завершувати TLS-з’єднання;
розподіляти запити за алгоритмом;
поступово направляти трафік на нову версію застосунку.
Поширені алгоритми розподілу:
round robin — запити передаються вузлам по черзі;
least connections — запит отримує вузол із найменшою кількістю активних з’єднань;
weighted routing — вузли отримують різну частку трафіку відповідно до своїх ресурсів.
Для простих однотипних вузлів часто достатньо round robin. Якщо запити мають різну тривалість або вузли мають різну потужність, потрібен складніший підхід.
Масштабування веб-серверів не розв’язує проблему, якщо вузьким місцем є база даних.
Наприклад:
100 запитів за секунду
|
10 серверів API
|
Одна перевантажена база данихУ такій системі додавання серверів API може майже не покращити результат, тому що всі вони очікують на одну базу даних.
Перед вибором стратегії потрібно визначити компонент, який досягає межі:
процесор;
оперативна пам’ять;
диск;
мережа;
база даних;
зовнішній сервіс;
обмеження кількості з’єднань;
синхронні операції або блокування.
Вертикальне масштабування бази даних часто є простішим, але також має межі. Горизонтальне масштабування бази даних зазвичай потребує окремих підходів до реплікації, розподілу даних і узгодженості.
система невелика або перебуває на ранньому етапі;
потрібно швидко збільшити доступні ресурси;
застосунок складно зробити stateless;
один вузол ще не досяг практичної межі;
простота важливіша за максимальну відмовостійкість;
короткий простій під час оновлення прийнятний.
навантаження зростає або змінюється;
потрібна висока доступність;
застосунок можна запускати в кількох незалежних екземплярах;
потрібно масштабувати ресурси поступово;
окремі вузли можуть виходити з ладу без зупинки системи;
існує механізм балансування та моніторингу.
На практиці стратегії комбінують:
збільшують ресурси кожного вузла до розумного рівня;
додають кілька вузлів застосунку;
розподіляють трафік між ними;
окремо масштабують базу даних та інші вузькі місця.
Такий підхід називають комбінованим масштабуванням.
Потрібно враховувати не лише пропускну здатність, а й повну вартість рішення.
Вертикальне масштабування зазвичай дешевше з точки зору операційної складності:
менше серверів;
простіше розгортання;
простіший моніторинг;
менше мережевої взаємодії.
Горизонтальне масштабування може вимагати додаткових ресурсів:
балансувальника;
спільного сховища стану;
системи автоматичного розгортання;
моніторингу;
журналювання;
перевірок доступності;
механізмів узгодження даних.
Проте горизонтальний підхід може бути вигіднішим для великого або нестабільного навантаження, оскільки дає змогу додавати ресурси поступово.
Якщо проблема в базі даних або повільному зовнішньому сервісі, додаткові сервери API не усунуть її.
Перед масштабуванням потрібно перевірити метрики:
завантаження CPU;
використання пам’яті;
час відповіді;
кількість помилок;
завантаження диска;
кількість з’єднань;
пропускну здатність мережі.
Локальна пам’ять, тимчасові файли та локальні сесії можуть бути доступні лише одному екземпляру. Після переходу запиту на інший вузол цей стан стане недоступним.
Кілька вузлів не гарантують доступність, якщо:
балансувальник є єдиною точкою відмови;
усі вузли залежать від одного несправного сховища;
немає перевірок стану;
вузли працюють у межах однієї аварійної зони.
Система може мати десятки екземплярів API, але залишатися повільною через одну перевантажену залежність.
Додавання вузлів збільшує не лише потужність, а й складність. Якщо система ще мала, горизонтальне масштабування може бути передчасним.
Визначте очікуване та пікове навантаження.
Знайдіть компонент, який досягає межі.
Перевірте, чи достатньо збільшити ресурси одного вузла.
Оцініть вимоги до доступності та допустимого простою.
Перевірте, чи можна зробити екземпляри застосунку незалежними.
Продумайте зберігання сесій, файлів і кешу.
Додайте балансування та перевірки доступності, якщо обираєте горизонтальну стратегію.
Повторно виміряйте систему після змін.
Плутати збільшення ресурсів сервера з додаванням нових серверів.
Масштабувати компонент, який не є вузьким місцем.
Запускати кілька екземплярів застосунку зі спільним локальним станом.
Забувати про балансувальник і перевірки доступності.
Ігнорувати обмеження бази даних.
Вважати, що більше вузлів завжди означає кращу продуктивність.
Не враховувати витрати на експлуатацію розподіленої системи.
Не перевіряти поведінку системи під піковим навантаженням.
Вертикальне масштабування збільшує ресурси одного вузла.
Горизонтальне масштабування додає нові вузли до системи.
Вертикальний підхід простіший, але має межі та не усуває єдину точку відмови.
Горизонтальний підхід краще підходить для високої доступності та змінного навантаження, але потребує балансування й керування станом.
Для горизонтального масштабування застосунок бажано зробити stateless.
Масштабувати потрібно саме вузьке місце, а не довільний компонент.
У реальних системах часто поєднують вертикальне та горизонтальне масштабування.