Пошук уроків, статей та іншого контенту
Розберете мету System Design, ключові компоненти систем і принципи проєктування масштабованих рішень.
System Design — це проєктування структури програмної системи: її компонентів, зв’язків між ними, способів зберігання даних і обробки запитів.
На практиці System Design допомагає відповісти на запитання:
з яких частин складатиметься система;
як ці частини взаємодіятимуть;
де зберігатимуться дані;
як система працюватиме під навантаженням;
що станеться, якщо окремий компонент стане недоступним;
як систему можна буде розширювати в майбутньому.
System Design потрібен не лише для великих компаній. Навіть невеликий вебзастосунок має систему: клієнт надсилає запит, сервер обробляє його, база даних зберігає інформацію, а відповідь повертається користувачу.
Без попереднього проєктування система може працювати на початку, але швидко зіткнутися з проблемами:
повільно обробляти запити;
втрачати дані;
ставати недоступною через несправність одного компонента;
бути складною для розширення;
вимагати повної переробки після збільшення кількості користувачів.
System Design дає змогу заздалегідь продумати:
Навантаження — скільки користувачів і запитів вона має обробляти.
Надійність — як система поводитиметься під час помилок.
Масштабованість — як збільшити її можливості зі зростанням навантаження.
Підтримуваність — наскільки легко систему змінювати й розвивати.
Перед проєктуванням потрібно визначити вимоги.
Функціональні вимоги описують, що саме робить система.
Наприклад, для сервісу скорочення посилань:
користувач надсилає довге посилання;
система створює короткий ідентифікатор;
за коротким посиланням користувач переходить на оригінальну адресу;
система зберігає відповідність між коротким і довгим посиланнями.
Нефункціональні вимоги описують, як саме система повинна працювати.
Приклади:
відповідь має надходити не довше ніж за 200 мілісекунд;
система має обробляти 10 000 запитів за секунду;
дані не повинні втрачатися;
сервіс має залишатися доступним у разі відмови одного сервера;
систему можна розширити без значної зміни коду.
Одна й та сама функціональність може мати різний System Design залежно від цих вимог.
Типова вебсистема складається з кількох компонентів.
Клієнтом може бути:
веббраузер;
мобільний застосунок;
інший сервер;
консольна програма.
Клієнт надсилає запити до системи та отримує відповіді.
Сервер застосунку:
приймає запити;
перевіряє вхідні дані;
виконує бізнес-логіку;
звертається до інших компонентів;
формує відповідь.
Наприклад, сервер може отримати запит на створення замовлення, перевірити товар, зберегти замовлення в базі даних і повернути його ідентифікатор.
База даних зберігає інформацію між запитами.
У ній можуть зберігатися:
користувачі;
замовлення;
повідомлення;
налаштування;
зв’язки між об’єктами.
База даних повинна відповідати характеру даних і запитів. Важливо продумати, які дані зберігаються, як вони шукаються та як змінюються.
Кеш зберігає часто потрібні дані ближче до сервера або користувача.
Наприклад, якщо список популярних товарів рідко змінюється, його можна тимчасово зберігати в кеші. Це зменшує кількість звернень до бази даних і пришвидшує відповіді.
Кеш не повинен бути єдиним місцем зберігання важливих даних, якщо його можна очистити або втратити.
Балансувальник розподіляє запити між кількома серверами застосунку.
Наприклад:
Клієнти
|
v
Балансувальник
| | |
v v v
Сервер 1 Сервер 2 Сервер 3
\ | /
\ | /
База данихЯкщо один сервер перевантажений або недоступний, балансувальник може направити запит до іншого.
Черга дає змогу виконувати деякі операції не одразу, а у фоновому режимі.
Наприклад, після реєстрації користувача система може:
швидко створити обліковий запис;
додати завдання на надсилання електронного листа до черги;
окремий працівник обробить це завдання пізніше.
Так основний запит не очікує завершення повільної операції.
Розглянемо простий потік запиту:
Користувач відкриває вебзастосунок.
Клієнт надсилає HTTP-запит.
Балансувальник вибирає сервер застосунку.
Сервер перевіряє запит і виконує бізнес-логіку.
Сервер читає або змінює дані в базі.
Сервер повертає відповідь клієнту.
Для операцій, які виконуються довго, сервер може додатково використати кеш або чергу.
Найпростіший приклад HTTP-сервера на Node.js:
const http = require("node:http");
const products = [
{ id: 1, name: "Keyboard", price: 80 },
{ id: 2, name: "Mouse", price: 40 }
];
const server = http.createServer((request, response) => {
if (request.method === "GET" && request.url === "/products") {
response.writeHead(200, {
"Content-Type": "application/json; charset=utf-8"
});
response.end(JSON.stringify(products));
return;
}
response.writeHead(404, {
"Content-Type": "application/json; charset=utf-8"
});
response.end(JSON.stringify({
error: "Маршрут не знайдено"
}));
});
server.listen(3000, () => {
console.log("Сервер запущено: http://localhost:3000");
});У цьому прикладі:
клієнтом може бути браузер;
Node.js-сервер виконує роль сервера застосунку;
масив products тимчасово виконує роль сховища даних;
HTTP-відповідь містить дані або помилку.
У реальній системі масив замінили б базою даних. Якщо запитів стане багато, можна запустити кілька копій сервера та додати балансувальник.
Масштабованість — це здатність системи працювати після збільшення кількості користувачів, даних або запитів.
Вертикальне масштабування означає збільшення потужності одного сервера:
більше оперативної пам’яті;
потужніший процесор;
швидший диск;
краща мережа.
Перевага цього підходу — простота. Недолік — можливості одного сервера обмежені, а сам сервер може стати єдиною точкою відмови.
Горизонтальне масштабування означає додавання нових серверів.
Наприклад, замість одного сервера застосунку використовують три, а балансувальник розподіляє між ними запити.
Переваги:
можна поступово збільшувати кількість серверів;
відмова одного сервера не обов’язково зупиняє систему;
навантаження розподіляється між кількома екземплярами.
Для горизонтального масштабування сервери застосунку бажано робити безстанними.
Безстанний сервер не зберігає важливий стан користувача лише у власній пам’яті.
Наприклад, якщо користувач увійшов у систему, інформацію про його сесію не варто зберігати тільки на сервері 1. Наступний запит може потрапити на сервер 2, який нічого не знає про цю сесію.
Стан можна зберігати в спільному компоненті:
базі даних;
спеціальному сховищі сесій;
іншому централізованому сховищі.
Тоді будь-який сервер зможе обробити запит користувача.
Надійність означає, що система правильно працює протягом тривалого часу.
Доступність означає, що система доступна для користувачів тоді, коли вони намагаються нею скористатися.
Щоб підвищити надійність системи, застосовують:
кілька екземплярів критичних компонентів;
резервні копії даних;
перевірки стану серверів;
обмеження часу очікування;
повторні спроби для тимчасових помилок;
моніторинг і журналювання.
Не всі компоненти потрібно дублювати однаково. Спочатку визначають, які частини є критичними для роботи системи.
Вузьке місце — це компонент, який обмежує продуктивність усієї системи.
Приклади:
один сервер приймає всі запити;
база даних не встигає обробляти запити;
повільний зовнішній сервіс блокує відповідь;
великі файли передаються через сервер застосунку;
часто потрібні дані щоразу читаються з бази.
Пошук вузьких місць починається з вимірювань. Не варто оптимізувати компонент лише на основі припущень.
Для початкового проєктування системи можна використовувати такий порядок:
Визначити мету системи.
Наприклад: «Користувачі можуть створювати та переглядати короткі посилання».
Записати функціональні вимоги.
Які дії повинні підтримуватися?
Записати нефункціональні вимоги.
Яка очікувана кількість користувачів, швидкість відповіді та доступність?
Оцінити дані.
Які сутності зберігатимуться та як вони пов’язані?
Намалювати основні компоненти.
Клієнт, сервер, база даних, кеш та інші необхідні частини.
Описати потік запиту.
Який шлях проходить запит від клієнта до відповіді?
Знайти можливі проблеми.
Що станеться при зростанні навантаження або відмові компонента?
Додати масштабування лише там, де воно потрібне.
Система має залишатися зрозумілою, а не містити зайві компоненти.
Питання «Яку базу даних використати?» не повинно бути першим.
Спочатку потрібно зрозуміти:
які дані зберігаються;
які операції виконуються найчастіше;
яке навантаження очікується;
які вимоги до надійності.
Лише після цього можна обирати технології.
Неможливо оцінити якість архітектури, якщо невідомо, що система повинна забезпечувати.
Система для кількох сотень користувачів і система для мільйонів користувачів можуть мати зовсім різну структуру.
Мікросервіси, черги, кеші та кілька баз даних не роблять систему автоматично кращою.
Кожен додатковий компонент створює:
нові точки відмови;
складніші розгортання;
більше способів взаємодії;
додаткові витрати на підтримку.
Починайте з найпростішої архітектури, яка відповідає вимогам.
Потрібно думати не лише про успішний сценарій.
Корисні запитання:
що буде, якщо база даних недоступна;
що станеться, якщо один сервер зупиниться;
як система повідомить про помилку;
чи можна повторити операцію без дублювання даних?
Це ускладнює горизонтальне масштабування. Якщо запити користувача потрапляють на різні сервери, спільний стан має зберігатися у відповідному спільному сховищі.
System Design — це проєктування структури програмної системи та взаємодії її компонентів.
Спочатку визначають функціональні й нефункціональні вимоги.
Типові компоненти системи — клієнт, сервер застосунку, база даних, кеш, балансувальник і черга.
Вертикальне масштабування збільшує потужність одного сервера, а горизонтальне додає нові сервери.
Безстанні сервери легше масштабувати горизонтально.
Надійність забезпечують резервування, перевірки стану, резервні копії та моніторинг.
Добрий System Design починається з вимог і залишається настільки простим, наскільки це можливо.