Dataöverväganden för mikrotjänster
I den här artikeln beskrivs överväganden för att hantera data i en arkitektur för mikrotjänster. Eftersom varje mikrotjänst hanterar sina egna data är dataintegritet och datakonsekvens kritiska utmaningar.
En grundläggande princip för mikrotjänster är att varje tjänst hanterar sina egna data. Två tjänster bör inte dela ett datalager. I stället ansvarar varje tjänst för sitt eget privata datalager, som andra tjänster inte kan komma åt direkt.
Anledningen till den här regeln är att undvika oavsiktlig koppling mellan tjänster, vilket kan leda till att tjänster delar samma underliggande datascheman. Om dataschemat ändras måste ändringen samordnas över alla tjänster som förlitar sig på databasen. Genom att isolera varje tjänsts datalager kan vi begränsa omfattningen av ändringar och bevara flexibiliteten hos verkligt oberoende distributioner. En annan orsak är att varje mikrotjänst kan ha egna datamodeller, frågor eller läs-/skrivmönster. Användningen av ett delat datalager begränsar varje teams möjlighet att optimera datalagringen för den specifika tjänsten.

Den här metoden leder naturligt till polyglobpersistence – användningen av flera datalagringstekniker i ett enda program. En tjänst kan kräva funktionerna för schema vid läsning i en dokumentdatabas. En annan kan behöva referensintegriteten som tillhandahålls av en RDBMS. Varje team kan göra det bästa valet för sin tjänst. Mer information om den allmänna principen om polyglobpersistence finns i Använda det bästa datalagret för jobbet.
Anteckning
Det går bra för tjänster att dela samma fysiska databasserver. Problemet uppstår när tjänster delar samma schema eller läser och skriver till samma uppsättning databastabeller.
Utmaningar
Vissa utmaningar uppstår med den här distribuerade metoden för att hantera data. För det första kan det finnas redundans i datalagren, där samma dataobjekt visas på flera platser. Data kan till exempel lagras som en del av en transaktion och sedan lagras någon annanstans för analys, rapportering eller arkivering. Duplicerade eller partitionerade data kan leda till problem med dataintegritet och konsekvens. När datarelationer sträcker sig över flera tjänster kan du inte använda traditionella datahanteringstekniker för att framtvinga relationerna.
Traditionell datamodellering använder regeln "ett faktum på en plats". Varje entitet visas exakt en gång i schemat. Andra entiteter kan innehålla referenser till den men inte duplicera den. Den uppenbara fördelen med den traditionella metoden är att uppdateringar görs på en enda plats, vilket undviker problem med datakonsekvens. I en arkitektur för mikrotjänster måste du överväga hur uppdateringar sprids över tjänster och hur du hanterar slutlig konsekvens när data visas på flera platser utan stark konsekvens.
Metoder för att hantera data
Det finns ingen enskild metod som är korrekt i samtliga fall, men här är några allmänna riktlinjer för att hantera data i en mikrotjänstarkitektur.
Utnyttja slutlig konsekvens där det är möjligt. Förstå de platser i systemet där du behöver stark konsekvens eller ACID-transaktioner och de platser där slutlig konsekvens är acceptabel.
När du behöver starka konsekvensgarantier kan en tjänst representera sanningskällan för en viss entitet, som exponeras via ett API. Andra tjänster kan innehålla sin egen kopia av data, eller en delmängd av data, som så småningom är konsekvent med huvuddata men inte anses vara sanningskällan. Tänk dig till exempel ett e-handelssystem med en kundtjänst och en rekommendationstjänst. Rekommendationstjänsten kan lyssna på händelser från ordertjänsten, men om en kund begär en återbetalning är det ordertjänsten, inte rekommendationstjänsten, som har den fullständiga transaktionshistoriken.
För transaktioner använder du mönster som Scheduler-agentövervakare och kompenserande transaktion för att hålla data konsekventa över flera tjänster. Du kan behöva lagra ytterligare en databit som avbildar tillståndet för en arbetsenhet som omfattar flera tjänster för att undvika partiella fel mellan flera tjänster. Du kan till exempel behålla ett arbetsobjekt i en varaktig kö medan en transaktion i flera steg pågår.
Lagra endast de data som en tjänst behöver. En tjänst kanske bara behöver en delmängd information om en domänentitet. I kontexten Leveransbunden behöver vi till exempel veta vilken kund som är associerad med en viss leverans. Men vi behöver inte kundens faktureringsadress – som hanteras av kontexten Konton. Att tänka noggrant på domänen och använda en DDD-metod kan vara till hjälp här.
Fundera över om dina tjänster är sammanhängande och löst kopplade. Om två tjänster kontinuerligt utbyter information med varandra, vilket resulterar i trafikknackade API:er, kan du behöva rita om tjänstgränserna genom att sammanfoga två tjänster eller omstrukturera deras funktioner.
Använd ett händelsedrivet arkitekturformat. I det här arkitekturformatet publicerar en tjänst en händelse när det finns ändringar i dess offentliga modeller eller entiteter. Intresserade tjänster kan prenumerera på dessa händelser. En annan tjänst kan till exempel använda händelserna för att skapa en materialiserad vy av de data som passar bättre för frågor.
En tjänst som äger händelser bör publicera ett schema som kan användas för att automatisera serialisering och deserialisering av händelserna för att undvika nära koppling mellan utgivare och prenumeranter. Överväg JSON-schema eller ett ramverk som Microsoft Bond,Protobuf eller Avro.
Händelser i hög skala kan bli en flaskhals i systemet, så överväg att använda aggregering eller batchbearbetning för att minska den totala belastningen.
Exempel: Välja datalager för programmet Drone Delivery
I de tidigare artiklarna i den här serien beskrivs en tjänst för drönarleverans som ett exempel som körs. Du kan läsa mer om scenariot och motsvarande referensimplementering här.
Det här programmet definierar flera mikrotjänster för schemaläggning av leveranser av drönare. När en användare schemalägger en ny leverans innehåller klientbegäran information om leveransen, till exempel platser för upphämtning och avlämning, samt om paketet, till exempel storlek och vikt. Den här informationen definierar en arbetsenhet.
De olika backend-tjänsterna bryr sig om olika delar av informationen i begäran och har även olika läs- och skrivprofiler.

Leveranstjänst
Leveranstjänsten lagrar information om varje leverans som är schemalagd eller pågår. Den lyssnar efter händelser från drönarna och spårar status för leveranser som pågår. Den skickar även domänhändelser med leveransstatusuppdateringar.
Det förväntas att användarna ofta kontrollerar statusen för en leverans medan de väntar på sitt paket. Därför kräver leveranstjänsten ett datalager som betonar dataflöde (läsning och skrivning) över långsiktig lagring. Dessutom utför inte leveranstjänsten några komplexa frågor eller analyser, utan hämtar bara den senaste statusen för en viss leverans. Leveranstjänstteamet valde Azure Cache for Redis för sina höga läs- och skrivprestanda. Informationen som lagras i Redis är relativt kortlivad. När en leverans är klar är leveranshistoriktjänsten postsystemet.
Leveranshistoriktjänst
Tjänsten Leveranshistorik lyssnar efter leveransstatushändelser från leveranstjänsten. Den lagrar dessa data i långsiktig lagring. Det finns två olika användningsfall för dessa historiska data, som har olika krav på datalagring.
Det första scenariot är att aggregera data i syfte att optimera verksamheten eller förbättra tjänstens kvalitet. Observera att tjänsten Leveranshistorik inte utför den faktiska analysen av data. Den ansvarar bara för inmatning och lagring. I det här scenariot måste lagringen optimeras för dataanalys över en stor uppsättning data, med hjälp av en metod för schema vid läsning för att hantera en mängd olika datakällor. Azure Data Lake Store passar bra för det här scenariot. Data Lake Store är ett Apache Hadoop-filsystem som är kompatibelt med Hadoop Distributed File System (HDFS) och är justerat för prestanda för dataanalysscenarier.
Det andra scenariot gör det möjligt för användare att leta upp historiken för en leverans när leveransen har slutförts. Azure Data Lake är inte optimerat för det här scenariot. För optimala prestanda rekommenderar Microsoft att du lagrar tidsseriedata i Data Lake i mappar som partitioneras efter datum. (Se Tuning Azure Data Lake Store for performance (Justera Azure-Data Lake Store för prestanda). Den strukturen är dock inte optimal för att söka efter enskilda poster efter ID. Om du inte också känner till tidsstämpeln kräver en sökning efter ID att hela samlingen genomsöks. Därför lagrar tjänsten Leveranshistorik även en delmängd av historiska data i Cosmos DB för snabbare sökning. Posterna behöver inte vara kvar i Cosmos DB på obestämd tid. Äldre leveranser kan arkiveras – till exempel efter en månad. Detta kan göras genom att köra en batchprocess då och då.
Pakettjänst
Pakettjänsten lagrar information om alla paket. Lagringskraven för paketet är:
- Långtidsförvaring.
- Kan hantera en stor mängd paket, vilket kräver högt skrivgenomflöde.
- Stöd för enkla frågor efter paket-ID. Inga komplexa kopplingar eller krav för referensintegritet.
Eftersom paketdata inte är relationsbaserade är en dokumentorienterad databas lämplig och Cosmos DB uppnå högt dataflöde med hjälp av shardade samlingar. Teamet som arbetar med pakettjänsten är bekanta med MEAN-stacken (MongoDB, Express.js, AngularJS och Node.js), så de väljer MongoDB API för Cosmos DB. Det gör att de kan utnyttja sin befintliga erfarenhet av MongoDB, samtidigt som de får fördelarna Cosmos DB, som är en hanterad Azure-tjänst.
Nästa steg
Lär dig mer om designmönster som kan hjälpa dig att lösa några vanliga utmaningar i en arkitektur för mikrotjänster.