Читать книгу Mikroserwisy w akcji - Группа авторов - Страница 36
Część 2
Projekt
3. Architektura aplikacji mikroserwisowej
3.4. Komunikacja
ОглавлениеKomunikacja jest podstawowym elementem aplikacji mikroserwisowej. Mikroserwisy komunikują się ze sobą, aby wykonać konkretne zadania. Kształt tworzonej aplikacji określają wybrane przez nas dla mikroserwisów sposoby wydawania poleceń i żądań wykonania działań w stosunku do innych mikroserwisów.
PORADA Komunikacja w sieci jest również głównym źródłem awaryjności w aplikacji mikroserwisowej. W rozdziale 6 omówimy techniki maksymalizacji niezawodności komunikacji między usługami.
Komunikacja nie jest niezależną warstwą architektoniczną, ale przenieśliśmy ją do osobnej części, ponieważ zaciera ona granicę między warstwami usług i platformy. Niektóre elementy, takie jak brokerzy komunikacji, są częścią warstwy platformy. Ale same usługi są odpowiedzialne za konstruowanie i wysyłanie komunikatów. Chcemy zbudować logikę w punktach końcowych przy prostocie kanałów (smart endpoints and dumb pipes).
W tym podrozdziale omówimy typowe wzorce komunikacji w mikroserwisach oraz ich wpływ na elastyczność i ewolucję aplikacji mikroserwisowej. Większość dojrzałych aplikacji mikroserwisowych łączy zarówno style interakcji synchronicznej, jak i asynchronicznej.
3.4.1. Kiedy używać komunikatów synchronicznych
Komunikaty synchroniczne są często pierwszym podejściem do projektowania, które przychodzi na myśl. Są dobrze dopasowane do scenariuszy, w których wyniki działania – albo potwierdzenie sukcesu, albo porażki – są wymagane przed przystąpieniem do kolejnego działania.
Na rysunku 3.10 pokazano wzorzec żądanie–odpowiedź dla komunikatów synchronicznych. Pierwsza usługa tworzy odpowiedni komunikat do współpracownika, który aplikacja wysyła za pomocą mechanizmu transportowego, takiego jak HTTP. Usługa docelowa odbiera ten komunikat i odpowiednio reaguje.
Rysunek 3.10. Synchroniczny cykl życia żądanie–odpowiedź między dwiema usługami komunikacyjnymi
WYBÓR KOMUNIKACJI
Wybór sposobu komunikacji – RESTful HTTP, biblioteki RPC lub czegoś innego – będzie miało wpływ na projektowanie naszych usług. Każdy sposób komunikacji ma inne właściwości związane z opóźnieniem, wsparciem języka i elastycznością. Na przykład gRPC zapewnia generowanie kontraktów API klienta/serwera za pomocą Protobufs, podczas gdy HTTP jest agnostyczny w zakresie kontekstu komunikatów. W całej aplikacji stosowanie jednej metody komunikacji synchronicznej zapewnia efekt skali; łatwiej jest to zrozumieć, monitorować i wspierać za pomocą narzędzi.
Ważna jest także separacja potrzeb między mikroserwisami. Wybrany przez nas mechanizm komunikacji powinien zostać odseparowany od logiki biznesowej usługi, która z kolei nie powinna wiedzieć o kodach statusu HTTP lub strumieniach odpowiedzi gRPC. W ten sposób łatwiej będzie wymieniać różne mechanizmy w przyszłości, jeśli potrzeby naszej aplikacji będą ewoluować.
WADY
Komunikacja synchroniczna ma ograniczenia:
■ tworzy ściślejsze powiązania między usługami, ponieważ muszą one być świadome usług z nimi współpracujących;
■ nie ma silnego modelu wspierającego wzorzec rozgłoszeniowy lub publish-subscribe, ograniczając naszą zdolność do wykonywania równoległych działań;
■ blokuje wykonywanie kodu podczas oczekiwania na odpowiedzi; w modelu serwera opartym na wątkach lub procesach może to spowodować zmniejszanie wydajności infrastruktury i wyzwalanie awarii kaskadowych;
■ nadużywanie komunikatów synchronicznych może tworzyć głębokie łańcuchy zależności, co zwiększa ogólną kruchość ścieżki wywołania.
3.4.2. Kiedy używać komunikatów asynchronicznych
Asynchroniczny styl przesyłania komunikatów jest bardziej elastyczny. Przez wprowadzenie zdarzeń można łatwo rozszerzyć system, aby sprostać nowym wymaganiom, ponieważ usługi nie muszą mieć wiedzy o ich klientach niższego szczebla. Nowe usługi mogą wykorzystywać istniejące zdarzenia bez zmiany istniejących usług.
WSKAZÓWKA Zdarzenia reprezentują zmiany stanu po fakcie. OrderCreated, OrderPlaced i OrderCanceled są przykładami zdarzeń, które może generować usługa obsługi zleceń SimpleBanku.
Ten styl umożliwia bardziej płynną ewolucję i tworzy luźniejsze powiązania między usługami. Kosztem tego jest jednak to, że asynchroniczne interakcje są trudniejsze do analizowania, ponieważ ogólne zachowanie systemu nie jest już jawnie zakodowane w liniowej sekwencji. Zachowanie systemu będzie coraz bardziej wynikające – w nieprzewidziany sposób z interakcji między usługami – wymagając inwestycji w monitorowanie, aby odpowiednio śledzić to, co się dzieje.
UWAGA Zdarzenia umożliwiają korzystanie z różnych stylów trwałości i zapytań, takich jak Event Sourcing oraz Command Query Responsibility Segregation (CQRS). Nie są one prerekwizytami dla mikroserwisów, ale mają pewną synergię z podejściem mikroserwisowym. Przeanalizujemy to w rozdziale 5.
Rysunek 3.11. Zdarzeniowa asynchroniczna komunikacja między usługami
Komunikacja asynchroniczna zwykle wymaga brokera komunikacji, niezależnego komponentu systemu, który odbiera zdarzenia i dystrybuuje je do ich odbiorców. Jest to czasami nazywane kręgosłupem zdarzeń, co wskazuje, jak centralnym elementem aplikacji staje się ten komponent (rys. 3.11). Narzędzia powszechnie używane jako brokerzy to Kafka, RabbitMQ i Redis. Semantyka tych narzędzi jest różna – Kafka specjalizuje się w wielostrumieniowym, odtwarzalnym magazynie zdarzeń, podczas gdy RabbitMQ zapewnia oprogramowanie pośredniczące do komunikacji na wyższych poziomach (oparte na protokole AMQP (https://www.amqp.org/)).
3.4.3. Wzorce komunikacji asynchronicznej
Przyjrzyjmy się dwóm najczęstszym wzorcom opartym na zdarzeniach: kolejce zadań i publish-subscribe. Spotkasz się z tymi wzorcami podczas projektowania mikroserwisów – najbardziej zaawansowane wzorce interakcji są zbudowane na jednym z tych dwóch prymitywów.
KOLEJKA ZADAŃ
W tym wzorcu komponenty pobierają zadania z kolejki i je wykonują (rys. 3.12). Zadanie powinno być przetwarzane tylko raz, bez względu na liczbę uruchomionych instancji komponentu. Ten wzorzec jest również znany jako „zwycięzca bierze wszystko”.
Rysunek 3.12. Kolejka zadań dystrybuuje je od 1 do n odbiorców
Nasza brama giełdy może działać w następujący sposób. Każde zlecenie utworzone przez usługę obsługi zleceń wywoła zdarzenie OrderCreated, które zostanie umieszczone w kolejce tak, aby zostało przekazane dalej przez usługę bramy giełdy. Ten wzorzec jest przydatny, gdy:
■ istnieje relacja 1: 1 między zdarzeniem a zadaniem do wykonania w odpowiedzi na to zdarzenie;
■ zadanie, które należy wykonać, jest złożone lub czasochłonne, dlatego należy je wykonywać poza zdarzeniem wyzwalającym.
Domyślnie to podejście nie wymaga zaawansowanego dostarczania zdarzeń. Dostępnych jest wiele bibliotek kolejek zadań, które korzystają z magazynów danych, takich jak Redis (Resque, Seler, Sidekiq) lub baz danych SQL.
PUBLISH-SUBSCRIBE
We wzorcu publish-subscribe usługi generują zdarzenia dla dowolnych odbiorców. Wszyscy odbiorcy, którzy odbiorą zdarzenie, otrzymują je na wyłączne posiadanie. Pod pewnymi względami jest to idealny wzorzec dla mikroserwisów – usługa może wysyłać dowolne zdarzenia do świata bez dbania o to, kto później będzie je przetwarzał (rys. 3.13).
Rysunek 3.13. Jak publish-subscribe wysyła zdarzenia do subskrybentów
Wyobraźmy sobie na przykład, że po złożeniu zlecenia chcemy śledzić działania wykonywane na niższym szczeblu. Możemy chcieć wysłać do klienta powiadomienie push, dostarczyć statystyki dotyczące zlecenia lub jakieś inne rekomendacje. Wszystkie te funkcjonalności mogą nasłuchiwać tego samego zdarzenia.
3.4.4. Lokalizowanie innych usług
Aby podsumować tę część, poświęćmy chwilę na zbadanie wykrywania usług. Aby usługi mogły się komunikować, muszą być w stanie się nawzajem wykrywać. Warstwa platformy powinna oferować taką możliwość.
Podstawowym podejściem do wykrywania usług jest stosowanie mechanizmów równoważenia obciążenia (rys. 3.14). Na przykład elastyczny moduł równoważenia obciążenia (elastic load balancer, ELB) w systemie AWS ma przypisaną nazwę DNS i zarządza sprawdzaniem działania węzłów podstawowych, opierając się na przynależności do grupy maszyn wirtualnych (grupa automatycznego skalowania w systemie AWS).
Rysunek 3.14. Wykrywanie usług za pomocą mechanizmów równoważenia obciążenia i nazw z DNS
To działa, ale nie obsługuje bardziej złożonych scenariuszy. A co, jeśli chcemy przekierować ruch do innej wersji kodu, aby włączyć wdrożenia kanarkowe (canary deployments) lub dark launch, lub jeśli chcemy przekierować ruch między różnymi centrami danych?
Bardziej wyrafinowanym podejściem jest skorzystanie z rejestru, takiego jak Consul (https://www.consul.io). Instancje usług zgłaszają się do rejestru zapewniającego interfejs API – za pośrednictwem DNS lub specjalnego mechanizmu rozpoznawania żądań dla tych usług. Na rysunku 3.15 pokazano to podejście.
Rysunek 3.15. Wykrywanie usług za pomocą rejestru usług jako źródła prawdy
Potrzeby związane z wykrywaniem usług będą zależeć od złożoności topologii wdrożonej aplikacji. Bardziej złożone wdrożenia, takie jak rozproszone geograficznie, wymagają bardziej niezawodnej architektury wykrywania usług10.
UWAGA Po wdrożeniu do Kubernetesa w rozdziale 9 dowiemy się o usługach, mechanizmie, którego Kubernetes używa do umożliwiania wykrywania.
10
Świetnym miejscem, aby rozpocząć odkrywanie różnych typy serwerów proxy i mechanizmów równoważenia obciążenia, jest bit.ly/2o86ShQ.