Övergång från monolit till mikrotjänster: historia och praktik

I den här artikeln kommer jag att prata om hur projektet jag arbetar med förvandlades från en stor monolit till en uppsättning mikrotjänster.

Projektet började sin historia för ganska länge sedan, i början av 2000. De första versionerna skrevs i Visual Basic 6. Med tiden stod det klart att utveckling på detta språk skulle bli svår att stödja i framtiden, eftersom IDE och språket i sig är dåligt utvecklat. I slutet av 2000-talet beslutades det att byta till det mer lovande C#. Den nya versionen skrevs parallellt med revideringen av den gamla, gradvis skrevs mer och mer kod i .NET. Backend i C# var från början fokuserat på en tjänstearkitektur, men under utvecklingen användes vanliga bibliotek med logik och tjänster lanserades i en enda process. Resultatet blev en applikation som vi kallade en "tjänstmonolit."

En av de få fördelarna med denna kombination var tjänsternas förmåga att anropa varandra via ett externt API. Det fanns tydliga förutsättningar för övergången till en mer korrekt tjänst, och i framtiden, mikrotjänstarkitektur.

Vi började vårt arbete med nedbrytning runt 2015. Vi har ännu inte nått ett idealläge – det finns fortfarande delar av ett stort projekt som knappast kan kallas monoliter, men de ser inte heller ut som mikrotjänster. Ändå är framstegen betydande.
Jag kommer att prata om det i artikeln.

Övergång från monolit till mikrotjänster: historia och praktik

Innehåll

Arkitektur och problem med den befintliga lösningen


Inledningsvis såg arkitekturen ut så här: UI är en separat applikation, den monolitiska delen är skriven i Visual Basic 6, .NET-applikationen är en uppsättning relaterade tjänster som arbetar med en ganska stor databas.

Nackdelar med den tidigare lösningen

En enda punkt av misslyckande
Vi hade ett enda fel: .NET-applikationen kördes i en enda process. Om någon modul misslyckades, misslyckades hela applikationen och måste startas om. Eftersom vi automatiserar ett stort antal processer för olika användare, på grund av ett fel i en av dem, kunde alla inte arbeta under en tid. Och i händelse av ett programvarufel hjälpte inte ens säkerhetskopiering.

Kö av förbättringar
Denna nackdel är ganska organisatorisk. Vår applikation har många kunder, och de vill alla förbättra den så snart som möjligt. Tidigare var det omöjligt att göra detta parallellt och alla kunder stod i kö. Denna process var negativ för företag eftersom de var tvungna att bevisa att deras uppgift var värdefull. Och utvecklingsteamet ägnade tid åt att organisera den här kön. Detta tog mycket tid och ansträngning, och produkten kunde till slut inte förändras så snabbt som de skulle ha velat.

Suboptimal användning av resurser
När vi hostade tjänster i en enda process kopierade vi alltid konfigurationen helt från server till server. Vi ville placera de mest belastade tjänsterna separat för att inte slösa med resurser och få mer flexibel kontroll över vårt distributionssystem.

Svårt att implementera modern teknik
Ett problem som är bekant för alla utvecklare: det finns en önskan att introducera modern teknik i projektet, men det finns ingen möjlighet. Med en stor monolitisk lösning förvandlas varje uppdatering av det nuvarande biblioteket, för att inte tala om övergången till ett nytt, till en ganska icke-trivial uppgift. Det tar lång tid att bevisa för lagledaren att detta kommer att ge fler bonusar än bortkastade nerver.

Svårt att utfärda ändringar
Detta var det allvarligaste problemet - vi släppte utgåvor varannan månad.
Varje utgåva förvandlades till en verklig katastrof för banken, trots testning och ansträngningar från utvecklarna. Verksamheten förstod att i början av veckan en del av dess funktionalitet inte skulle fungera. Och utvecklarna förstod att en vecka av allvarliga incidenter väntade dem.
Alla hade en önskan att förändra situationen.

Förväntningar från mikrotjänster


Utfärdande av komponenter när de är klara. Leverans av komponenter när de är klara genom att sönderdela lösningen och separera olika processer.

Små produktteam. Detta är viktigt eftersom ett stort team som arbetade med den gamla monoliten var svårt att hantera. Ett sådant team tvingades arbeta enligt en strikt process, men de ville ha mer kreativitet och självständighet. Endast små team hade råd med detta.

Isolering av tjänster i separata processer. Helst ville jag isolera det i behållare, men ett stort antal tjänster skrivna i .NET Framework körs bara på Windows. Tjänster baserade på .NET Core dyker nu upp, men det finns få av dem ännu.

Implementeringsflexibilitet. Vi skulle vilja kombinera tjänster på det sätt vi behöver det, och inte så som koden tvingar fram det.

Användning av ny teknik. Detta är intressant för alla programmerare.

Övergångsproblem


Naturligtvis, om det var lätt att bryta en monolit i mikrotjänster, skulle det inte behövas prata om det på konferenser och skriva artiklar. Det finns många fallgropar i denna process, jag kommer att beskriva de viktigaste som hindrade oss.

Första problemet typiskt för de flesta monoliter: samstämmighet i affärslogik. När vi skriver en monolit vill vi återanvända våra klasser för att inte skriva onödig kod. Och när man flyttar till mikrotjänster blir detta ett problem: all kod är ganska tätt kopplad, och det är svårt att separera tjänsterna.

Vid tiden för arbetets start hade förvaret mer än 500 projekt och mer än 700 tusen rader kod. Detta är ett ganska stort beslut och andra problemet. Det gick inte att helt enkelt ta det och dela upp det i mikrotjänster.

Tredje problemet — Brist på nödvändig infrastruktur. Faktum är att vi manuellt kopierade källkoden till servrarna.

Hur man går från monolit till mikrotjänster


Tillhandahållande av mikrotjänster

För det första bestämde vi omedelbart för oss själva att separationen av mikrotjänster är en iterativ process. Vi var alltid skyldiga att utveckla affärsproblem parallellt. Hur vi ska implementera detta tekniskt är redan vårt problem. Därför förberedde vi oss för en iterativ process. Det fungerar inte på något annat sätt om du har en stor applikation och den initialt inte är redo att skrivas om.

Vilka metoder använder vi för att isolera mikrotjänster?

Det första sättet — Flytta befintliga moduler som tjänster. I detta avseende hade vi tur: det fanns redan registrerade tjänster som fungerade med WCF-protokollet. De var uppdelade i separata församlingar. Vi porterade dem separat och lade till en liten startprogram till varje byggnad. Det skrevs med hjälp av det underbara Topshelf-biblioteket, som låter dig köra applikationen både som en tjänst och som en konsol. Detta är bekvämt för felsökning eftersom inga ytterligare projekt krävs i lösningen.

Tjänsterna kopplades ihop enligt affärslogik, eftersom de använde gemensamma assembler och arbetade med en gemensam databas. De kunde knappast kallas mikrotjänster i sin rena form. Vi skulle dock kunna tillhandahålla dessa tjänster separat, i olika processer. Bara detta gjorde det möjligt att minska deras inflytande på varandra, vilket minskade problemet med parallell utveckling och en enda punkt av misslyckande.

Montering med värden är bara en rad kod i programklassen. Vi gömde arbetet med Topshelf i en hjälpklass.

namespace RBA.Services.Accounts.Host
{
   internal class Program
   {
      private static void Main(string[] args)
      {
        HostRunner<Accounts>.Run("RBA.Services.Accounts.Host");

       }
    }
}

Det andra sättet att allokera mikrotjänster är: skapa dem för att lösa nya problem. Om monoliten samtidigt inte växer är detta redan utmärkt, vilket betyder att vi går i rätt riktning. För att lösa nya problem försökte vi skapa separata tjänster. Om det fanns en sådan möjlighet skapade vi mer "kanoniska" tjänster som helt hanterar sin egen datamodell, en separat databas.

Vi, som många, började med autentiserings- och auktoriseringstjänster. De är perfekta för detta. De är oberoende, som regel har de en separat datamodell. De själva interagerar inte med monoliten, bara det vänder sig till dem för att lösa vissa problem. Med hjälp av dessa tjänster kan du påbörja övergången till en ny arkitektur, felsöka infrastrukturen på dem, prova några metoder relaterade till nätverksbibliotek, etc. Vi har inga team i vår organisation som inte kunde skapa en autentiseringstjänst.

Det tredje sättet att allokera mikrotjänsterDen vi använder är lite specifik för oss. Detta är borttagningen av affärslogik från UI-lagret. Vårt huvudsakliga användargränssnitt är skrivbordet, det är, precis som backend, skrivet i C#. Utvecklarna gjorde med jämna mellanrum misstag och överförde delar av logik till användargränssnittet som borde ha funnits i backend och återanvänts.

Om du tittar på ett riktigt exempel från koden för UI-delen kan du se att det mesta av denna lösning innehåller verklig affärslogik som är användbar i andra processer, inte bara för att bygga UI-formuläret.

Övergång från monolit till mikrotjänster: historia och praktik

Den verkliga UI-logiken finns bara på de sista par raderna. Vi överförde den till servern så att den kunde återanvändas, vilket minskade användargränssnittet och uppnådde rätt arkitektur.

Det fjärde och viktigaste sättet att isolera mikrotjänster, som gör det möjligt att minska monoliten, är borttagning av befintliga tjänster med bearbetning. När vi tar ut befintliga moduler som de är blir resultatet inte alltid utvecklarnas smak och affärsprocessen kan ha blivit föråldrad sedan funktionaliteten skapades. Med refactoring kan vi stödja en ny affärsprocess eftersom affärskraven ständigt förändras. Vi kan förbättra källkoden, ta bort kända defekter och skapa en bättre datamodell. Det finns många fördelar.

Att skilja tjänster från bearbetning är oupplösligt kopplat till begreppet avgränsat sammanhang. Detta är ett koncept från Domain Driven Design. Det betyder en del av domänmodellen där alla termer för ett enda språk är unikt definierade. Låt oss titta på sammanhanget för försäkringar och räkningar som ett exempel. Vi har en monolitisk applikation, och vi måste arbeta med kontot inom försäkring. Vi förväntar oss att utvecklaren hittar en befintlig kontoklass i en annan sammansättning, refererar till den från försäkringsklassen, så får vi en fungerande kod. DRY-principen kommer att respekteras, uppgiften kommer att göras snabbare genom att använda befintlig kod.

Som ett resultat visar det sig att sammanhangen för konton och försäkringar hänger ihop. När nya krav dyker upp kommer denna koppling att störa utvecklingen, vilket ökar komplexiteten hos redan komplex affärslogik. För att lösa detta problem måste du hitta gränserna mellan sammanhang i koden och ta bort deras överträdelser. Till exempel, i försäkringssammanhang, är det mycket möjligt att ett 20-siffrigt centralbankskontonummer och datumet då kontot öppnades kommer att räcka.

För att separera dessa avgränsade sammanhang från varandra och påbörja processen att separera mikrotjänster från en monolitisk lösning, använde vi ett tillvägagångssätt som att skapa externa API:er i applikationen. Om vi ​​visste att någon modul skulle bli en mikrotjänst, på något sätt modifierad inom processen, så ringde vi omedelbart till logiken som hör till ett annat begränsat sammanhang genom externa samtal. Till exempel via REST eller WCF.

Vi bestämde oss bestämt för att vi inte skulle undvika kod som skulle kräva distribuerade transaktioner. I vårt fall visade det sig vara ganska lätt att följa denna regel. Vi har ännu inte stött på situationer där strikt distribuerade transaktioner verkligen behövs - den slutliga överensstämmelsen mellan modulerna är helt tillräcklig.

Låt oss titta på ett specifikt exempel. Vi har konceptet med en orkestrator - en pipeline som bearbetar enheten för "applikationen". Han skapar en kund, ett konto och ett bankkort i tur och ordning. Om klienten och kontot har skapats framgångsrikt, men skapandet av kortet misslyckas, flyttas applikationen inte till statusen "lyckad" och förblir i statusen "kort inte skapat". I framtiden kommer bakgrundsaktivitet att plocka upp det och avsluta det. Systemet har varit i ett tillstånd av inkonsekvens under en tid, men vi är generellt nöjda med detta.

Om en situation uppstår när det är nödvändigt att konsekvent spara en del av data, kommer vi med största sannolikhet att gå till konsolidering av tjänsten för att behandla den i en process.

Låt oss titta på ett exempel på tilldelning av en mikrotjänst. Hur kan man få den till produktion relativt säkert? I det här exemplet har vi en separat del av systemet - en lönetjänstmodul, en av kodsektionerna som vi skulle vilja göra mikroservice av.

Övergång från monolit till mikrotjänster: historia och praktik

Först och främst skapar vi en mikrotjänst genom att skriva om koden. Vi förbättrar några aspekter som vi inte var nöjda med. Vi implementerar nya affärskrav från kunden. Vi lägger till en API-gateway till anslutningen mellan UI och backend, som kommer att tillhandahålla vidarekoppling av samtal.

Övergång från monolit till mikrotjänster: historia och praktik

Därefter släpper vi denna konfiguration i drift, men i ett pilotläge. De flesta av våra användare arbetar fortfarande med gamla affärsprocesser. För nya användare utvecklar vi en ny version av den monolitiska applikationen som inte längre innehåller denna process. I huvudsak har vi en kombination av en monolit och en mikrotjänst som arbetar som pilot.

Övergång från monolit till mikrotjänster: historia och praktik

Med en framgångsrik pilot förstår vi att den nya konfigurationen verkligen är användbar, vi kan ta bort den gamla monoliten från ekvationen och lämna den nya konfigurationen i stället för den gamla lösningen.

Övergång från monolit till mikrotjänster: historia och praktik

Totalt använder vi nästan alla befintliga metoder för att dela upp källkoden för en monolit. Alla av dem tillåter oss att minska storleken på delar av applikationen och översätta dem till nya bibliotek, vilket gör bättre källkod.

Arbeta med databasen


Databasen kan delas sämre än källkoden, eftersom den inte bara innehåller det aktuella schemat utan även ackumulerad historisk data.

Vår databas, liksom många andra, hade en annan viktig nackdel - dess enorma storlek. Denna databas utformades enligt den intrikata affärslogiken hos en monolit, och relationer ackumulerades mellan tabellerna i olika avgränsade sammanhang.

I vårt fall, till toppen av alla problem (stor databas, många kopplingar, ibland oklara gränser mellan tabeller), uppstod ett problem som uppstår i många stora projekt: användningen av den delade databasmallen. Data togs från tabeller genom vy, genom replikering och skickades till andra system där denna replikering behövdes. Som ett resultat kunde vi inte flytta tabellerna till ett separat schema eftersom de användes aktivt.

Samma uppdelning i begränsade sammanhang i koden hjälper oss i separationen. Det ger oss vanligtvis en ganska bra uppfattning om hur vi bryter ner data på databasnivå. Vi förstår vilka tabeller som hör till ett avgränsat sammanhang och vilka till ett annat.

Vi använde två globala metoder för databaspartitionering: partitionering av befintliga tabeller och partitionering med bearbetning.

Att dela upp befintliga tabeller är en bra metod att använda om datastrukturen är bra, uppfyller affärskraven och alla är nöjda med den. I det här fallet kan vi separera befintliga tabeller i ett separat schema.

En avdelning med bearbetning behövs när affärsmodellen har förändrats kraftigt, och tabellerna inte längre tillfredsställer oss alls.

Dela upp befintliga tabeller. Vi måste bestämma vad vi ska separera. Utan denna kunskap kommer ingenting att fungera, och här kommer separationen av avgränsade sammanhang i koden att hjälpa oss. Om du kan förstå gränserna för sammanhang i källkoden blir det i regel tydligt vilka tabeller som ska ingå i listan för institutionen.

Låt oss föreställa oss att vi har en lösning där två monolitmoduler interagerar med en databas. Vi måste se till att endast en modul interagerar med sektionen av separerade tabeller, och den andra börjar interagera med den via API:et. Till att börja med räcker det att endast inspelning utförs via API:et. Detta är en nödvändig förutsättning för att vi ska kunna prata om mikrotjänsters oberoende. Läsanslutningar kan finnas kvar så länge det inte är några större problem.

Övergång från monolit till mikrotjänster: historia och praktik

Nästa steg är att vi kan separera kodavsnittet som fungerar med separerade tabeller, med eller utan bearbetning, till en separat mikrotjänst och köra den i en separat process, en container. Detta kommer att vara en separat tjänst med en koppling till monolitdatabasen och de tabeller som inte direkt relaterar till den. Monoliten samverkar fortfarande för läsning med den löstagbara delen.

Övergång från monolit till mikrotjänster: historia och praktik

Senare kommer vi att ta bort denna anslutning, det vill säga att läsa data från en monolitisk applikation från separerade tabeller kommer också att överföras till API.

Övergång från monolit till mikrotjänster: historia och praktik

Därefter väljer vi från den allmänna databasen de tabeller som endast den nya mikrotjänsten fungerar med. Vi kan flytta tabellerna till ett separat schema eller till och med till en separat fysisk databas. Det finns fortfarande en läskoppling mellan mikrotjänsten och monolitdatabasen, men det finns inget att oroa sig för, i den här konfigurationen kan den leva ganska länge.

Övergång från monolit till mikrotjänster: historia och praktik

Det sista steget är att helt ta bort alla anslutningar. I det här fallet kan vi behöva migrera data från huvuddatabasen. Ibland vill vi återanvända vissa data eller kataloger som replikeras från externa system i flera databaser. Detta händer oss med jämna mellanrum.

Övergång från monolit till mikrotjänster: historia och praktik

Bearbetningsavdelning. Denna metod är mycket lik den första, bara i omvänd ordning. Vi allokerar omedelbart en ny databas och en ny mikrotjänst som interagerar med monoliten via ett API. Men samtidigt finns det en uppsättning databastabeller kvar som vi vill ta bort i framtiden. Vi behöver det inte längre, vi bytte ut det i den nya modellen.

Övergång från monolit till mikrotjänster: historia och praktik

För att det här systemet ska fungera behöver vi sannolikt en övergångsperiod.

Det finns då två möjliga tillvägagångssätt.

Först: vi duplicerar all data i de nya och gamla databaserna. I det här fallet har vi dataredundans och synkroniseringsproblem kan uppstå. Men vi kan ta två olika kunder. En kommer att arbeta med den nya versionen, den andra med den gamla.

För det andra: vi delar upp data enligt vissa affärskriterier. Vi hade till exempel 5 produkter i systemet som var lagrade i den gamla databasen. Vi placerar den sjätte inom den nya affärsuppgiften i en ny databas. Men vi kommer att behöva en API-gateway som kommer att synkronisera denna data och visa klienten var och vad den ska hämta ifrån.

Båda tillvägagångssätten fungerar, välj beroende på situationen.

Efter att vi är säkra på att allt fungerar kan den del av monoliten som fungerar med gamla databasstrukturer inaktiveras.

Övergång från monolit till mikrotjänster: historia och praktik

Det sista steget är att ta bort de gamla datastrukturerna.

Övergång från monolit till mikrotjänster: historia och praktik

För att sammanfatta kan vi säga att vi har problem med databasen: det är svårt att arbeta med den jämfört med källkoden, det är svårare att dela, men det kan och bör göras. Vi har hittat några sätt som gör att vi kan göra detta ganska säkert, men det är fortfarande lättare att göra misstag med data än med källkod.

Arbeta med källkod


Så här såg källkodsdiagrammet ut när vi började analysera det monolitiska projektet.

Övergång från monolit till mikrotjänster: historia och praktik

Den kan grovt delas upp i tre lager. Detta är ett lager av lanserade moduler, plugins, tjänster och individuella aktiviteter. I själva verket var dessa ingångspunkter inom en monolitisk lösning. Alla av dem var tätt förseglade med ett gemensamt lager. Det hade affärslogik som delade tjänsterna och många kopplingar. Varje tjänst och plugin använde upp till 10 eller fler vanliga sammansättningar, beroende på deras storlek och utvecklarnas samvete.

Vi hade turen att ha infrastrukturbibliotek som kunde användas separat.

Ibland uppstod en situation när några vanliga objekt faktiskt inte tillhörde detta lager, utan var infrastrukturbibliotek. Detta löstes genom att döpa om.

Det största bekymret var avgränsade sammanhang. Det hände att 3-4 sammanhang blandades i en gemensam församling och använde varandra inom samma affärsfunktioner. Det var nödvändigt att förstå var detta kunde delas upp och längs vilka gränser, och vad man skulle göra härnäst med att kartlägga denna uppdelning i källkodssammansättningar.

Vi har formulerat flera regler för koddelningsprocessen.

Den första: Vi ville inte längre dela affärslogik mellan tjänster, aktiviteter och plugins. Vi ville göra affärslogik oberoende inom mikrotjänster. Mikrotjänster, å andra sidan, är idealiskt tänkta som tjänster som existerar helt oberoende. Jag anser att det här tillvägagångssättet är något slösaktigt, och det är svårt att uppnå, eftersom till exempel tjänster i C# i alla fall kommer att kopplas samman av ett standardbibliotek. Vårt system är skrivet i C#, vi har ännu inte använt andra teknologier. Därför bestämde vi oss för att vi hade råd att använda gemensamma tekniska sammansättningar. Huvudsaken är att de inte innehåller några fragment av affärslogik. Om du har ett bekvämlighetsomslag över den ORM du använder är det mycket dyrt att kopiera det från tjänst till tjänst.

Vårt team är ett fan av domändriven design, så lökarkitektur passade oss utmärkt. Grunden för våra tjänster är inte dataåtkomstskiktet, utan en sammansättning med domänlogik, som endast innehåller affärslogik och inte har några kopplingar till infrastrukturen. Samtidigt kan vi självständigt modifiera domänsammansättningen för att lösa problem relaterade till ramverk.

I detta skede stötte vi på vårt första allvarliga problem. Tjänsten var tvungen att referera till en domänsammansättning, vi ville göra logiken oberoende och DRY-principen hindrade oss här mycket. Utvecklarna ville återanvända klasser från angränsande sammansättningar för att undvika dubbelarbete, och som ett resultat började domäner att länkas samman igen. Vi analyserade resultaten och beslutade att problemet kanske också ligger i området för källkodslagringsenheten. Vi hade ett stort förråd som innehöll all källkod. Lösningen för hela projektet var mycket svår att montera på en lokal maskin. Därför skapades separata små lösningar för delar av projektet, och ingen förbjöd att lägga till någon gemensam eller domänsammansättning till dem och återanvända dem. Det enda verktyget som inte tillät oss att göra detta var kodgranskning. Men ibland misslyckades det också.

Sedan började vi gå över till en modell med separata förråd. Affärslogik flödar inte längre från tjänst till tjänst, domäner har verkligen blivit oberoende. Avgränsade sammanhang stöds tydligare. Hur återanvänder vi infrastrukturbibliotek? Vi separerade dem i ett separat förråd och lade dem sedan i Nuget-paket, som vi lade in i Artifactory. Vid alla ändringar sker montering och publicering automatiskt.

Övergång från monolit till mikrotjänster: historia och praktik

Våra tjänster började referera till interna infrastrukturpaket på samma sätt som externa. Vi laddar ner externa bibliotek från Nuget. För att arbeta med Artifactory, där vi placerade dessa paket, använde vi två pakethanterare. I små förråd använde vi även Nuget. I arkiv med flera tjänster använde vi Paket, som ger mer versionskonsistens mellan moduler.

Övergång från monolit till mikrotjänster: historia och praktik

Genom att arbeta med källkoden, ändra arkitekturen något och separera arkiven gör vi våra tjänster mer oberoende.

Infrastrukturproblem


De flesta av nackdelarna med att flytta till mikrotjänster är infrastrukturrelaterade. Du kommer att behöva automatiserad distribution, du behöver nya bibliotek för att köra infrastrukturen.

Manuell installation i miljöer

Inledningsvis installerade vi lösningen för miljöer manuellt. För att automatisera denna process skapade vi en CI/CD-pipeline. Vi valde den kontinuerliga leveransprocessen eftersom kontinuerlig driftsättning ännu inte är acceptabel för oss ur affärsprocessernas synvinkel. Därför utförs sändning för drift med en knapp och för testning - automatiskt.

Övergång från monolit till mikrotjänster: historia och praktik

Vi använder Atlassian, Bitbucket för källkodslagring och Bamboo för att bygga. Vi gillar att skriva byggskript i Cake eftersom det är samma som C#. Färdiga paket kommer till Artifactory och Ansible kommer automatiskt till testservrarna, varefter de kan testas direkt.

Övergång från monolit till mikrotjänster: historia och praktik

Separat loggning


En gång i tiden var en av idéerna med monoliten att tillhandahålla delad loggning. Vi behövde också förstå vad vi skulle göra med de enskilda loggarna som finns på diskarna. Våra loggar skrivs till textfiler. Vi bestämde oss för att använda en standard ELK-stack. Vi skrev inte till ELK direkt via leverantörerna, utan beslutade att vi skulle modifiera textloggarna och skriva spårnings-ID i dem som en identifierare, lägga till tjänstens namn, så att dessa loggar kunde analyseras senare.

Övergång från monolit till mikrotjänster: historia och praktik

Genom att använda Filebeat får vi möjlighet att samla in våra loggar från servrar, sedan transformera dem, använda Kibana för att bygga frågor i användargränssnittet och se hur samtalet gick mellan tjänsterna. Trace ID hjälper mycket med detta.

Testning och felsökningsrelaterade tjänster


Till en början förstod vi inte helt hur vi skulle felsöka de tjänster som utvecklades. Allt var enkelt med monoliten, vi körde den på en lokal maskin. Först försökte de göra samma sak med mikrotjänster, men ibland för att helt lansera en mikrotjänst måste du starta flera andra, och det är obekvämt. Vi insåg att vi måste gå över till en modell där vi bara lämnar den eller de tjänster som vi vill felsöka på den lokala maskinen. Resterande tjänster används från servrar som matchar konfigurationen med prod. Efter felsökning, under testning, för varje uppgift, skickas endast de ändrade tjänsterna till testservern. Således testas lösningen i den form den kommer att synas i produktion i framtiden.

Det finns servrar som bara kör produktionsversioner av tjänster. Dessa servrar behövs vid incidenter, för att kontrollera leveransen före driftsättning och för intern utbildning.

Vi har lagt till en automatiserad testprocess med det populära Specflow-biblioteket. Tester körs automatiskt med NUnit omedelbart efter distribution från Ansible. Om uppgiftstäckningen är helautomatisk, så finns det inget behov av manuell testning. Även om det ibland krävs ytterligare manuell testning. Vi använder taggar i Jira för att avgöra vilka tester som ska köras för ett specifikt problem.

Dessutom har behovet av belastningstester ökat, tidigare utfördes det endast i sällsynta fall. Vi använder JMeter för att köra tester, InfluxDB för att lagra dem och Grafana för att bygga processgrafer.

Vad har vi uppnått?


För det första blev vi av med begreppet "släpp". Borta är de två månader långa monstruösa utgåvorna när denna koloss distribuerades i en produktionsmiljö, vilket tillfälligt störde affärsprocesser. Nu distribuerar vi tjänster i genomsnitt var 1,5 dag, och grupperar dem eftersom de tas i drift efter godkännande.

Det finns inga dödliga misslyckanden i vårt system. Om vi ​​släpper en mikrotjänst med en bugg kommer funktionaliteten associerad med den att brytas, och all annan funktionalitet kommer inte att påverkas. Detta förbättrar användarupplevelsen avsevärt.

Vi kan kontrollera distributionsmönstret. Du kan välja grupper av tjänster separat från resten av lösningen, om det behövs.

Dessutom har vi minskat problemet avsevärt med en stor kö av förbättringar. Vi har nu separata produktteam som arbetar med några av tjänsterna självständigt. Scrum-processen passar redan bra här. Ett specifikt team kan ha en separat produktägare som tilldelar uppgifter till det.

Sammanfattning

  • Mikrotjänster är väl lämpade för att bryta ner komplexa system. I processen börjar vi förstå vad som finns i vårt system, vilka begränsade sammanhang det finns, var deras gränser går. Detta låter dig fördela förbättringar korrekt mellan moduler och förhindra kodförvirring.
  • Mikrotjänster ger organisatoriska fördelar. De talas ofta om bara som arkitektur, men vilken arkitektur som helst behövs för att lösa affärsbehov, och inte på egen hand. Därför kan vi säga att mikrotjänster är väl lämpade för att lösa problem i små team med tanke på att Scrum är väldigt populärt nu.
  • Separation är en iterativ process. Du kan inte ta en applikation och helt enkelt dela upp den i mikrotjänster. Det är osannolikt att den resulterande produkten är funktionell. När man dedikerar mikrotjänster är det fördelaktigt att skriva om det befintliga arvet, det vill säga omvandla det till kod som vi gillar och bättre möter affärsbehov vad gäller funktionalitet och hastighet.

    En liten varning: Kostnaderna för att flytta till mikrotjänster är ganska betydande. Det tog lång tid att ensam lösa infrastrukturproblemet. Så om du har en liten applikation som inte kräver specifik skalning, såvida du inte har ett stort antal kunder som konkurrerar om ditt teams uppmärksamhet och tid, kanske mikrotjänster inte är vad du behöver idag. Det är ganska dyrt. Om man startar processen med mikrotjänster så blir kostnaderna initialt högre än om man startar samma projekt med utveckling av en monolit.

    PS En mer känslosam historia (och som för dig personligen) - enligt länk.
    Här är den fullständiga versionen av rapporten.

Källa: will.com

Lägg en kommentar