Uwolnienie się od monolitu

I jak ewoluowaliśmy dzięki mikrousługom

(19 lipca 2018 r.)

Monolitowe małpy z kodem rządzącym

W Conio wszystko zaczęło się od tego, co powszechnie nazywa się „monolitem”. Oznacza to, że jedna baza kodu zawierająca wszystko z pełnej aplikacji: jej komponenty frontendu, komponenty backendu, usługi API, zadania w tle; do diabła, nawet skrypty Devops. I na początku działało bardzo dobrze. Tylko kilku inżynierów oprogramowania, pracujących nad oddzielnymi obszarami bazy kodu (więc bardzo małe szanse na konflikty zmian kodu), łatwe do wdrożenia. Pełna koncentracja na pisaniu funkcjonalności aplikacji, bez martwienia się o wiele więcej. Jak podeszliśmy do wdrożenia? Ponieważ tylko kilku klientów beta było świadomych ciągłego postępu, zamknięcie usług na jakiś czas, wdrożenie pełnej bazy kodu nie było prawdziwym problemem (bez względu na to, jak małe lub duże były ogólne zmiany i czy obejmowały migracje baz danych), a następnie ponownie podnieść jakość usług.

Z pewnością satysfakcjonujące było obserwowanie, jak produkt kształtuje się od podstaw i cieszy się uznaniem klientów końcowych. Jednak doskonale wiedzieliśmy, że takie podejście nie pasuje do nowoczesnej firmy z branży Fintech.

Co wtedy?

W przypadku większości aplikacji klienci są dość tolerancyjni. Tak, Whatsapp może przestać działać i mieć awarię trwającą kilka godzin: zdecydowanie uciążliwe, ale nie postrzegany problem. To samo dotyczy Pokemon Go lub Twojej ulubionej aplikacji do gier. Jednak tak nie jest, gdy chodzi o pieniądze : nastrój się zmienia, jeśli nie możesz zalogować się na swoje konto bankowe lub nie możesz dokonywać operacji handlowych. Jeszcze gorzej jest w przypadku aplikacji kryptowalutowych: większość ludzi pamięta niesławne błędy z przeszłości, a gdy nie są w stanie uzyskać dostępu do swoich funduszy kryptowalutowych nawet przez krótki czas, pojawiają się spekulacje. To uczciwe. To twoje pieniądze i powinieneś mieć mały problem lub żaden problem, gdy chcesz go użyć.

Powyższy Monolit nie nadaje się do takiego scenariusza: każda zmiana bazy kodu w środowisku produkcyjnym wymagałaby pełnego wdrożenia, z powiązanymi przestojami. Każdego dnia pracujemy nad ulepszaniem naszych usług, naprawiając błędy, czyniąc nasz interfejs jeszcze bardziej przyjaznym, usuwając stare funkcjonalności i dodając nowe, które mają lepsze zastosowanie. Często codziennie publikujemy te aktualizacje, aby nasi klienci mogli odnieść natychmiastowe korzyści, i staramy się nie mieć żadnego wpływu na ich wrażenia. Oznacza to, że każda formuła, którą wymyślimy za kulisami, musi być niewidoczna dla świata zewnętrznego (przynajmniej w większości przypadków). Dlatego odeszliśmy od Monolitu i wybraliśmy tzw. „Architekturę mikrousług”.

Ewolucja poprzez mikrousługi

Ogromny, ściśle sklejony pojedynczy kod jest teraz rozłożony na mniejsze części, z których każda reprezentuje określoną usługę. Za każdym razem usługi komunikują się ze sobą synchronicznie za pośrednictwem standardowego protokołu HTTP i asynchronicznie za pośrednictwem kolejek (obsługiwanych na przykład przez RabbitMQ i Apache Kafka).

Interakcje w architekturze mikrousług

Rozpoczęcie dzielenia monolitu na mniejsze komponenty jest dość trudne, ale warto wysiłek. Pod względem militarnym jest to bardzo podobne do tego, co zrobił Juliusz Cezar, aby stabilnie rządzić dużym terytorium Galii: „ dziel i podbijaj ”.

1) Produkt można stale wdrażać. Aktualizacja kodu ma teraz zastosowanie tylko do mikrousługi: w większości przypadków można ją natychmiast wdrożyć w środowisku produkcyjnym i wydać bez wpływu na klienta.

2) Kod jest łatwiejszy w zarządzaniu. Z punktu widzenia organizacji firmy sytuacja zmienia się, gdy zespół 2 inżynierów oprogramowania staje się zespołem 10 inżynierów oprogramowania. Jest bardziej efektywny i ma mniej konfliktów kodu, gdy każdy członek zespołu jest odpowiedzialny za własną mikrousługę.

3) Kod jest łatwiejszy w utrzymaniu. Architektura mikrousług z natury wymaga definicji interfejsu do komunikacji ze światem zewnętrznym (czy to aplikacja frontendowa, czy inna usługa backendu) i jest całkowicie odizolowana od każdego innego punktu widzenia. Pozwala to na przejrzenie, przeprojektowanie lub nawet całkowite przepisanie od podstaw (nawet w innych językach, jeśli jest to wygodne) pojedynczych składników aplikacji bez wpływu na resztę.

4) Wydajność można zwiększyć. Każda mikrousługa może teraz używać swojego najbardziej odpowiedniego języka. Ciężkie kryptograficzne komponenty obliczeniowe można na przykład zoptymalizować w C, a usługi API w Pythonie i długotrwałe zadania w Go.

5) Ulepszona izolacja kodu i bezpieczeństwo. Każda mikrousługa może działać we własnym kontenerze Docker, zapewniając w ten sposób izolację uprawnień, segregację sieci i danych oraz, co ma ogromne znaczenie dla fazy wzrostu, ogromny potencjał skalowalności.

Czy zatem mikrousługi są odpowiedzią?

Oczywiście nie ma czegoś takiego jak darmowy lunch. Architektura mikrousług wiąże się również z własnym zestawem trudnych wyzwań:

1) Złożoność operacyjna. Inżynierowie DevOps są zdecydowanie potrzebni, aby uprościć zawiłości nowego procesu wdrażania.

2) Nadmiar sprzętu. Mikrousługi są często uruchamiane w kontenerach Docker; gdy tylko liczba mikrousług rośnie, coraz trudniej jest uruchomić pełną aplikację na tym samym sprzęcie, co wcześniej.

3) Narzut komunikacji wewnętrznej: każde żądanie może wymagać interakcji z jednym lub większą liczbą różnych mikrousługi przez sieć. Może to spowodować zwiększone opóźnienie i może podlegać tymczasowym awariom. Aby zaimplementować odporne usługi, a także poprawić skalowalność całego systemu, konieczne jest przeniesienie interakcji do asynchronicznego przesyłania wiadomości (np. Przy użyciu Apache Kafka i / lub RabbitMQ)

4) Konsekwencja ostateczna. To prawdopodobnie najtrudniejsze wyzwanie architektury mikrousług. Biorąc pod uwagę pojedynczą mikrousługę, możliwe jest tworzenie transakcji RDBMS w jej granicach . Niestety, częstym problemem w architekturach rozproszonych jest radzenie sobie z wieloma transakcjami, które nie mieszczą się w tych samych granicach. W rezultacie system może znaleźć się w nielegalnym i nieodwracalnym stanie. Aby złagodzić takie problemy, Conio przyjmuje różne strategie:

  1. Postępując zgodnie z praktykami projektowania opartego na domenie, należy rozłożyć domeny wyższego poziomu na subdomeny i ograniczyć je do indywidualnie ograniczonych kontekstów ; każdy ograniczony kontekst jest implementowany jako mikrousługa, w której stosowane są granice transakcji. Rozwiązuje to możliwość wystąpienia niespójności dla określonych subdomen.
  2. Zaimplementuj idempotentne asynchroniczne interakcje, które prędzej czy później rozwiążą niespójności.
  3. O ile to możliwe, unikaj wszelkich działań, które mogą obejmować wiele subdomen.

5) Złożone raportowanie. Ponieważ każda subdomena znajduje się w określonym, ograniczonym kontekście, złożone raporty, które obejmują wiele subdomen, mogą wymagać przeszukiwania danych z wielu źródeł danych: może to mieć negatywny wpływ zarówno na wyrazistość domen, jak i na skalowalność systemu. Tutaj w Conio przyjęliśmy architekturę CQRS do obsługi raportów z działalności zaplecza i analizy biznesowej.

6 ) System logowania. Każdy element w systemie rozproszonym przyczynia się do tworzenia dziennika całego systemu. Jednak konieczne jest wdrożenie narzędzi, które mogą tworzyć potrzebne połączenia między wszystkimi takimi oddzielnymi dziennikami, aby mieć ujednolicony dziennik dla każdej interakcji. Tutaj w Conio używamy stosu ELK (ElasticSearch, Logstash, Kibana) do przechowywania i przeszukiwania danych dziennika: każdy dziennik jest wzbogacony o niezbędne identyfikatory korelacji, które pozwalają na wspomniany wyżej ujednolicony dziennik.

Nigdy nie zatrzymuj ewolucji

Nasze podejście? Rozłożenie początkowej pojedynczej bazy kodu musi być postrzegane jako zadanie długoterminowe , z ciągłymi udoskonaleniami. W Conio zajęło nam to kilka lat, kiedy krok po kroku przenieśliśmy się z 1 ogromnej bazy kodów do ponad 100 mikrousług . Doszliśmy do punktu, w którym jesteśmy dumni z wyników, ale jednocześnie wciąż odkrywamy. Istnieje wiele możliwych nowych optymalizacji: przejście z Docker Swarm do Kubernetes? Migracja usług backend-for-frontend do bezserwerowych funkcji lambda? Przełączasz się na przepływ operacji pełnego ciągłego wdrażania? Możliwości są nieograniczone.

Poruszyliśmy tutaj wiele tematów i technologii. W kolejnych artykułach podamy więcej szczegółów na temat naszych ustaleń i postępów. Jeśli chcesz, napisz komentarz i opowiedz nam o swoich doświadczeniach.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *