Share via


Uitdagingen en oplossingen voor gedistribueerd gegevensbeheer

Tip

Deze inhoud is een fragment uit het eBook, .NET Microservices Architecture for Containerized .NET Applications, beschikbaar op .NET Docs of als een gratis downloadbare PDF die offline kan worden gelezen.

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

Uitdaging 1: De grenzen van elke microservice definiëren

Het definiëren van microservicegrenzen is waarschijnlijk de eerste uitdaging die iedereen tegenkomt. Elke microservice moet een deel van uw toepassing zijn en elke microservice moet autonoom zijn met alle voordelen en uitdagingen die het overbrengt. Maar hoe identificeer je die grenzen?

Eerst moet u zich richten op de logische domeinmodellen en gerelateerde gegevens van de toepassing. Probeer losgekoppelde eilanden met gegevens en verschillende contexten binnen dezelfde toepassing te identificeren. Elke context kan een andere bedrijfstaal hebben (verschillende zakelijke termen). De contexten moeten onafhankelijk worden gedefinieerd en beheerd. De termen en entiteiten die in deze verschillende contexten worden gebruikt, lijken misschien op elkaar, maar misschien ontdekt u dat in een bepaalde context een bedrijfsconcept met een ander doel in een andere context wordt gebruikt en zelfs een andere naam heeft. Een gebruiker kan bijvoorbeeld worden aangeduid als een gebruiker in de identiteits- of lidmaatschapscontext, als klant in een CRM-context, als koper in een bestelcontext, enzovoort.

De manier waarop u grenzen tussen meerdere toepassingscontexten met een ander domein voor elke context identificeert, is precies hoe u de grenzen voor elke zakelijke microservice en het bijbehorende domeinmodel en de bijbehorende domeinmodellen en gegevens kunt identificeren. U probeert altijd de koppeling tussen deze microservices te minimaliseren. In deze handleiding wordt nader ingegaan op dit identificatie- en domeinmodelontwerp in de sectie Domeinmodelgrenzen voor elke microservice later identificeren.

Uitdaging #2: Query's maken waarmee gegevens worden opgehaald uit verschillende microservices

Een tweede uitdaging is het implementeren van query's die gegevens ophalen uit verschillende microservices, terwijl u chattycommunicatie naar de microservices van externe client-apps vermijdt. Een voorbeeld kan een enkel scherm zijn van een mobiele app die gebruikersgegevens moet weergeven die eigendom zijn van de microservices voor winkelwagen, catalogus en gebruikersidentiteit. Een ander voorbeeld is een complex rapport met veel tabellen die zich in meerdere microservices bevinden. De juiste oplossing is afhankelijk van de complexiteit van de query's. Maar in ieder geval hebt u een manier nodig om informatie samen te voegen als u de efficiëntie in de communicatie van uw systeem wilt verbeteren. De populairste oplossingen zijn de volgende.

API-gateway. Voor eenvoudige gegevensaggregatie van meerdere microservices die eigenaar zijn van verschillende databases, is de aanbevolen benadering een aggregatie-microservice die een API-gateway wordt genoemd. U moet echter voorzichtig zijn met het implementeren van dit patroon, omdat het een stikpunt in uw systeem kan zijn en het het principe van microserviceautonomie kan schenden. Om deze mogelijkheid te beperken, kunt u meerdere fijnmazige API-gateways hebben die elk gericht zijn op een verticaal segment of bedrijfsgebied van het systeem. Het API Gateway-patroon wordt verderop in de sectie API Gateway uitgebreider uitgelegd.

GraphQL Federation One-optie om te overwegen of uw microservices al Gebruikmaken van GraphQL GraphQL is GraphQL Federation. Met federatie kunt u 'subgrafen' van andere services definiëren en deze opstellen in een statistische supergraaf die fungeert als een zelfstandig schema.

CQRS met query's/leestabellen. Een andere oplossing voor het samenvoegen van gegevens van meerdere microservices is het patroon Gerealiseerde weergave. In deze benadering genereert u vooraf (bereidt u gedenormaliseerde gegevens voor voordat de werkelijke query's plaatsvinden), een alleen-lezen tabel met de gegevens die eigendom zijn van meerdere microservices. De tabel heeft een indeling die geschikt is voor de behoeften van de client-app.

Overweeg iets als het scherm voor een mobiele app. Als u één database hebt, kunt u de gegevens voor dat scherm verzamelen met behulp van een SQL-query waarmee een complexe join met meerdere tabellen wordt uitgevoerd. Wanneer u echter meerdere databases hebt en elke database eigendom is van een andere microservice, kunt u geen query's uitvoeren op deze databases en een SQL-join maken. Uw complexe query wordt een uitdaging. U kunt de vereiste aanpakken met behulp van een CQRS-benadering. U maakt een gedenormaliseerde tabel in een andere database die alleen wordt gebruikt voor query's. De tabel kan specifiek worden ontworpen voor de gegevens die u nodig hebt voor de complexe query, met een een-op-een-relatie tussen velden die nodig zijn voor het scherm van uw toepassing en de kolommen in de querytabel. Het kan ook dienen voor rapportagedoeleinden.

Deze aanpak lost niet alleen het oorspronkelijke probleem op (hoe u query's kunt uitvoeren en samenvoegen in microservices), maar verbetert ook de prestaties aanzienlijk in vergelijking met een complexe join, omdat u al de gegevens hebt die de toepassing nodig heeft in de querytabel. Natuurlijk betekent het gebruik van CQRS (Command and Query Responsibility Segregation) met query's/leestabellen extra ontwikkelwerkzaamheden en moet u uiteindelijke consistentie omarmen. Niettemin zijn de vereisten voor prestaties en hoge schaalbaarheid in samenwerkingsscenario's (of concurrerende scenario's , afhankelijk van het oogpunt) waar u CQRS met meerdere databases moet toepassen.

'Koude gegevens' in centrale databases. Voor complexe rapporten en query's die mogelijk geen realtime gegevens vereisen, is het gebruikelijk om uw 'dynamische gegevens' (transactionele gegevens van de microservices) als 'koude gegevens' te exporteren naar grote databases die alleen worden gebruikt voor rapportage. Dat centrale databasesysteem kan een big data-systeem zijn, zoals Hadoop; een datawarehouse zoals een op basis van Azure SQL Data Warehouse; of zelfs één SQL-database die alleen wordt gebruikt voor rapporten (als de grootte geen probleem is).

Houd er rekening mee dat deze gecentraliseerde database alleen wordt gebruikt voor query's en rapporten die geen realtime gegevens nodig hebben. De oorspronkelijke updates en transacties, als uw bron van waarheid, moeten zich in uw microservicesgegevens bevinden. De manier waarop u gegevens synchroniseert, is door gebruik te maken van gebeurtenisgestuurde communicatie (behandeld in de volgende secties) of door gebruik te maken van andere import-/exporthulpprogramma's voor database-infrastructuur. Als u gebeurtenisgestuurde communicatie gebruikt, is dat integratieproces vergelijkbaar met de manier waarop u gegevens doorgeeft zoals eerder is beschreven voor CQRS-querytabellen.

Als uw toepassingsontwerp echter voortdurend informatie uit meerdere microservices voor complexe query's samenvoegt, kan het een symptoom zijn van een slecht ontwerp. Een microservice moet zo geïsoleerd mogelijk zijn van andere microservices. (Hiermee worden rapporten/analyses uitgesloten die altijd centrale databases met koude gegevens moeten gebruiken.) Dit probleem kan vaak een reden zijn om microservices samen te voegen. U moet de autonomie van evolutie en implementatie van elke microservice in balans houden met sterke afhankelijkheden, samenhang en gegevensaggregatie.

Uitdaging #3: Consistentie bereiken voor meerdere microservices

Zoals eerder vermeld, zijn de gegevens die eigendom zijn van elke microservice privé voor die microservice en kunnen ze alleen worden geopend met behulp van de microservice-API. Daarom is het een uitdaging om end-to-end bedrijfsprocessen te implementeren terwijl consistentie in meerdere microservices behouden blijft.

Laten we eens kijken naar een voorbeeld van de referentietoepassing eShopOnContainers om dit probleem te analyseren. De Catalogus-microservice onderhoudt informatie over alle producten, inclusief de productprijs. De basket microservice beheert tijdelijke gegevens over productitems die gebruikers toevoegen aan hun winkelwagens, waaronder de prijs van de artikelen op het moment dat ze aan de mand zijn toegevoegd. Wanneer de prijs van een product wordt bijgewerkt in de catalogus, moet die prijs ook worden bijgewerkt in de actieve manden die hetzelfde product bevatten, plus het systeem moet de gebruiker waarschijnlijk waarschuwen dat de prijs van een bepaald item is gewijzigd sinds ze het aan hun mandje hebben toegevoegd.

In een hypothetische monolithische versie van deze toepassing, wanneer de prijs in de tabel producten verandert, kan het catalogussubsysteem gewoon een ACID-transactie gebruiken om de huidige prijs in de tabel Basket bij te werken.

In een toepassing op basis van microservices zijn de tabellen Product en Basket echter eigendom van hun respectieve microservices. Er mag nooit een microservice worden opgenomen in tabellen/opslag die eigendom zijn van een andere microservice in zijn eigen transacties, zelfs niet in directe query's, zoals wordt weergegeven in afbeelding 4-9.

Diagram showing that microservices database data can't be shared.

Afbeelding 4-9. Een microservice heeft geen rechtstreekse toegang tot een tabel in een andere microservice

De Catalogus-microservice moet de tabel Basket niet rechtstreeks bijwerken, omdat de baskettabel eigendom is van de Basket-microservice. Als u een update wilt uitvoeren voor de Basket-microservice, moet de Catalogus-microservice uiteindelijke consistentie gebruiken die waarschijnlijk is gebaseerd op asynchrone communicatie, zoals integratiegebeurtenissen (bericht- en gebeurtenisgebaseerde communicatie). Dit is de manier waarop de eShopOnContainers-referentietoepassing dit type consistentie uitvoert voor microservices.

Zoals aangegeven door de CAP-theorema, moet u kiezen tussen beschikbaarheid en ACID sterke consistentie. De meeste op microservices gebaseerde scenario's vragen beschikbaarheid en hoge schaalbaarheid in plaats van sterke consistentie. Bedrijfskritieke toepassingen moeten actief blijven en ontwikkelaars kunnen sterke consistentie omzeilen door technieken te gebruiken voor het werken met zwakke of uiteindelijke consistentie. Dit is de benadering die wordt gebruikt door de meeste microservicearchitecturen.

Bovendien zijn acid-stijl- of tweefasige doorvoertransacties niet alleen tegen microservicesprincipes; de meeste NoSQL-databases (zoals Azure Cosmos DB, MongoDB, enzovoort) bieden geen ondersteuning voor doorvoertransacties in twee fasen, zoals in gedistribueerde databases. Het onderhouden van gegevensconsistentie tussen services en databases is echter essentieel. Deze uitdaging is ook gerelateerd aan de vraag hoe wijzigingen in meerdere microservices moeten worden doorgegeven wanneer bepaalde gegevens redundant moeten zijn, bijvoorbeeld wanneer u de naam of beschrijving van het product in de microservice Catalogus en de Basket-microservice moet hebben.

Een goede oplossing voor dit probleem is het gebruik van uiteindelijke consistentie tussen microservices die zijn geformuleerd via gebeurtenisgestuurde communicatie en een systeem voor publiceren en abonneren. Deze onderwerpen worden verderop in deze handleiding besproken in de sectie Asynchrone gebeurtenisgestuurde communicatie .

Uitdaging #4: Communicatie tussen microservicegrenzen ontwerpen

Communicatie tussen microservicegrenzen is een echte uitdaging. In deze context verwijst communicatie niet naar welk protocol u moet gebruiken (HTTP en REST, AMQP, messaging, enzovoort). In plaats daarvan wordt uitgelegd welke communicatiestijl u moet gebruiken, en met name hoe uw microservices moeten worden gekoppeld. Afhankelijk van het niveau van koppeling, wanneer er een fout optreedt, zal de impact van die fout op uw systeem aanzienlijk verschillen.

In een gedistribueerd systeem, zoals een toepassing op basis van microservices, met zoveel artefacten die zich verplaatsen en met gedistribueerde services op veel servers of hosts, mislukken onderdelen uiteindelijk. Gedeeltelijke storingen en nog grotere storingen treden op, dus u moet uw microservices en de communicatie tussen deze services ontwerpen, rekening houdend met de algemene risico's in dit type gedistribueerd systeem.

Een populaire benadering is het implementeren van microservices op basis van HTTP (REST), vanwege hun eenvoud. Een op HTTP gebaseerde benadering is perfect acceptabel; het probleem hier is gerelateerd aan hoe u dit gebruikt. Als u HTTP-aanvragen en -antwoorden gebruikt om alleen te communiceren met uw microservices vanuit clienttoepassingen of api-gateways, is dat prima. Maar als u lange ketens maakt van synchrone HTTP-aanroepen tussen microservices, die communiceren over hun grenzen alsof de microservices objecten waren in een monolithische toepassing, treedt uw toepassing uiteindelijk problemen op.

Stel dat uw clienttoepassing een HTTP-API-aanroep maakt naar een afzonderlijke microservice, zoals de Order microservice. Als de ordermicroservice op zijn beurt extra microservices aanroept met behulp van HTTP binnen dezelfde aanvraag-/antwoordcyclus, maakt u een keten van HTTP-aanroepen. Het klinkt in eerste instantie redelijk. Er zijn echter belangrijke punten om rekening mee te houden bij het omlaag gaan van dit pad:

  • Blokkeren en lage prestaties. Vanwege de synchrone aard van HTTP krijgt de oorspronkelijke aanvraag geen antwoord totdat alle interne HTTP-aanroepen zijn voltooid. Stel dat het aantal van deze aanroepen aanzienlijk toeneemt en tegelijkertijd een van de tussenliggende HTTP-aanroepen naar een microservice wordt geblokkeerd. Het resultaat is dat de prestaties worden beïnvloed en dat de algehele schaalbaarheid exponentieel wordt beïnvloed naarmate extra HTTP-aanvragen toenemen.

  • Microservices koppelen aan HTTP. Zakelijke microservices mogen niet worden gekoppeld aan andere zakelijke microservices. In het ideale geval zouden ze niet 'weten' over het bestaan van andere microservices. Als uw toepassing afhankelijk is van het koppelen van microservices, zoals in het voorbeeld, is het bereiken van autonomie per microservice bijna onmogelijk.

  • Fout in één microservice. Als u een keten van microservices hebt geïmplementeerd die zijn gekoppeld door HTTP-aanroepen, mislukt een van de microservices (en uiteindelijk zullen ze mislukken) mislukt de hele keten van microservices. Een systeem op basis van een microservice moet zo goed mogelijk blijven werken tijdens gedeeltelijke storingen. Zelfs als u clientlogica implementeert die gebruikmaakt van nieuwe pogingen met exponentieel uitstel of circuitonderbrekermechanismen, hoe complexer de HTTP-aanroepketens zijn, hoe complexer het is om een foutstrategie te implementeren op basis van HTTP.

Als uw interne microservices communiceren door ketens van HTTP-aanvragen te maken zoals beschreven, kan worden aangevoerd dat u een monolithische toepassing hebt, maar één op basis van HTTP tussen processen in plaats van communicatiemechanismen binnen processen.

Om microserviceautonomie af te dwingen en betere tolerantie te hebben, moet u daarom het gebruik van ketens van communicatie tussen aanvragen en antwoorden in microservices minimaliseren. Het is raadzaam om alleen asynchrone interactie te gebruiken voor communicatie tussen microservices, hetzij met behulp van asynchrone bericht- en gebeurteniscommunicatie, of door HTTP-polling onafhankelijk van de oorspronkelijke HTTP-aanvraag/-responscyclus te gebruiken (asynchroon).

Het gebruik van asynchrone communicatie wordt uitgelegd met aanvullende informatie verderop in deze handleiding in de secties Asynchrone microservice-integratie dwingt de autonomie van de microservice en Asynchrone communicatie op basis van berichten af.

Aanvullende bronnen