Пошук уроків, статей та іншого контенту
Навчитеся застосовувати дублювання сервісів, даних і мережевих ресурсів для усунення єдиних точок відмови.
Резервування — це створення додаткових екземплярів компонентів системи, щоб відмова одного з них не призвела до недоступності всієї системи.
Компонентами, які резервують, можуть бути:
сервіси та сервери;
бази даних і сховища;
мережеві канали, балансувальники та зони доступності.
Основна мета резервування — усунути єдину точку відмови (Single Point of Failure, SPOF).
Єдина точка відмови — це компонент, поломка якого зупиняє роботу критичної частини системи.
Наприклад, якщо вебзастосунок працює лише на одному сервері, цей сервер є єдиною точкою відмови. Якщо він стане недоступним, користувачі не зможуть отримати відповідь.
Замість одного екземпляра сервісу запускають кілька екземплярів:
Користувачі
|
Балансувальник
/ \
Сервіс 1 Сервіс 2Балансувальник розподіляє запити між доступними екземплярами. Якщо один сервіс відмовляє, балансувальник перестає надсилати йому нові запити.
У схемі active-active всі екземпляри одночасно обробляють запити.
Переваги:
ресурси використовуються постійно;
збільшується пропускна здатність;
відмова одного екземпляра не обов’язково помітна для користувача.
Недоліки:
потрібно правильно розподіляти запити;
усі екземпляри мають бути узгоджені щодо спільних даних;
складніше працювати зі станом користувача в пам’яті одного сервера.
У схемі active-passive один екземпляр є активним, а інший очікує.
Запити -> Активний сервіс
|
відмова
v
Резервний сервісПісля відмови активного екземпляра резервний починає обробляти запити.
Переваги:
простіше реалізувати;
менше проблем із конфліктами стану.
Недоліки:
резервний ресурс простоює більшу частину часу;
перемикання може зайняти певний час;
необхідно регулярно перевіряти, чи резервний екземпляр справді готовий до роботи.
Балансувальник не повинен визначати доступність сервісу лише за фактом відкритого TCP-з’єднання. Сервер може приймати з’єднання, але не мати доступу до бази даних або працювати некоректно.
Для цього створюють health check, наприклад:
GET /healthВідповідь 200 OK означає, що сервіс готовий приймати трафік. Перевірка може також включати стан критичних залежностей, якщо це відповідає призначенню endpoint.
Водночас health check не повинен створювати велике навантаження або виконувати небезпечні операції.
Нижче наведено runnable-приклад на Node.js без зовнішніх бібліотек. Він запускає:
два екземпляри сервісу на портах 3001 і 3002;
простий шлюз на порту 3000;
автоматичне перемикання на інший екземпляр, якщо поточний недоступний.
const http = require("node:http");
const backends = [
{ name: "service-a", port: 3001 },
{ name: "service-b", port: 3002 },
];
function createBackend(name, port) {
const server = http.createServer((req, res) => {
if (req.url === "/health") {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ status: "ok", service: name }));
return;
}
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({
service: name,
message: "Відповідь від резервованого сервісу",
}));
});
server.listen(port, () => {
console.log(`${name} працює на http://localhost:${port}`);
});
return server;
}
const backendServers = backends.map((backend) => ({
...backend,
server: createBackend(backend.name, backend.port),
}));
let nextBackend = 0;
function requestBackend(backend, req, res) {
const proxyRequest = http.request(
{
hostname: "localhost",
port: backend.port,
path: req.url,
method: req.method,
timeout: 1000,
},
(backendResponse) => {
res.writeHead(
backendResponse.statusCode || 502,
backendResponse.headers,
);
backendResponse.pipe(res);
},
);
proxyRequest.on("timeout", () => {
proxyRequest.destroy(new Error("Перевищено час очікування"));
});
proxyRequest.on("error", (error) => {
if (!res.headersSent) {
res.writeHead(503, { "Content-Type": "application/json" });
res.end(JSON.stringify({
error: "Сервіс тимчасово недоступний",
details: error.message,
}));
}
});
req.pipe(proxyRequest);
}
const gateway = http.createServer((req, res) => {
const startIndex = nextBackend;
nextBackend = (nextBackend + 1) % backends.length;
// Спочатку пробуємо наступний екземпляр за схемою round-robin.
const first = backends[startIndex];
const proxyRequest = http.request(
{
hostname: "localhost",
port: first.port,
path: req.url,
method: req.method,
timeout: 500,
},
(backendResponse) => {
res.writeHead(
backendResponse.statusCode || 502,
backendResponse.headers,
);
backendResponse.pipe(res);
},
);
proxyRequest.on("timeout", () => {
proxyRequest.destroy(new Error("Перевищено час очікування"));
});
proxyRequest.on("error", () => {
// Якщо перший екземпляр недоступний, пробуємо інший.
const fallback = backends.find((backend) => backend !== first);
if (!fallback) {
res.writeHead(503, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "Немає доступних екземплярів" }));
return;
}
requestBackend(fallback, req, res);
});
req.pipe(proxyRequest);
});
gateway.listen(3000, () => {
console.log("Шлюз працює на http://localhost:3000");
console.log("Виконайте: curl http://localhost:3000");
});Запустіть програму:
node failover.jsПісля цього виконайте:
curl http://localhost:3000Шлюз почергово надсилатиме запити до двох сервісів. Якщо один із серверів стане недоступним, запит буде повторно спрямовано до іншого.
Це спрощена демонстрація. У реальній системі балансувальник також має:
регулярно виконувати health checks;
вилучати несправні екземпляри з пулу;
повертати їх до пулу після відновлення;
обмежувати кількість повторних спроб;
записувати події відмови в журнали та метрики.
Дублювання сервісів саме по собі недостатнє. Якщо всі екземпляри використовують одну недоступну базу даних, база залишається єдиною точкою відмови.
Дані можна резервувати за допомогою:
реплікації бази даних;
резервних копій;
дублювання дисків або сховищ;
зберігання копій у різних зонах доступності.
Найпоширеніша схема:
Записи -> Основна база
|
реплікація
v
Резервна базаОсновна база приймає записи, а резервна отримує їх копії. У разі відмови основної бази резервну можна зробити новою основною.
Потрібно враховувати, що реплікація може бути:
синхронною — запис вважається завершеним після підтвердження кількома вузлами;
асинхронною — основна база підтверджує запис раніше, а репліка отримує його пізніше.
Синхронна реплікація зменшує ризик втрати останніх даних, але може збільшити час запису. Асинхронна зазвичай швидша, проте під час раптової відмови частина останніх змін може ще не встигнути потрапити до репліки.
Реплікація копіює зміни, зокрема помилкові:
випадкове видалення даних;
неправильне оновлення;
пошкодження даних через помилку програми.
Тому потрібні окремі резервні копії з можливістю відновлення на певний момент часу.
Реплікація допомагає пережити відмову компонента, а резервна копія допомагає відновити дані після логічної помилки або пошкодження.
Якщо сервіс зберігає сесії лише в пам’яті конкретного екземпляра, перемикання на інший екземпляр може завершити сесію користувача.
Для резервованої системи стан зазвичай:
зберігають у спільному сховищі;
передають у запиті за допомогою токена;
або використовують механізм тимчасової прив’язаності користувача до одного екземпляра, якщо це справді необхідно.
Головний принцип: сервіс не повинен втрачати критичний стан лише через перемикання на інший екземпляр.
Навіть кілька сервісів і реплік бази даних не допоможуть, якщо весь трафік проходить через один несправний мережевий компонент.
Резервувати можна:
балансувальники;
маршрутизатори;
мережеві канали;
підключення до інтернету;
зони доступності та центри обробки даних.
Приклад:
Користувачі
|
Доменне ім’я
|
Балансувальник 1 ----\
Сервіси
Балансувальник 2 ----/Якщо використовується один балансувальник, він сам стає єдиною точкою відмови. Потрібна пара балансувальників або керований сервіс, який приховує цю відмову за своєю інфраструктурою.
Так само один мережевий канал між застосунком і базою даних може стати проблемою. Резервний канал або інший маршрут зменшує ризик повної втрати зв’язку.
Розміщення всіх копій у межах одного фізичного або логічного майданчика не захищає від аварії цього майданчика.
Надійніша схема розподіляє екземпляри між окремими зонами:
Зона A Зона B
Сервіс 1 Сервіс 2
Репліка бази Основна база
Балансувальник БалансувальникВідмова однієї зони не повинна зупиняти систему повністю.
Резервування підвищує доступність, але не гарантує її автоматично.
Необхідні додаткові механізми:
Виявлення відмови — система має зрозуміти, що компонент несправний.
Перемикання — трафік потрібно спрямувати до справного компонента.
Синхронізація — резервний компонент має мати актуальні дані та конфігурацію.
Перевірка відновлення — потрібно переконатися, що резервний компонент справді працює.
Повернення до штатної схеми — після відновлення необхідно безпечно повернути компонент у систему.
Якщо один із цих кроків не працює, дублювання може існувати лише формально.
Для кожного критичного компонента поставте такі запитання:
Що станеться, якщо цей компонент вимкнеться?
Чи є інший компонент, який може виконати його роботу?
Як система виявить відмову?
Як відбудеться перемикання?
Чи актуальні дані в резервній копії?
Чи розміщені копії в іншій зоні?
Як перевіряється відновлення?
Чи можна втратити частину даних під час відмови?
Корисно скласти простий список компонентів:
Вебсервіс — 2 екземпляри
Балансувальник — резервна конфігурація або 2 екземпляри
База даних — основна база та репліка
Файли — копія в іншому сховищі
Мережа — 2 незалежні маршрутиНесправний резервний сервер не допоможе під час аварії. Регулярно перевіряйте health checks і проводьте контрольовані тести перемикання.
Кілька серверів в одній зоні не захищають від аварії всієї зони.
Реплікація захищає переважно від відмови компонента. Резервні копії потрібні для відновлення після помилкових змін або видалення.
Два екземпляри API не усувають проблему, якщо вони залежать від одного сервера бази даних, одного диска або одного мережевого каналу.
Постійні retry можуть перевантажити систему, яка вже працює на межі можливостей. Кількість спроб, тайм-аут і подальша поведінка мають бути обмежені.
Два екземпляри сервісу можуть одночасно змінювати ті самі дані. Потрібно заздалегідь визначити правила запису, реплікації та розв’язання конфліктів.
Резервування усуває єдині точки відмови.
Сервіси можна дублювати за схемами active-active або active-passive.
Балансувальник має перевіряти доступність екземплярів і перемикати трафік.
Дані резервують за допомогою реплікації та резервних копій.
Реплікація не замінює резервне копіювання.
Мережеві канали, балансувальники та зони доступності також можуть бути точками відмови.
Резервні компоненти потрібно перевіряти, синхронізувати та регулярно тестувати.