Projekt systemu: Przygotuj się na najgorsze
Podczas opracowywania nowej aplikacji lub nowej funkcji jedną z ważnych rzeczy, na które należy zwrócić uwagę, jest zdolność aplikacji do samodzielnego odzyskiwania w przypadku awarii. Twój kod może być świetny i spełniać wymagania biznesowe, ALE czy jest wystarczająco dobry, aby robić to również w czasach nieoczekiwanych awarii? Czy Twoja aplikacja jest wystarczająco odporna, aby automatycznie przezwyciężyć te awarie?
Czasami nie zostaniesz pośrednio poinstruowany, aby wdrożyć mechanizmy odporności i rezerwowe jako część wymagań biznesowych, ponieważ scenariusze, które ma obsłużyć, mogą być bardzo techniczne i napędzane rozwojem. Mechanizmy te były związane z rodziną wymagań niefunkcjonalnych.
Czym są te „nieoczekiwane awarie”, o których mówimy?
Cóż, mogą się zdarzyć różne scenariusze, które doprowadzą twoją aplikację do stanu awarii/niestabilności. Na przykład:
- Przekroczono limit czasu / Usługa niedostępna zewnętrznego interfejsu API używanego przez Twoją aplikację
- Błędy stron trzecich, z których korzysta Twoja aplikacja
- Niepowodzenie operacji bazy danych
- Nieoczekiwane obciążenie aplikacji, które powoduje pogorszenie wydajności, a tym samym odrzucanie przychodzących żądań
- Nieoczekiwany błąd, który powoduje utratę danych
- długotrwałe procesy zarządzane przez aplikację utknęły w swoim stanie z powodu utraty danych
Posiadanie aplikacji bez odpowiednich mechanizmów samoodzyskiwania i awaryjnych nie przetrwa na dłuższą metę. Te „nieoczekiwane awarie” w końcu się wydarzą, co zaszkodzi umowie SLA i doświadczeniu klienta. Może to spowodować utratę danych, które będą trudne do odzyskania i będą wymagać od CIEBIE ręcznej interwencji i wykonania pewnych czynności magicznych w celu rozwiązania problemu. Pomyśl teraz o aplikacjach, które obsługują setki tysięcy żądań na minutę, mała awaria może mieć duży wpływ, który może być trudny do naprawienia ręcznie i będzie kosztować dużo czasu i pieniędzy dla Twojej organizacji.
Dlatego musisz przygotować się na najgorsze.
Przyjrzyjmy się kilku typowym scenariuszom i zaproponuj rozwiązania, które mogą zwiększyć poziom odporności aplikacji i możliwości awaryjne, abyś mógł spać spokojnie w nocy :)
Mechanizm ponawiania
Zwłaszcza w przypadku operacji we/wy, takich jak wysyłanie żądań HTTP, odczyt/zapis z bazy danych lub pliku itp., prawdopodobieństwo napotkania błędu jest powszechne. Na przykład: Usługa niedostępna, Wewnętrzny błąd serwera, Przekroczono limit czasu operacji i inne.
Wiele z tych błędów może być przejściowych i zatrzymać się w ciągu kilku sekund, co oznacza, że istnieje duża szansa, że następna próba po kilku sekundach zadziała.
Więc… dlaczego zawodzisz w swojej firmie tylko z powodu drobnego tymczasowego problemu? Spróbuj ponownie! Użyj mechanizmu ponawiania operacji, który zapewni, że jeśli problem jest tymczasowy, aplikacja automatycznie go przezwycięży.
Zwróć uwagę na następujące kwestie:
- Być w stanie skonfigurować i ograniczyć liczbę ponownych prób, które aplikacja próbuje wykonać. Nieskończona / duża liczba ponownych prób może spowodować zablokowanie aplikacji i odrzucenie innych żądań, ponieważ będzie ona zajęta niekończącymi się próbami
- zaimplementować wykładniczy wypiekanie. Wykładniczo zwiększ interwał między kolejnymi próbami.
Zwiększy to szansę na rozwiązanie tymczasowego problemu (na serwerze/bazie danych/zewnętrznej stronie) i następna próba zakończy się sukcesem. Ponadto użycie interwału wykładniczego daje również łaskę stronie trzeciej, która może być obciążona i dlatego nie może obsługiwać swoich klientów. Wykonywanie natychmiastowych ponownych prób bez opóźnień może pogorszyć problem. - Upewnij się, że rozumiesz, przy których błędach należy ponowić próbę, a przy których nie. Nie każdy błąd można rozwiązać za pomocą kolejnej próby. Załóżmy na przykład, że Twoje żądanie http nie powiodło się z kodem stanu „Złe żądanie” (400). Oznacza to, że coś z danymi żądania jest błędne i zostało odrzucone przez serwer. Bez względu na to, ile ponowień wykona Twoja aplikacja, zakończy się to tym samym wynikiem — niepowodzeniem.
Ponawianie prób w przypadku tego rodzaju błędów jest zbędne i wpłynie na wydajność aplikacji. Musisz rozróżnić różne błędy i zdecydować, którą próbę można powtórzyć.
Istnieje wiele bibliotek open source, które już zapewniają wszystkie istotne funkcje mechanizmu ponawiania, więc nie musisz pisać go od zera. Na przykład w .NET masz bibliotekę o nazwie Polly.
Nie zapomnij zadać pytania, co powinienem zrobić, jeśli wszystkie ponowne próby zakończą się niepowodzeniem? To jest pytanie biznesowe, a odpowiedź można zmieniać w zależności od scenariusza.
Zdefiniuj limit czasu dla swoich procesów
Niektóre aplikacje zarządzają długotrwałymi procesami. Proces posiada stan wskazujący na postęp. Postęp procesu czasami zależy od długich operacji lub asynchronicznej odpowiedzi od strony trzeciej. Ponieważ coś może pójść nie tak (i zawsze się nie udaje), Twój proces może utknąć na zawsze w stanie niekońcowym i pozostać „otwarty”. Na przykład strona trzecia miała problem z odesłaniem odpowiedzi.
Ok, więc pozostaną otwarte. Co to jest wielka sprawa?
Najprawdopodobniej inne systemy są uzależnione od twoich procesów i czekają na ich wynik. Twoja aplikacja zablokuje cały przepływ biznesowy, wpływając na umowę SLA innych usług i wrażenia użytkownika.
Co więcej, te otwarte procesy utrudnią dochodzenia i analizę danych, ponieważ zachowują się inaczej i mogą zaszkodzić Twoim statystykom.
nie chcę tego! Jak to rozwiązać?
Najpierw zdefiniuj, jaki jest odpowiedni i akceptowalny czas, przez jaki twój proces może pozostawać w stanie niekońcowym. Jest to kwestia biznesowa i powinna być określona w umowie SLA, do której jesteś zobowiązany, oraz w ocenie, jaki jest całkowity rozsądny czas, jaki powinien zająć operacje w ramach procesu. Na przykład, jeśli mój proces zależy od strony trzeciej, której średni czas odpowiedzi wynosi 10 minut, możesz zdefiniować limit czasu na 20–30 minut.
Teraz możesz przejść do implementacji. Podstawową implementacją może być utworzenie zaplanowanej operacji uruchamianej co X minut i szukającej otwartych procesów przez więcej niż Y minut (wartość limitu czasu). Jeśli zostanie znaleziony, po prostu przenieś proces do stanu końcowego, może to być stan awarii/stan anulowania.
W ten sposób masz możliwość płynnego niepowodzenia/anulowania procesu. Odbywa się to w łatwy do zarządzania sposób i zapewnia aplikacji możliwość powiadamiania oczekujących klientów o odpowiednim wyniku.
Zauważyć! wzorzec sam w sobie nie rozwiązał problemu, ALE zapewnił ci możliwość automatycznego zarządzania nim i wykonania przepływu awaryjnego. Możliwość poinformowania o problemie i dotrzymania obiecanej umowy SLA.
Spójrzmy na rzeczywisty scenariusz, który wykorzystuje ten wzór:
Mój zespół w Payoneer jest odpowiedzialny za proces automatycznego zatwierdzania dokumentów klientów. Proces zatwierdzania dokumentu to proces offline składający się z wielu kroków i obejmujący integrację z zewnętrznymi dostawcami, którzy dostarczają wyniki analizy za pośrednictwem komunikacji asynchronicznej.
Nasz system realizuje wzorzec przekroczenia limitu czasu i po przekroczeniu limitu czasu system przenosi dokumenty klienta do ręcznego przeglądu przez przedstawiciela zamiast czekać na automatyczny wynik.
W ten sposób nie wpływamy na umowę SLA, którą mamy przed klientem w odniesieniu do czasu potrzebnego na przejrzenie jego dokumentów.
Nie przekazuj tylko wywołań zwrotnych/webhooka — zaimplementuj mechanizm odpytywania
W przypadku komunikacji asynchronicznej odpowiedź jest zwracana do klienta jako wywołanie zwrotne. Może to być serwer wyzwalający webhook z danymi lub serwer publikujący zdarzenie za pośrednictwem magistrali zdarzeń/brokera komunikatów.
Istnieją scenariusze, w których wiadomość nie została odebrana przez klienta lub została odebrana, ale nie została poprawnie obsłużona i dlatego została odrzucona, co prowadzi do utraty danych. W tych scenariuszach klient nadal oczekuje na odpowiedź, ale serwer już ją wysłał i nie wyśle jej ponownie.
w poprzedniej sekcji wspomnieliśmy o wzorcu limitu czasu, który nie rozwiązuje problemu, ale pozwala z wdziękiem go zawieść i włączyć zarządzalny przepływ awaryjny. Wdrażając mechanizm ankietowania, będziesz w stanie automatycznie rozwiązać część problemów i pozostać na „szczęśliwej ścieżce” przepływu biznesu.
Zamiast czekać, aż serwer dostarczy odpowiedź asynchronicznie przez push, zaimplementuj zaplanowany proces, który będzie wykonywany co X minut i będzie wyszukiwał żądania oczekujące dłużej niż Y minut. W przypadku tych żądań Twoja aplikacja wyśle synchroniczne żądanie do serwera w celu uzyskania danych odpowiedzi. Jeśli pierwotne żądanie zostało zakończone do przetworzenia przez serwer, będzie mógł wysłać Ci wynik. W takim przypadku Twoja aplikacja będzie kontynuować przepływ biznesowy tak samo, jak w przypadku odebrania danych przez webhooka. W przeciwnym razie pierwotne żądanie jest nadal przetwarzane przez serwer i nie ma potrzeby odesłania. Musisz tylko dalej czekać.
Wdrażając ten mechanizm można było automatycznie rozwiązać nieoczekiwane problemy związane z komunikacją asynchroniczną i tym samym uniknąć awarii przepływu lub ręcznie interweniować w celu przywrócenia danych z serwera.
Zauważyć! Nie wszystkie usługi, z którymi będziesz się integrować, będą miały możliwość dostarczania punktów końcowych do sondowania oprócz oferowanej przez nie komunikacji asynchronicznej. Jeśli nie mają, warto zapytać, czy można to zrobić w przyszłości.
To prawda, że to rozwiązanie wiąże się z obciążeniem wydajności, ALE przy wyborze odpowiednich interwałów będzie ono płynne, a korzyści, jakie zapewnia w przypadku awarii, są bezcenne.
Unikaj blokady dostawcy
Często zdarza się, że aplikacja korzysta z zewnętrznych dostawców, aby osiągnąć swoje cele biznesowe. Nie jest możliwe, aby każda organizacja opracowała każdy element logiki we własnym zakresie, zwłaszcza jeśli logika jest złożona i odległa od głównej działalności organizacji. Dlatego prawdopodobnie będziesz wolał zintegrować się z dostawcą, który będzie w stanie dostarczyć część logiki potrzebnej do ukończenia przepływu biznesowego.
Więc po badaniach i POC znalazłeś odpowiedniego dostawcę dla swoich potrzeb.
Integracja przebiegła sprawnie i… VOILA! Twoja firma działa płynnie i obsługuje setki tysięcy klientów dziennie.
Teraz pozwól, że zadam ci pytanie. Co się stanie, jeśli pewnego dnia ten zewnętrzny dostawca przestanie działać? co się stanie, jeśli zbankrutuje? Jeśli doszło do włamania i usługa nie będzie dostępna przez dłuższy czas? A może po prostu nie będziesz zadowolony z wydajności dostawcy, a może z tego, jak jest zarządzany, ponieważ co miesiąc pojawiają się nowe przełomowe zmiany, które powodują, że Twój zespół programistów pracuje przez całą dobę, aby dostosować integrację?
Szczególnie w przypadku głównych i wrażliwych przepływów biznesowych ten scenariusz jest krytyczny i może sparaliżować część Twojej organizacji i mieć ogromny wpływ na Twoich klientów.
Wracając do procesu automatycznego zatwierdzania dokumentów klienta zarządzanego przez mój zespół w Payoneer, w ramach tego procesu korzystamy z zewnętrznych dostawców w celu wyodrębnienia odpowiedniego tekstu z dokumentu i jego przetworzenia. Zdecydowaliśmy się na integrację z 2 dostawcami, którzy zapewniają nam ten wynik i mają między nimi pierwszeństwo, więc jeśli jeden zawiedzie lub ma wiele fałszywych alarmów, możemy przejść do drugiego bez przestojów w przepływie biznesowym. To oczywiście daje nam również pewność, że jeśli coś się stanie jednemu z nich, zawsze mamy innego, na którym możemy polegać.
Oczywiście nie zawsze musisz tworzyć tę kopię zapasową. To kwestia stosunku jakości do ceny i tego, jak bardzo krytyczny jest przepływ biznesowy, z którym masz do czynienia.
Wzór wyłącznika
W przeciwieństwie do wzorca ponawiania prób, który ma rozwiązywać przejściowe awarie, wzorzec przerwania obwodu ma zapobiegać wywoływaniu przez aplikację operacji, które mają duże prawdopodobieństwo niepowodzenia.
Myślisz, że używasz zewnętrznego interfejsu API REST, który obecnie ma błąd, który powoduje niepowodzenie wszystkich żądań. Załóżmy, że ETA naprawy to 4 godziny.
W przypadku wzorca ponawiania aplikacja będzie wielokrotnie próbowała wywołać żądanie, które ostatecznie zakończy się niepowodzeniem. Te bezużyteczne ponowne próby wpłyną na wydajność aplikacji i strony trzeciej.
Zamiast tego wzorzec przerwania obwodu umożliwi szybkie awarie aplikacji i próbę stopniowego odzyskania sprawności, gdy problem zostanie rozwiązany. W ten sposób unika się zgłaszania błędów i degradacji wydajności.
Jak to działa?
Mechanizm działa jako proxy dla operacji, którą chcesz wykonać.
Ma 3 stany:
- Zamknięta — oznacza, że operacja może być wykonywana regularnie.
W tym stanie zliczana jest liczba awarii. Jeśli liczba awarii przekroczy określony próg w określonym przedziale czasu, mechanizm przejdzie do stanu otwartego. - Otwarty - Oznacza to, że mechanizm uniemożliwi wykonanie operacji, ponieważ doświadczył wielu niepowodzeń i dlatego woli szybką awarię bez próby jej wykonania. W tym stanie mamy timeout. Po osiągnięciu, mechanizm przechodzi do stanu półotwartego.
- Półotwarte — oznacza, że ograniczona liczba żądań wykonania operacji jest dozwolona i przekazywana. Ten stan służy do sprawdzania, czy problem został rozwiązany. Jeśli ta ograniczona liczba operacji jest wykonywana pomyślnie, mechanizm zakłada, że problem został rozwiązany i przełącza stan mechanizmu na zamknięty. W przeciwnym razie wraca do Open, ponieważ problem nadal istnieje.
Dedykowane punkty końcowe do łatwej i wydajnej interwencji ręcznej
Powiedziawszy to wszystko, czasami nie jesteśmy przygotowani na zautomatyzowane rozwiązanie dla każdego przypadku, który może nadejść. Czasami nasze zautomatyzowane rozwiązanie pojawi się dopiero po tym, jak po raz pierwszy zetknęliśmy się ze scenariuszem i zrozumieliśmy, jak rozwiązać go za pomocą automatyzacji. Dlatego w takich przypadkach ręczna interwencja jest nieunikniona. W większości przypadków te ręczne interwencje wymagają przejścia przez „wnętrzności” usługi, takie jak: bezpośrednia zmiana wartości za pośrednictwem bazy danych lub ręczna restrukturyzacja wiadomości i wysłanie jej bezpośrednio przez brokera wiadomości.
Również tutaj możemy być wstępnie przygotowani, aby ta ręczna interwencja była łatwa do opanowania, bezpieczna i wydajna. Po prostu stwórz wewnętrzne API w swojej aplikacji, które umożliwi te typowe operacje ręczne.
Na przykład utwórz punkt końcowy, który pozwoli anulować proces zarządzany przez aplikację. Lub stwórz punkt końcowy, który umożliwi ponowne opublikowanie wyniku starego procesu, aby inny system mógł go ponownie wykorzystać, jeśli został utracony lub nigdy nie został uruchomiony.
Tworząc ten wewnętrzny interfejs API, chociaż wymaga to, aby ktoś go uruchomił, ma wiele zalet:
- Operacja jest zarządzana przez aplikację. Aplikacja jest właścicielem zmiany i jest tego świadoma.
- Operacja została przetestowana przez QA, ponieważ była częścią rozwoju. W ten sposób zmniejszasz szanse na błędy
- czasami operacja nie jest tak prosta, jak tylko aktualizacja wartości w DB. Czasami trzeba wykonać również działania poboczne. Wszystkimi tymi działaniami pobocznymi można zarządzać w ramach wewnętrznego interfejsu API w celu łatwego użytkowania.
- Oszczędność czasu
Podsumowując
Twoja aplikacja MUSI być zawsze przygotowana na najgorsze!
Im więcej Twoja aplikacja będzie wiedzieć, jak samodzielnie odzyskać i mieć rozwiązania awaryjne, tym bardziej wzrośnie jej wydajność, wpływ na doświadczenie klienta będzie niski, tym szybciej będziesz w stanie przezwyciężyć awarie i zaoszczędzić cenny czas i pieniądze dla Twojej organizacji.
Podczas projektowania aplikacji/funkcji musisz zwrócić na to uwagę. Musisz zadać odpowiednie pytania i zidentyfikować słabe punkty, aby przygotować odpowiednią kurację i uniknąć przykrych niespodzianek w produkcji.