Стійкість програмного забезпечення означає здатність програми продовжувати безперебійну та надійну роботу навіть у разі неочікуваних проблем або збоїв. У фінтех-проектах стійкість особливо важлива з кількох причин. По-перше, компанії зобов’язані відповідати нормативним вимогам, а фінансові регулятори наголошують на операційній стійкості для підтримки стабільності в системі. Крім того, поширення цифрових інструментів і залежність від сторонніх постачальників послуг наражає фінтех-бізнес на підвищені загрози безпеці. Стійкість також допомагає зменшити ризики збоїв, спричинених різними факторами, такими як кіберзагрози, пандемії або геополітичні події, захищаючи основні бізнес-операції та критично важливі активи.
Під шаблонами стійкості ми розуміємо набір найкращих практик і стратегій, розроблених для забезпечення того, щоб програмне забезпечення могло протистояти збоям і підтримувати свою роботу. Ці шаблони діють як мережі безпеки, забезпечуючи механізми для обробки помилок, керування навантаженням і відновлення після збоїв, таким чином гарантуючи, що додатки залишаються стійкими та надійними за несприятливих умов.
Найпоширеніші стратегії стійкості включають перегородку, кеш-пам’ять, резервний варіант, повторну спробу та автоматичний вимикач. У цій статті я розповім про них докладніше, наведу приклади проблем, які вони можуть допомогти вирішити.
Давайте розглянемо наведене вище налаштування. У нас є звичайнісінька програма з кількома серверними програмами, з яких можна отримати деякі дані. До цих серверних модулів підключено кілька HTTP-клієнтів. Виявляється, усі вони мають один і той же пул з’єднань! А також інші ресурси, такі як процесор і оперативна пам'ять.
Що станеться, якщо в одному з серверних модулів виникнуть певні проблеми, що призведе до великої затримки запиту? Через великий час відповіді весь пул з’єднань буде повністю зайнятий запитами, які очікують відповіді від серверної частини1. У результаті запити, призначені для справних серверів 2 і серверів 3, не зможуть продовжуватися, оскільки пул вичерпано. Це означає, що збій в одному з наших серверів може спричинити збій усієї програми. В ідеалі ми хочемо, щоб лише функціональні можливості, пов’язані з несправною серверною частиною, зазнали погіршення, а решта програми продовжувала працювати нормально.
Що таке візерунок перегородки?
Термін «схема перегородки» походить від суднобудування, він передбачає створення кількох ізольованих відсіків усередині корабля. Якщо витік відбувається в одному відсіку, він заповнюється водою, але інші відсіки залишаються незмінними. Ця ізоляція запобігає затопленню всього судна через один пролом.
Патерн перегородки можна використовувати для ізоляції різних типів ресурсів у програмі, запобігаючи вплинунню збою однієї частини на всю систему. Ось як ми можемо застосувати це до нашої проблеми:
Припустімо, що наші серверні системи мають низьку ймовірність індивідуальних помилок. Однак, коли операція включає паралельне запитування всіх цих серверних модулів, кожен може незалежно повертати помилку. Оскільки ці помилки виникають незалежно, загальна ймовірність помилки в нашій програмі вища, ніж ймовірність помилки будь-якої окремої серверної частини. Кумулятивну ймовірність помилки можна обчислити за формулою P_total=1−(1−p)^n, де n — кількість серверних систем.
Наприклад, якщо у нас є десять серверних модулів, кожна з яких має ймовірність помилки p=0,001 (що відповідає SLA 99,9%), кінцева ймовірність помилки:
P_total=1−(1−0,001)^10=0,009955
Це означає, що наше об’єднане SLA падає приблизно до 99%, ілюструючи, як загальна надійність знижується під час паралельного запиту кількох серверних модулів. Щоб пом’якшити цю проблему, ми можемо застосувати кеш-пам’ять.
Кеш-пам’ять служить високошвидкісним буфером даних, зберігаючи дані, до яких часто звертаються, і усуває необхідність щоразу отримувати їх із потенційно повільних джерел. Оскільки ймовірність помилки кеш-пам’яті, що зберігається в пам’яті, становить 0% порівняно з отриманням даних через мережу, вони значно підвищують надійність нашої програми. Крім того, кешування зменшує мережевий трафік, ще більше знижуючи ймовірність помилок. Отже, використовуючи кеш-пам’ять, ми можемо досягти ще нижчого рівня помилок у нашій програмі порівняно з нашими серверними системами. Крім того, кеші в пам’яті пропонують швидший пошук даних, ніж вибірка через мережу, тим самим зменшуючи затримку додатків, що є помітною перевагою.
Для персоналізованих даних, таких як профілі користувачів або рекомендації, використання кешу в пам’яті також може бути дуже ефективним. Але нам потрібно переконатися, що всі запити від користувача постійно надходять до одного екземпляра програми, щоб використовувати дані, збережені для них, що вимагає закріплених сеансів. Реалізація закріплених сеансів може бути складною, але для цього сценарію нам не потрібні складні механізми. Незначне перебалансування трафіку є прийнятним, тому стабільного алгоритму балансування навантаження, як-от послідовного хешування, буде достатньо.
Більше того, у разі збою вузла узгоджене хешування гарантує, що лише користувачі, пов’язані з несправним вузлом, пройдуть ребалансування, мінімізуючи збої в системі. Цей підхід спрощує керування персоналізованими кеш-пам’ятами та підвищує загальну стабільність і продуктивність нашої програми.
Якщо дані, які ми збираємося кешувати, є критичними та використовуються в кожному запиті, який обробляє наша система, як-от політики доступу, плани підписки чи інші життєво важливі об’єкти в нашому домені, джерело цих даних може стати причиною значного збою в нашій системі. Щоб вирішити цю проблему, одним із підходів є повне копіювання цих даних безпосередньо в пам’ять нашої програми.
У цьому сценарії, якщо обсягом даних у джерелі можна керувати, ми можемо розпочати процес, завантаживши знімок цих даних на початку нашої програми. Згодом ми можемо отримувати події оновлень, щоб гарантувати, що кешовані дані залишаються синхронізованими з джерелом. Використовуючи цей метод, ми підвищуємо надійність доступу до цих важливих даних, оскільки кожне отримання відбувається безпосередньо з пам’яті з імовірністю помилки 0%. Крім того, отримання даних із пам’яті надзвичайно швидке, завдяки чому оптимізується продуктивність нашої програми. Ця стратегія ефективно зменшує ризик, пов’язаний із використанням зовнішнього джерела даних, забезпечуючи послідовний і надійний доступ до важливої інформації для роботи нашої програми.
Однак необхідність завантажувати дані під час запуску програми, що затримує процес запуску, порушує один із принципів «12-факторної програми», яка підтримує швидкий запуск програми. Але ми не хочемо втрачати переваги використання кешування. Щоб вирішити цю дилему, давайте дослідимо можливі рішення.
Швидкий запуск має вирішальне значення, особливо для таких платформ, як Kubernetes, які покладаються на швидку міграцію програм на різні фізичні вузли. На щастя, Kubernetes може керувати програмами, що запускаються повільно, використовуючи такі функції, як зонди запуску.
Іншою проблемою, з якою ми можемо зіткнутися, є оновлення конфігурацій під час роботи програми. Часто коригування часу кешування або тайм-аутів запитів необхідне для вирішення проблем виробництва. Навіть якщо ми можемо швидко розгорнути оновлені файли конфігурації в нашій програмі, застосування цих змін зазвичай вимагає перезапуску. У зв’язку з тим, що час запуску кожної програми збільшується, постійний перезапуск може значно затримати впровадження виправлень для наших користувачів.
Щоб вирішити цю проблему, одним із рішень є збереження конфігурацій у паралельній змінній і періодичне оновлення фонового потоку. Однак певні параметри, наприклад тайм-аути запитів HTTP, можуть вимагати повторної ініціалізації HTTP або клієнтів бази даних, коли відповідна конфігурація змінюється, що створює потенційну проблему. Проте деякі клієнти, як-от драйвер Cassandra для Java, підтримують автоматичне перезавантаження конфігурацій, спрощуючи цей процес.
Реалізація перезавантажуваних конфігурацій може пом’якшити негативний вплив тривалого часу запуску додатків і запропонувати додаткові переваги, такі як полегшення впровадження прапорів функцій. Такий підхід дає нам змогу підтримувати надійність і швидкість реагування програми, одночасно ефективно керуючи оновленнями конфігурації.
Тепер давайте подивимося на іншу проблему: у нашій системі, коли запит користувача отримується та обробляється шляхом надсилання запиту до сервера або бази даних, іноді замість очікуваних даних отримується відповідь про помилку. Згодом наша система відповідає користувачеві «помилкою».
Однак у багатьох сценаріях краще відображати трохи застарілі дані разом із повідомленням про затримку оновлення даних, а не залишати користувача з великим червоним повідомленням про помилку.
Щоб вирішити цю проблему та покращити поведінку нашої системи, ми можемо застосувати резервний шаблон. Концепція цього шаблону передбачає наявність вторинного джерела даних, яке може містити дані нижчої якості або свіжості порівняно з первинним джерелом. Якщо основне джерело даних недоступне або повертає помилку, система може повернутися до отримання даних із цього вторинного джерела, гарантуючи, що певна форма інформації буде представлена користувачеві замість відображення повідомлення про помилку.
Якщо ви подивіться на зображення вище, ви помітите подібність між проблемою, з якою ми зараз зіткнулися, і тією, з якою ми зіткнулися у прикладі кешу.
Щоб її вирішити, ми можемо застосувати шаблон, відомий як повторна спроба. Замість того, щоб покладатися на кеш-пам’ять, систему можна спроектувати так, щоб автоматично повторно надсилати запит у разі помилки. Цей шаблон повторення пропонує простішу альтернативу та може ефективно зменшити ймовірність помилок у нашій програмі. На відміну від кешування, яке часто потребує складних механізмів анулювання кешу для обробки змін даних, повторну спробу невдалих запитів відносно просто реалізувати. Оскільки визнання кешу недійсним широко вважається одним із найскладніших завдань у розробці програмного забезпечення, застосування стратегії повторних спроб може оптимізувати обробку помилок і підвищити стійкість системи.
Однак застосування стратегії повторної спроби без урахування можливих наслідків може призвести до подальших ускладнень.
Давайте уявімо, що один із наших серверів зазнає збою. У такому сценарії ініціювання повторних спроб до серверної частини, що не впоралася, може призвести до значного збільшення обсягу трафіку. Цей раптовий сплеск трафіку може перевантажити серверну частину, посиливши збій і потенційно спричинивши ефект каскаду в системі.
Щоб впоратися з цією проблемою, важливо доповнити шаблон повторної спроби шаблоном автоматичного вимикача. Автоматичний вимикач служить захисним механізмом, який відстежує частоту помилок низхідних служб. Коли частота помилок перевищує попередньо визначений поріг, автоматичний вимикач перериває запити до відповідної служби на певний час. Протягом цього періоду система утримується від надсилання додаткових запитів, щоб дати можливість відновити час несправного обслуговування. Після визначеного інтервалу автоматичний вимикач обережно пропускає обмежену кількість запитів, перевіряючи, чи стабілізувалося обслуговування. Якщо сервіс відновився, нормальний трафік поступово відновлюється; інакше ланцюг залишається відкритим, продовжуючи блокувати запити, доки служба не відновить нормальну роботу. Інтегруючи шаблон автоматичного вимикача разом із логікою повторних спроб, ми можемо ефективно керувати помилковими ситуаціями та запобігати перевантаженню системи під час збоїв серверної частини.
Підсумовуючи, запроваджуючи ці шаблони стійкості, ми можемо посилити наші додатки проти надзвичайних ситуацій, підтримувати високу доступність і надавати користувачам бездоганний досвід. Крім того, я хотів би підкреслити, що телеметрія є ще одним інструментом, який не можна нехтувати, забезпечуючи стійкість проекту. Хороші журнали та показники можуть значно підвищити якість послуг і надати цінну інформацію про їх ефективність, допомагаючи приймати обґрунтовані рішення щодо їх подальшого вдосконалення.