Udostępnij za pośrednictwem


Komunikacja w ramach architektury mikrousługi

Napiwek

Ta zawartość jest fragmentem książki eBook, architektury mikrousług platformy .NET dla konteneryzowanych aplikacji platformy .NET dostępnych na platformie .NET Docs lub jako bezpłatnego pliku PDF, który można odczytać w trybie offline.

.NET Microservices Architecture for Containerized .NET Applications eBook cover thumbnail.

W aplikacji monolitycznej działającej w jednym procesie składniki wywołują się nawzajem przy użyciu metody lub wywołań funkcji na poziomie języka. Mogą one być silnie powiązane, jeśli tworzysz obiekty z kodem (na przykład new ClassName()), lub można je wywołać w sposób oddzielony, jeśli używasz wstrzykiwania zależności, odwołując się do abstrakcji, a nie do konkretnych wystąpień obiektów. Tak czy inaczej obiekty są uruchamiane w ramach tego samego procesu. Największym wyzwaniem podczas zmiany aplikacji monolitycznej na aplikację opartą na mikrousługach jest zmiana mechanizmu komunikacji. Bezpośrednia konwersja wywołań metody w procesie do wywołań RPC do usług spowoduje rozmowę i nie wydajną komunikację, która nie będzie działać dobrze w środowiskach rozproszonych. Wyzwania związane z projektowaniem systemu rozproszonego są wystarczająco dobrze znane, że istnieje nawet kanon znany jako Fallacies rozproszonego przetwarzania , który wymienia założenia, które deweloperzy często robią podczas przechodzenia z monolitycznych do rozproszonych projektów.

Nie ma jednego rozwiązania, ale kilku. Jedno rozwiązanie polega na izolowaniu mikrousług biznesowych tak bardzo, jak to możliwe. Następnie należy użyć komunikacji asynchronicznej między wewnętrznymi mikrousługami i zastąpić precyzyjną komunikację typową w komunikacji wewnątrzprocesowej między obiektami z grubszą komunikacją. Można to zrobić, grupując wywołania i zwracając dane, które agregują wyniki wielu wywołań wewnętrznych, do klienta.

Aplikacja oparta na mikrousługach to rozproszony system działający na wielu procesach lub usługach, zwykle nawet na wielu serwerach lub hostach. Każde wystąpienie usługi jest zazwyczaj procesem. W związku z tym usługi muszą korzystać z protokołu komunikacyjnego między procesami, takiego jak HTTP, AMQP lub protokół binarny, taki jak TCP, w zależności od charakteru każdej usługi.

Społeczność mikrousług promuje filozofię "inteligentnych punktów końcowych i głupich potoków". To hasło zachęca do projektowania, który jest tak oddzielony, jak to możliwe między mikrousługami i tak spójny, jak to możliwe w ramach jednej mikrousługi. Jak wyjaśniono wcześniej, każda mikrousługa jest właścicielem własnych danych i własnej logiki domeny. Jednak mikrousługi tworzące kompleksową aplikację są zwykle po prostu choreografią przy użyciu komunikacji REST, a nie złożonych protokołów, takich jak WS-* i elastyczna komunikacja sterowana zdarzeniami zamiast scentralizowanych koordynatorów procesów biznesowych.

Dwa najczęściej używane protokoły to żądanie/odpowiedź HTTP z interfejsami API zasobów (w przypadku wykonywania zapytań przede wszystkim) i uproszczone komunikaty asynchroniczne podczas komunikowania aktualizacji w wielu mikrousługach. Bardziej szczegółowo opisano je w poniższych sekcjach.

Typy komunikacji

Klient i usługi mogą komunikować się za pośrednictwem wielu różnych typów komunikacji, z których każdy jest przeznaczony dla innego scenariusza i celów. Początkowo tego typu komunikację można sklasyfikować na dwóch osiach.

Pierwsza oś określa, czy protokół jest synchroniczny lub asynchroniczny:

  • Protokół synchroniczny. HTTP to protokół synchroniczny. Klient wysyła żądanie i czeka na odpowiedź z usługi. Jest to niezależne od wykonywania kodu klienta, które może być synchroniczne (wątek jest zablokowany) lub asynchroniczne (wątek nie jest blokowany, a odpowiedź ostatecznie osiągnie wywołanie zwrotne). Ważnym punktem jest to, że protokół (HTTP/HTTPS) jest synchroniczny, a kod klienta może kontynuować swoje zadanie tylko wtedy, gdy odbiera odpowiedź serwera HTTP.

  • Protokół asynchroniczny. Inne protokoły, takie jak AMQP (protokół obsługiwany przez wiele systemów operacyjnych i środowisk w chmurze), używają komunikatów asynchronicznych. Kod klienta lub nadawca wiadomości zwykle nie czeka na odpowiedź. Po prostu wysyła komunikat jako podczas wysyłania komunikatu do kolejki RabbitMQ lub innego brokera komunikatów.

Druga oś określa, czy komunikacja ma jeden odbiornik lub wiele odbiorników:

  • Pojedynczy odbiornik. Każde żądanie musi być przetwarzane przez dokładnie jednego odbiornika lub usługę. Przykładem tej komunikacji jest wzorzec polecenia.

  • Wiele odbiorników. Każde żądanie może być przetwarzane przez zero do wielu odbiorników. Ten typ komunikacji musi być asynchroniczny. Przykładem jest mechanizm publikowania/subskrybowania używany we wzorcach, takich jak architektura sterowana zdarzeniami. Jest to oparte na interfejsie magistrali zdarzeń lub brokera komunikatów podczas propagacji aktualizacji danych między wieloma mikrousługami za pośrednictwem zdarzeń; Jest on zwykle implementowany za pośrednictwem magistrali usług lub podobnego artefaktu, takiego jak Usługa Azure Service Bus , przy użyciu tematów i subskrypcji.

Aplikacja oparta na mikrousługach często używa kombinacji tych stylów komunikacji. Najczęstszym typem jest komunikacja z jednym odbiornikiem za pomocą protokołu synchronicznego, takiego jak HTTP/HTTPS podczas wywoływania regularnej usługi HTTP internetowego interfejsu API. Mikrousługi zwykle używają protokołów obsługi komunikatów na potrzeby asynchronicznej komunikacji między mikrousługami.

Te osie są dobre do poznania, więc masz jasność co do możliwych mechanizmów komunikacji, ale nie są to ważne kwestie związane z tworzeniem mikrousług. Ani asynchroniczny charakter wykonywania wątku klienta, ani asynchroniczny charakter wybranego protokołu nie są ważnymi punktami podczas integrowania mikrousług. Ważne jest , aby można było asynchronicznie zintegrować mikrousługi przy zachowaniu niezależności mikrousług, jak wyjaśniono w poniższej sekcji.

Integracja mikrousługi asynchronicznej wymusza autonomię mikrousługi

Jak wspomniano, ważnym punktem tworzenia aplikacji opartej na mikrousługach jest sposób integrowania mikrousług. W idealnym przypadku należy spróbować zminimalizować komunikację między wewnętrznymi mikrousługami. Tym mniejsza jest komunikacja między mikrousługami, tym lepiej. Jednak w wielu przypadkach trzeba będzie w jakiś sposób zintegrować mikrousługi. W tym celu reguła krytyczna polega na tym, że komunikacja między mikrousługami powinna być asynchroniczna. Nie oznacza to, że musisz użyć określonego protokołu (na przykład asynchronicznego przesyłania komunikatów w porównaniu z synchronicznym protokołem HTTP). Oznacza to tylko, że komunikacja między mikrousługami powinna być wykonywana tylko przez propagowanie danych asynchronicznie, ale nie należy zależeć od innych wewnętrznych mikrousług w ramach operacji żądania HTTP/odpowiedzi początkowej usługi.

Jeśli to możliwe, nigdy nie zależy od synchronicznej komunikacji (żądania/odpowiedzi) między wieloma mikrousługami, nawet w przypadku zapytań. Celem każdej mikrousługi jest autonomiczne i dostępne dla konsumenta klienta, nawet jeśli inne usługi, które są częścią aplikacji kompleksowej, nie działają lub są w złej kondycji. Jeśli uważasz, że musisz wykonać wywołanie z jednej mikrousługi do innych mikrousług (takich jak wykonywanie żądania HTTP dla zapytania o dane), aby móc dostarczyć odpowiedź na aplikację kliencką, masz architekturę, która nie będzie odporna, gdy niektóre mikrousługi kończą się niepowodzeniem.

Ponadto posiadanie zależności HTTP między mikrousługami, na przykład podczas tworzenia długich cykli żądań/odpowiedzi za pomocą łańcuchów żądań HTTP, jak pokazano w pierwszej części rysunku 4–15, nie tylko sprawia, że mikrousługi nie są autonomiczne, ale także ich wydajność ma wpływ na jedną z usług w tym łańcuchu.

Im więcej dodajesz synchroniczne zależności między mikrousługami, takimi jak żądania zapytań, tym gorzej jest ogólny czas odpowiedzi dla aplikacji klienckich.

Diagram showing three types of communications across microservices.

Rysunek 4–15. Wzorce i wzorce w komunikacji między mikrousługami

Jak pokazano na powyższym diagramie, w synchronicznej komunikacji "łańcuch" żądań jest tworzony między mikrousługami podczas obsługi żądania klienta. Jest to antywzór. W przypadku asynchronicznej komunikacji mikrousługi używają komunikatów asynchronicznych lub sondowania http do komunikowania się z innymi mikrousługami, ale żądanie klienta jest obsługiwane od razu.

Jeśli mikrousługa musi zgłosić dodatkową akcję w innej mikrousłudze, jeśli to możliwe, nie wykonaj tej akcji synchronicznie i w ramach oryginalnej operacji mikrousługi i odpowiedzi. Zamiast tego należy to zrobić asynchronicznie (przy użyciu asynchronicznych komunikatów lub zdarzeń integracji, kolejek itp.). Jednak jak najwięcej, nie należy synchronicznie wywoływać akcji w ramach oryginalnego synchronicznego żądania i operacji odpowiedzi.

I wreszcie (i w tym miejscu pojawiają się większość problemów podczas tworzenia mikrousług), jeśli początkowa mikrousługi potrzebuje danych, które są pierwotnie własnością innych mikrousług, nie polegaj na tworzeniu synchronicznych żądań dla tych danych. Zamiast tego należy replikować lub propagować te dane (tylko potrzebne atrybuty) do bazy danych początkowej usługi przy użyciu spójności ostatecznej (zazwyczaj przy użyciu zdarzeń integracji, jak wyjaśniono w kolejnych sekcjach).

Jak wspomniano wcześniej w sekcji Identyfikowanie granic modelu domeny dla każdej mikrousługi , duplikowanie niektórych danych między kilkoma mikrousługami nie jest niepoprawnym projektem — wręcz przeciwnie, podczas wykonywania tego działania można przetłumaczyć dane na określony język lub terminy tej dodatkowej domeny lub ograniczonego kontekstu. Na przykład w aplikacji eShopOnContainers masz mikrousługę o nazwie identity-api , która jest odpowiedzialna za większość danych użytkownika z jednostką o nazwie User. Jeśli jednak musisz przechowywać dane o użytkowniku Ordering w ramach mikrousługi, zapisz je jako inną jednostkę o nazwie Buyer. Jednostka Buyer współudzieli tę samą tożsamość z oryginalną User jednostką, ale może mieć tylko kilka atrybutów wymaganych przez domenę Ordering , a nie cały profil użytkownika.

Aby zapewnić spójność ostateczną, możesz użyć dowolnego protokołu do komunikowania i propagowania danych asynchronicznie między mikrousługami. Jak wspomniano, możesz użyć zdarzeń integracji przy użyciu magistrali zdarzeń lub brokera komunikatów, a nawet użyć protokołu HTTP, sondując inne usługi. To nie ma znaczenia. Ważną regułą jest nie tworzenie synchronicznych zależności między mikrousługami.

W poniższych sekcjach opisano wiele stylów komunikacji, które można rozważyć przy użyciu w aplikacji opartej na mikrousługach.

Style komunikacji

Istnieje wiele protokołów i opcji, których można użyć do komunikacji, w zależności od typu komunikacji, którego chcesz użyć. Jeśli używasz synchronicznego mechanizmu komunikacji żądań/odpowiedzi, protokoły, takie jak metody HTTP i REST, są najbardziej typowe, zwłaszcza w przypadku publikowania usług spoza hosta platformy Docker lub klastra mikrousług. Jeśli komunikujesz się wewnętrznie między usługami (w ramach hosta platformy Docker lub klastra mikrousług), możesz również użyć mechanizmów komunikacji formatu binarnego (takich jak WCF przy użyciu formatu TCP i formatu binarnego). Alternatywnie można użyć asynchronicznych mechanizmów komunikacji opartych na komunikatach, takich jak AMQP.

Istnieje również wiele formatów komunikatów, takich jak JSON lub XML, a nawet formaty binarne, które mogą być bardziej wydajne. Jeśli wybrany format binarny nie jest standardowy, prawdopodobnie nie jest dobrym pomysłem, aby publicznie opublikować usługi przy użyciu tego formatu. Możesz użyć niestandardowego formatu komunikacji wewnętrznej między mikrousługami. Można to zrobić podczas komunikacji między mikrousługami w ramach hosta platformy Docker lub klastra mikrousług (na przykład orkiestratorów platformy Docker) lub w przypadku zastrzeżonych aplikacji klienckich, które komunikują się z mikrousługami.

Komunikacja żądań/odpowiedzi z protokołem HTTP i rest

Gdy klient używa komunikacji żądania/odpowiedzi, wysyła żądanie do usługi, a następnie usługa przetwarza żądanie i wysyła odpowiedź. Komunikacja żądań/odpowiedzi jest szczególnie odpowiednia do wykonywania zapytań o dane dla interfejsu użytkownika w czasie rzeczywistym (interfejs użytkownika na żywo) z aplikacji klienckich. W związku z tym w architekturze mikrousług prawdopodobnie użyjesz tego mechanizmu komunikacyjnego dla większości zapytań, jak pokazano na rysunku 4–16.

Diagram showing request/response comms for live queries and updates.

Rysunek 4–16. Korzystanie z komunikacji żądania HTTP/odpowiedzi (synchronicznej lub asynchronicznej)

Gdy klient korzysta z komunikacji żądań/odpowiedzi, zakłada, że odpowiedź pojawi się w krótkim czasie, zazwyczaj mniej niż sekunda lub kilka sekund. W przypadku opóźnionych odpowiedzi należy zaimplementować asynchroniczną komunikację opartą na wzorcach obsługi komunikatów i technologiach obsługi komunikatów, co jest innym podejściem, które objaśniamy w następnej sekcji.

Popularnym stylem architektury komunikacji żądań/odpowiedzi jest REST. Takie podejście jest oparte na protokole HTTP, ściśle powiązanym z protokołem HTTP, obejmującym czasowniki HTTP , takie jak GET, POST i PUT. Rest to najczęściej używane podejście do komunikacji architektonicznej podczas tworzenia usług. Usługi REST można zaimplementować podczas tworzenia ASP.NET podstawowych usług internetowego interfejsu API.

W przypadku używania usług REST HTTP jako języka definicji interfejsu jest dodatkowa wartość. Jeśli na przykład używasz metadanych struktury Swagger do opisywania interfejsu API usługi, możesz użyć narzędzi, które generują wycinki klienta, które mogą bezpośrednio odnajdywać i korzystać z usług.

Dodatkowe zasoby

Wypychanie i komunikacja w czasie rzeczywistym na podstawie protokołu HTTP

Inną możliwością (zwykle w różnych celach niż REST) jest komunikacja w czasie rzeczywistym i jednorazowa z platformami wyższego poziomu, takimi jak ASP.NET SignalR i protokoły, takie jak WebSocket.

Jak pokazano na rysunku 4–17, komunikacja HTTP w czasie rzeczywistym oznacza, że kod serwera wypycha zawartość do połączonych klientów, ponieważ dane staną się dostępne, zamiast czekać na klienta na żądanie nowych danych.

Diagram showing push and real-time comms based on SignalR.

Rysunek 4–17. Komunikacja asynchroniczna "jeden do wielu" w czasie rzeczywistym

Usługa SignalR to dobry sposób na osiągnięcie komunikacji w czasie rzeczywistym na potrzeby wypychania zawartości do klientów z serwera zaplecza. Ponieważ komunikacja jest w czasie rzeczywistym, aplikacje klienckie pokazują zmiany niemal natychmiast. Jest to zwykle obsługiwane przez protokół, taki jak WebSockets, przy użyciu wielu połączeń protokołu WebSocket (po jednym na klienta). Typowym przykładem jest to, że usługa komunikuje się ze zmianą wyniku gry sportowej na wiele aplikacji internetowych klientów jednocześnie.