Share via


Kommunikation i en mikrotjänstarkitektur

Dricks

Det här innehållet är ett utdrag från eBook, .NET Microservices Architecture for Containerized .NET Applications, tillgängligt på .NET Docs eller som en kostnadsfri nedladdningsbar PDF som kan läsas offline.

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

I ett monolitiskt program som körs på en enda process anropar komponenter varandra med hjälp av metod- eller funktionsanrop på språknivå. Dessa kan vara starkt kopplade om du skapar objekt med kod (till exempel new ClassName()), eller kan anropas på ett frikopplat sätt om du använder beroendeinmatning genom att referera till abstraktioner i stället för konkreta objektinstanser. Hur som helst körs objekten i samma process. Den största utmaningen när du byter från ett monolitiskt program till ett mikrotjänstbaserat program är att ändra kommunikationsmekanismen. En direkt konvertering från processmetodanrop till RPC-anrop till tjänster orsakar en pratsam och inte effektiv kommunikation som inte fungerar bra i distribuerade miljöer. Utmaningarna med att utforma distribuerade system på rätt sätt är tillräckligt kända för att det till och med finns en kanon som kallas fallacies för distribuerad databehandling som listar antaganden som utvecklare ofta gör när de går från monolitisk till distribuerad design.

Det finns inte en lösning, utan flera. En lösning innebär att isolera affärsmikrotjänster så mycket som möjligt. Sedan använder du asynkron kommunikation mellan de interna mikrotjänsterna och ersätter detaljerad kommunikation som är typisk för intraprocesskommunikation mellan objekt med grovkornig kommunikation. Du kan göra detta genom att gruppera anrop och genom att returnera data som aggregerar resultatet av flera interna anrop till klienten.

Ett mikrotjänstbaserat program är ett distribuerat system som körs på flera processer eller tjänster, vanligtvis även över flera servrar eller värdar. Varje tjänstinstans är vanligtvis en process. Därför måste tjänsterna interagera med ett kommunikationsprotokoll mellan processer, till exempel HTTP, AMQP eller ett binärt protokoll som TCP, beroende på vilken typ av tjänst som används.

Mikrotjänstcommunityn främjar filosofin om "smarta slutpunkter och dumma rör". Den här sloganen uppmuntrar en design som är så frikopplad som möjligt mellan mikrotjänster och så sammanhängande som möjligt inom en enda mikrotjänst. Som vi förklarade tidigare äger varje mikrotjänst sina egna data och sin egen domänlogik. Men mikrotjänsterna som utgör ett program från slutpunkt till slutpunkt koreograferas vanligtvis helt enkelt med hjälp av REST-kommunikation snarare än komplexa protokoll som WS-* och flexibel händelsedriven kommunikation i stället för centraliserade affärsprocessorkestrerare.

De två protokoll som används ofta är HTTP-begäran/-svar med resurs-API:er (när du frågar mest av allt) och enkla asynkrona meddelanden när du kommunicerar uppdateringar över flera mikrotjänster. Dessa beskrivs mer detaljerat i följande avsnitt.

Kommunikationstyper

Klient och tjänster kan kommunicera via många olika typer av kommunikation, där var och en riktar in sig på olika scenarion och mål. Inledningsvis kan dessa typer av kommunikation klassificeras i två axlar.

Den första axeln definierar om protokollet är synkront eller asynkront:

  • Synkront protokoll. HTTP är ett synkront protokoll. Klienten skickar en begäran och väntar på ett svar från tjänsten. Det är oberoende av klientkodkörningen som kan vara synkron (tråden blockeras) eller asynkron (tråden blockeras inte och svaret kommer att nå ett motringning så småningom). Det viktiga här är att protokollet (HTTP/HTTPS) är synkront och att klientkoden bara kan fortsätta sin uppgift när den tar emot HTTP-serversvaret.

  • Asynkront protokoll. Andra protokoll som AMQP (ett protokoll som stöds av många operativsystem och molnmiljöer) använder asynkrona meddelanden. Klientkoden eller meddelandesändaren väntar vanligtvis inte på ett svar. Det skickar bara meddelandet som när du skickar ett meddelande till en RabbitMQ-kö eller någon annan meddelandekö.

Den andra axeln definierar om kommunikationen har en enda mottagare eller flera mottagare:

  • Enskild mottagare. Varje begäran måste bearbetas av exakt en mottagare eller tjänst. Ett exempel på den här kommunikationen är kommandomönstret.

  • Flera mottagare. Varje begäran kan bearbetas med noll till flera mottagare. Den här typen av kommunikation måste vara asynkron. Ett exempel är mekanismen publicera/prenumerera som används i mönster som händelsedriven arkitektur. Detta baseras på ett event-bus-gränssnitt eller en meddelandekö när datauppdateringar sprids mellan flera mikrotjänster via händelser. Den implementeras vanligtvis via en servicebuss eller liknande artefakt som Azure Service Bus med hjälp av ämnen och prenumerationer.

Ett mikrotjänstbaserat program använder ofta en kombination av dessa kommunikationsformat. Den vanligaste typen är kommunikation med en mottagare med ett synkront protokoll som HTTP/HTTPS när en vanlig HTTP-tjänst för webb-API anropas. Mikrotjänster använder också vanligtvis meddelandeprotokoll för asynkron kommunikation mellan mikrotjänster.

Dessa axlar är bra att veta så du har klarhet om möjliga kommunikationsmekanismer, men de är inte de viktiga problemen när du skapar mikrotjänster. Varken klienttrådskörningens asynkrona karaktär eller det valda protokollets asynkrona karaktär är viktiga punkter när mikrotjänster integreras. Det viktiga är att kunna integrera dina mikrotjänster asynkront samtidigt som mikrotjänsternas oberoende bibehålls, enligt beskrivningen i följande avsnitt.

Asynkron mikrotjänstintegrering framtvingar mikrotjänstens autonomi

Som nämnts är den viktiga punkten när du skapar ett mikrotjänstbaserat program hur du integrerar dina mikrotjänster. Helst bör du försöka minimera kommunikationen mellan de interna mikrotjänsterna. Ju färre kommunikationer mellan mikrotjänster, desto bättre. Men i många fall måste du på något sätt integrera mikrotjänsterna. När du behöver göra det är den kritiska regeln här att kommunikationen mellan mikrotjänsterna ska vara asynkron. Det betyder inte att du måste använda ett specifikt protokoll (till exempel asynkrona meddelanden jämfört med synkron HTTP). Det innebär bara att kommunikationen mellan mikrotjänster endast ska göras genom att sprida data asynkront, men försök att inte vara beroende av andra interna mikrotjänster som en del av den första tjänstens HTTP-begäran/svar-åtgärd.

Om möjligt är du aldrig beroende av synkron kommunikation (begäran/svar) mellan flera mikrotjänster, inte ens för frågor. Målet med varje mikrotjänst är att vara autonom och tillgänglig för klientkonsumenten, även om de andra tjänsterna som ingår i programmet från slutpunkt till slutpunkt är inte felfria. Om du tror att du behöver göra ett anrop från en mikrotjänst till andra mikrotjänster (som att utföra en HTTP-begäran för en datafråga) för att kunna ge ett svar på ett klientprogram, har du en arkitektur som inte är motståndskraftig när vissa mikrotjänster misslyckas.

Att ha HTTP-beroenden mellan mikrotjänster, till exempel när du skapar långa begäran/svar-cykler med HTTP-begärandekedjor, vilket visas i den första delen av bild 4-15, gör inte bara dina mikrotjänster inte autonoma utan även deras prestanda påverkas så snart en av tjänsterna i den kedjan inte presterar bra.

Ju mer du lägger till synkrona beroenden mellan mikrotjänster, till exempel frågebegäranden, desto sämre blir den totala svarstiden för klientapparna.

Diagram showing three types of communications across microservices.

Bild 4-15. Antimönster och mönster i kommunikationen mellan mikrotjänster

Som du ser i diagrammet ovan skapas i synkron kommunikation en "kedja" med begäranden mellan mikrotjänster när klientbegäran hanteras. Det här är ett antimönster. I asynkrona kommunikationsmikrotjänster använder du asynkrona meddelanden eller http-avsökning för att kommunicera med andra mikrotjänster, men klientbegäran hanteras direkt.

Om din mikrotjänst behöver skapa ytterligare en åtgärd i en annan mikrotjänst ska du om möjligt inte utföra åtgärden synkront och som en del av den ursprungliga mikrotjänstbegäran och svarsåtgärden. Gör det i stället asynkront (med asynkrona meddelande- eller integreringshändelser, köer osv.). Men så mycket som möjligt anropar du inte åtgärden synkront som en del av den ursprungliga synkrona begäran och svarsåtgärden.

Och slutligen (och det är här de flesta problem uppstår när du skapar mikrotjänster), om din första mikrotjänst behöver data som ursprungligen ägs av andra mikrotjänster, förlitar du dig inte på att göra synkrona begäranden för dessa data. Replikera eller sprid i stället dessa data (endast de attribut du behöver) till den första tjänstens databas med hjälp av slutlig konsekvens (vanligtvis med hjälp av integrationshändelser, enligt beskrivningen i kommande avsnitt).

Som tidigare nämnts i avsnittet Identifiera domänmodellgränser för varje mikrotjänst är duplicering av vissa data över flera mikrotjänster inte en felaktig design, tvärtom, när du gör det kan du översätta data till det specifika språket eller villkoren för den ytterligare domänen eller begränsad kontext. I eShopOnContainers-programmet har du till exempel en mikrotjänst med namnet identity-api som ansvarar för de flesta av användarens data med en entitet med namnet User. Men när du behöver lagra data om användaren i Ordering mikrotjänsten lagrar du dem som en annan entitet med namnet Buyer. Entiteten Buyer delar samma identitet med den ursprungliga User entiteten, men den kanske bara har de få attribut som krävs av domänen Ordering och inte hela användarprofilen.

Du kan använda valfritt protokoll för att kommunicera och sprida data asynkront över mikrotjänster för att få slutlig konsekvens. Som nämnts kan du använda integreringshändelser med hjälp av en händelsebuss eller meddelandekö, eller så kan du till och med använda HTTP genom att avsöka de andra tjänsterna i stället. Det spelar ingen roll. Den viktiga regeln är att inte skapa synkrona beroenden mellan dina mikrotjänster.

I följande avsnitt beskrivs de flera kommunikationsformat som du kan överväga att använda i ett mikrotjänstbaserat program.

Kommunikationsformat

Det finns många protokoll och alternativ som du kan använda för kommunikation, beroende på vilken kommunikationstyp du vill använda. Om du använder en synkron mekanism för begäran/svar är protokoll som HTTP- och REST-metoder vanligast, särskilt om du publicerar dina tjänster utanför Docker-värden eller mikrotjänstklustret. Om du kommunicerar mellan tjänster internt (inom Docker-värden eller mikrotjänstklustret) kanske du också vill använda kommunikationsmekanismer för binärt format (till exempel WCF med TCP och binärt format). Du kan också använda asynkrona, meddelandebaserade kommunikationsmekanismer som AMQP.

Det finns också flera meddelandeformat som JSON eller XML, eller till och med binära format, vilket kan vara mer effektivt. Om ditt valda binära format inte är en standard är det förmodligen inte en bra idé att publicera dina tjänster offentligt med det formatet. Du kan använda ett icke-standardformat för intern kommunikation mellan dina mikrotjänster. Du kan göra detta när du kommunicerar mellan mikrotjänster i Docker-värden eller mikrotjänstklustret (till exempel Docker-orkestratorer) eller för proprietära klientprogram som kommunicerar med mikrotjänsterna.

Begäran/svarskommunikation med HTTP och REST

När en klient använder begäran/svar-kommunikation skickar den en begäran till en tjänst, sedan bearbetar tjänsten begäran och skickar tillbaka ett svar. Kommunikation mellan begäran och svar passar särskilt bra för att fråga efter data för ett realtidsgränssnitt (ett liveanvändargränssnitt) från klientappar. I en mikrotjänstarkitektur använder du därför förmodligen den här kommunikationsmekanismen för de flesta frågor, som du ser i bild 4–16.

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

Bild 4-16. Använda HTTP-begäran/svarskommunikation (synkron eller asynkron)

När en klient använder kommunikation med begäran/svar förutsätter det att svaret kommer att tas emot på kort tid, vanligtvis mindre än en sekund eller högst några sekunder. För fördröjda svar måste du implementera asynkron kommunikation baserat på meddelandemönster och meddelandetekniker, vilket är en annan metod som vi förklarar i nästa avsnitt.

Ett populärt arkitekturformat för kommunikation mellan begäran och svar är REST. Den här metoden baseras på, och nära kopplat till, HTTP-protokollet , som omfattar HTTP-verb som GET, POST och PUT. REST är den vanligaste metoden för arkitekturkommunikation när du skapar tjänster. Du kan implementera REST-tjänster när du utvecklar ASP.NET Core Web API-tjänster.

Det finns ytterligare värde när du använder HTTP REST-tjänster som gränssnittsdefinitionsspråk. Om du till exempel använder Swagger-metadata för att beskriva ditt tjänst-API kan du använda verktyg som genererar klientstubbar som kan identifiera och använda dina tjänster direkt.

Ytterligare resurser

Push- och realtidskommunikation baserat på HTTP

En annan möjlighet (vanligtvis för andra syften än REST) är en kommunikation i realtid och en-till-många-kommunikation med ramverk på högre nivå, till exempel ASP.NET SignalR och protokoll som WebSockets.

Som bild 4-17 visar innebär HTTP-kommunikation i realtid att du kan ha serverkod som push-överför innehåll till anslutna klienter när data blir tillgängliga, i stället för att servern väntar på att en klient ska begära nya data.

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

Bild 4-17. Asynkron meddelandekommunikation i realtid

SignalR är ett bra sätt att uppnå realtidskommunikation för att skicka innehåll till klienterna från en serverdelsserver. Eftersom kommunikationen sker i realtid visar klientappar ändringarna nästan omedelbart. Detta hanteras vanligtvis av ett protokoll, till exempel WebSockets, med många WebSockets-anslutningar (en per klient). Ett typiskt exempel är när en tjänst kommunicerar en ändring i poängen för ett sportspel till många klientwebbappar samtidigt.