Design af skalerbar backend-infrastruktur fra bunden

At designe en fremtidsklar backend-platform fra bunden er meget efterspurgt i disse dage, men det er ikke let at vikle dit hoved omkring den overvældende information, der er tilgængelig på det på internettet. Så vi opbygger en fuldt udstyret skalerbar backend trin for trin i denne flerdelsserie.

Jeg har oprettet en YouTube-serie ud af denne blogpost, da jeg fik så mange anmodninger. Følg min alfakode på youtube-kanalen for en række forelæsninger om mikroservices-arkitektur.

Link til den første crashkursserie: https://www.youtube.com/playlist?list=PLZBNtT95PIW3BPNYF5pYOi4MJjg_boXCG

Når du udvikler den første version af en applikation, har du ofte ikke problemer med skalerbarhed. Desuden bremser brugen af ​​en distribueret arkitektur udviklingen. Dette kan være et stort problem for startups, hvis største udfordring er at hurtigt udvikle forretningsmodellen og reducere markedstiden. Men da du er her, antager jeg, at du allerede ved det. Lad os hoppe lige ind i det, mens vi holder følgende mål i tankerne:

  1. Distribuer API-udvikling: Systemet skal designes på en sådan måde, at flere hold kan arbejde på det samtidigt, og et enkelt team bør ikke blive en flaskehals, og det behøver heller ikke at have ekspertise i hele applikationen for at skabe optimerede slutpunkter.
  2. Understøtt flere sprog: For at drage fordel af nye teknologier skal hver funktionel del af systemet være i stand til at understøtte det foretrukne sprog, du vælger for denne funktionalitet.
  3. Minimer forsinkelse: Enhver arkitektur, som vi foreslår, skal altid forsøge at minimere klientens responstid.
  4. Minimer implementeringsrisici: Forskellige funktionelle komponenter i systemet skal kunne implementeres separat med minimal koordinering.
  5. Minimer hardwarefodaftryk: Systemet skal prøve at optimere mængden af ​​hardware, der bruges, og skal være vandret skalerbar.

Bygning af monolitiske applikationer

Lad os forestille os, at du begyndte at bygge en helt ny e-handelsapplikation, der skulle konkurrere med Amazon. Du vil starte med at oprette et nyt projekt i dit foretrukne valg af platform som Rails, Spring Boot, Play osv. Det ville typisk have en modulær arkitektur noget i denne retning:

Fig

Det øverste lag håndterer generelt klientanmodningerne, og efter at have udført nogle valideringer, vil det videresende anmodningen til servicelaget, hvor al forretningslogik er implementeret. En tjeneste vil gøre brug af forskellige adaptere som databaseadgangskomponenter i DAO-lag, messaging-komponenter, eksterne API'er eller andre tjenester i samme lag for at forberede resultatet og returnere det tilbage til den controller, som praktikant returnerer det til klienten.

Denne type applikation er generelt pakket og implementeret som en monolit, hvilket betyder en stor fil. For f.eks. det vil være en krukke i tilfælde af springstart og en zip-fil i tilfælde af Rails eller Node.js-app. Programmer som disse er temmelig almindelige og har mange fordele, de er lette at forstå, styre, udvikle, teste og implementere. Du kan også skalere dem ved at køre flere kopier af det bag en belastningsafbalancering, og det fungerer ganske godt op til et vist niveau.

Desværre har denne enkle tilgang enorme begrænsninger som:

  • Sprog / rammelås: Da hele applikationen er skrevet i en enkelt tech-stak. Kan ikke eksperimentere med nye teknologier.
  • Svær at fordøje: Når appen bliver stor, bliver det svært for en udvikler at forstå en så stor kodebase.
  • Svært at distribuere API-udvikling: Det bliver ekstremt vanskeligt at udføre agil udvikling, og en stor del af udviklerens tid spildes med at løse konflikter.
  • Distribution som en enkelt enhed: Kan ikke uafhængigt distribuere en enkelt ændring til en enkelt komponent. Ændringer "holdes som gidsler" af andre ændringer.
  • Udviklingen går langsommere: Jeg har arbejdet på en kodebase, der havde mere end 50.000 klasser. Den store størrelse af kodebasen var nok til at bremse IDE- og opstartstiderne på grund af hvilken produktivitet, der plejede at lide.
  • Ressourcer er ikke optimeret: Nogle moduler implementerer muligvis CPU-intensiv billedbehandlingslogik, der kræver beregningsoptimerede forekomster, og et andet modul er muligvis en database i hukommelsen og bedst egnet til hukommelsesoptimerede forekomster. Men vi bliver nødt til at gå på kompromis med vores valg af hardware. Det kan også ske, at et applikationsmodul kræver skalering, men vi bliver nødt til at køre en hel instans af applikationen igen, fordi vi ikke kan skalere et modul individuelt.

Ville det ikke være fantastisk, hvis vi kunne opdele applikationen i mindre dele og administrere dem på en sådan måde, at den opfører sig som en enkelt applikation, når vi kører den? Ja, det ville være, og det er præcis, hvad vi skal gøre næste!

Mikroservices-arkitektur

Mange organisationer, såsom Amazon, Facebook, Twitter, eBay og Netflix, har løst dette problem ved at anvende det, der nu kaldes Microservices Architecture-mønster. Det løser dette problem ved at dele det op i mindre underproblemer, dvs. fordele og erobre i udviklernes verden. Se nøje på figur 1, vi klipper lodrette skiver ud af det og skaber mindre sammenkoblede tjenester. Hver skive implementerer en særskilt funktionalitet såsom kurvstyring, brugeradministration og orderstyring osv. Hver service kan skrives på ethvert sprog / ramme og kan have den polyglot persistens, der passer til brugssagen. Let-peasy ikke?

Men vent! Vi ønskede også, at det skulle opføre sig som en enkelt applikation til klienten ellers vil klienten skulle håndtere al den kompleksitet, der følger med denne arkitektur, som at samle dataene fra forskellige tjenester, vedligeholde så mange slutpunkter, øget chattiness af klient og server, separat godkendelse til hver tjeneste. Klientafhængighed af mikroservices direkte gør det også vanskeligt at refaktorere tjenesterne. En intuitiv måde at gøre dette på er at skjule disse tjenester bag et nyt servicelag og levere API'er, der er skræddersyet til hver klient. Dette aggregator-servicelag er også kendt som API Gateway og er en almindelig måde at tackle dette problem på.

API Gateway-baseret mikroservices arkitekturmønster

Alle anmodninger fra klienter går først gennem API-gatewayen. Derefter dirigerer anmodninger til den relevante mikroservice. API Gateway håndterer ofte en anmodning ved at påberåbe sig flere mikroservices og aggregerer resultaterne. Det kan have andre ansvarsområder, såsom godkendelse, overvågning, belastningsbalancering, cache og statisk responshåndtering. Da denne gateway leverer klientspecifikke API'er reducerer den antallet af rundture mellem klienten og applikationen, hvilket reducerer netværkets latenstid, og det forenkler også klientkoden.

Den funktionelle nedbrydning af monolitten vil variere afhængigt af anvendelsessagen. Amazon bruger mere end 100 mikroservices til at vise en enkelt produktside, hvorimod Netflix har mere end 600 mikroservices til styring af deres backend. Mikroservicerne, der er anført i ovenstående diagram, giver dig en idé om, hvordan en skalerbar eCommerce-applikation skal nedbrydes, men en mere omhyggelig observation kan være nødvendig, før den implementeres til produktion.

Der er ikke sådan noget som en gratis frokost. Microservices bringer nogle komplekse udfordringer med sig, som:

  • Distribuerede computing-udfordringer: Da forskellige mikroservices skal køre i et distribueret miljø, er vi nødt til at tage os af disse Fallacies of Distribuerede Computing. Kort sagt må vi antage, at opførslen og placeringen af ​​komponenterne i vores system konstant vil ændre sig.
  • Fjernopkald er dyre: Udviklere skal vælge og implementere en effektiv kommunikationsmekanisme mellem processer.
  • Distribuerede transaktioner: Forretningstransaktioner, der opdaterer flere forretningsenheder, er nødt til at stole på eventuel konsistens over ACID.
  • Utilgængelighed af håndtering af tjenester: Vi bliver nødt til at designe vores system til at håndtere utilgængelighed eller langsomhed af tjenester. Alt svigter hele tiden.
  • Implementering af funktioner, der spænder over flere tjenester.
  • Integrationstest og forandringsstyring bliver vanskelige.

Naturligvis begynder håndtering af kompleksiteter af mikroservices manuelt snart at komme ud af hænderne. For at opbygge et automatiseret og selektiv helbredende distribueret system skal vi have følgende funktioner i vores arkitektur.

  • Central konfiguration: Et centraliseret, versioneret konfigurationssystem, noget som Zookeeper, hvor ændringer anvendes dynamisk til kørende tjenester.
  • Tjenesteopdagelse: Hver kørende tjeneste skal registrere sig selv på en serveropdagelsesserver, og serveren fortæller alle, der er online. Ligesom en typisk chat-app. Vi vil ikke have en hårdkodetjenestepunktadresse ind i hinanden.
  • Belastningsbalancering: Belastningsbalancering på klientsiden, så du kan anvende komplekse balanceringsstrategier og udføre cache, batching, fejltolerance, serviceopdagelse og håndtere flere protokoller.
  • Inter-process kommunikation: Vi bliver nødt til at implementere en effektiv inter-process kommunikationsstrategi. Det kan være noget som REST eller sparsommelig eller asynkrone, meddelelsesbaserede kommunikationsmekanismer såsom AMQP eller STOMP. Vi kan også bruge effektive meddelelsesformater som Avro eller Protocol Buffere, da dette ikke bruges til at kommunikere med omverdenen.
  • Godkendelse og sikkerhed: Vi skal have et system til at identificere godkendelseskrav for hver ressource og afvise anmodninger, der ikke opfylder dem.
  • Ikke-blokerende IO: API Gateway håndterer anmodninger ved at påberåbe sig flere backend-tjenester og samle resultaterne. Med nogle anmodninger, f.eks. En produktinformation, er anmodningerne om backend-tjenester uafhængige af hinanden. For at minimere responstid skal API Gateway udføre uafhængige anmodninger samtidigt.
  • Eventuel konsistens: Vi er nødt til at have et system til at håndtere forretningstransaktioner, der spænder over flere tjenester. Når en service opdaterer sin database, skal den offentliggøre en begivenhed, og der skal være en meddelelsesmægler, der garanterer, at begivenheder leveres mindst en gang til abonnementstjenesterne.
  • Fejltolerance: Vi skal undgå situationen, hvor en enkelt fejl kaskader ind i en systemsvigt. API Gateway bør aldrig blokere på ubestemt tid og vente på en downstream-tjeneste. Det skal håndtere fejlbehag og med at returnere delvise svar, når det er muligt.
  • Distribuerede sessioner: Ideelt set bør vi ikke have nogen tilstand på serveren. Ansøgningstilstand skal gemmes på klientsiden. Det er et af de vigtige principper for en RESTful-tjeneste. Men hvis du har en undtagelse, og du ikke kan undgå den, skal du altid have distribuerede sessioner. Da klient kun kommunikerer med API Gateway, bliver vi nødt til at køre flere kopier af den bag en belastningsbalancer, fordi vi ikke ønsker, at API Gateway skal blive en flaskehals. Dette betyder, at klientens efterfølgende anmodninger kan lande på alle de kørende forekomster af API Gateway. Vi er nødt til at have en måde at dele godkendelsesinfo mellem forskellige tilfælde af API Gateway. Vi ønsker ikke, at klienten skal autentificere, hver gang dens anmodning falder på en anden forekomst af API Gateway.
  • Distribueret cache: Vi bør have cachemekanismer på flere niveauer for at reducere klientforsinkelse. Flere niveauer betyder simpelthen, klient, API Gateway og mikroservices skal hver have en pålidelig cachemekanisme.
  • Detaljeret overvågning: Vi skal kunne spore meningsfulde data og statistikker over hver funktionel komponent for at give os et nøjagtigt billede af produktionen. Korrekte alarmer skal udløses i tilfælde af undtagelser eller høje responstider.
  • Dynamisk routing: API Gateway skal være i stand til intelligent at rute anmodningerne til mikroservices, hvis den ikke har en bestemt kortlægning til den ønskede ressource. Med andre ord skal ændringer ikke kræves i API Gateway, hver gang en mikroservice tilføjer et nyt slutpunkt på sin side.
  • Automatisk skalering: Hver komponent i vores arkitektur inklusive API Gateway skal være vandret skalerbar og skal skaleres automatisk når det er nødvendigt, selvom det er placeret i en container.
  • Polyglot-understøttelse: Da forskellige mikroservices muligvis kan skrives på forskellige sprog eller rammer, skal systemet give glatte service-kald og ovennævnte funktioner uanset hvilket sprog det er skrevet på.
  • Glat implementering: Implementering af vores mikroservices skal være hurtig, uafhængig og automatiseret, hvis det er muligt.
  • Platform uafhængig: For at gøre effektiv brug af hardware og for at holde vores tjenester uafhængige af den platform, hvorpå den er implementeret, bør vi distribuere vores webservices i en eller anden container som docker.
  • Log-aggregering: Vi skulle have et system på plads, der automatisk holder aggregerende logfiler fra alle mikroservices til et filsystem. Disse logfiler kan muligvis bruges til forskellige analyser senere.

Whoa! Dette er mange funktioner, der skal implementeres bare for at tage sig af en arkitektur. Er dette virkelig det værd? Og svaret er "Ja". Mikroservicearkitekturen er kamptestet af virksomheder som Netflix, der alene bruger omkring 40% af verdens internet båndbredde.

I mit næste indlæg beskriver jeg, hvordan vi kan begynde at designe vores mikroservices. Følg med og følg mig hvor som helst på Youtube for opdateringer. Fred ud!