Praktiska arkitekturmönster

Hej, Habr!

I ljuset av den aktuella situationen pÄ grund av coronaviruset har ett antal internettjÀnster fÄtt en ökad belastning. Till exempel, en av detaljhandelskedjorna i Storbritannien stÀngde bara ner sin webbplats för onlinebestÀllningar,, eftersom kapaciteten inte rÀckte till. Och det Àr lÄngt ifrÄn alltid möjligt att snabba upp en server genom att helt enkelt lÀgga till kraftfullare utrustning, ÀndÄ mÄste vi bearbeta kundernas förfrÄgningar (eller sÄ gÄr de till konkurrenterna).

I den hÀr artikeln kommer jag kort att berÀtta om populÀra metoder som kan hjÀlpa till att skapa en snabb och motstÄndskraftig tjÀnst. Men bland de möjliga utvecklingsscheman har jag valt ut bara de som Àr lÀtta att anvÀnda.För varje punkt har du antingen redan fÀrdiga bibliotek, eller sÄ finns det möjlighet att lösa uppgiften med hjÀlp av en molnplattform.

Horisontell skalning

Det enklaste och mest kĂ€nda punkten. I allmĂ€nhet finns det tvĂ„ vanliga sĂ€tt att fördela belastningen — horisontell och vertikal skalning. I det första fallet lĂ„ter du tjĂ€nster arbeta parallellt, vilket fördelar belastningen mellan dem. I det andra bestĂ€ller du kraftfullare servrar eller optimerar koden.

Som exempel tar jag ett abstrakt molnlagringssystem för filer, det vill sÀga en analog till OwnCloud, OneDrive och sÄ vidare.

Den standardiserade bilden av en sÄdan struktur nedan visar bara systemets komplexitet. För vi mÄste pÄ nÄgot sÀtt synkronisera tjÀnsterna. Vad hÀnder om en anvÀndare sparar en fil frÄn sin surfplatta och sedan vill titta pÄ den frÄn sin telefon?

Praktiska arkitekturmönster
Skillnaden mellan metoderna: vid vertikal skalning Àr vi beredda att öka kapaciteten hos noderna, medan vi vid horisontell skalning lÀgger till nya noder för att fördela belastningen.

CQRS

Command Query Responsibility Segregation Àr ett ganska viktigt mönster eftersom det gör det möjligt för olika kunder att inte bara ansluta till olika tjÀnster, utan ocksÄ att fÄ samma flöden av hÀndelser. Dess fördelar Àr inte sÄ uppenbara för en enkel applikation, men den Àr extremt viktig (och enkel) för en belastad tjÀnst. KÀrnan i detta Àr att inkommande och utgÄende datastreamar inte bör korsas. Det vill sÀga att du inte kan skicka en förfrÄgan och förvÀnta dig ett svar, istÀllet skickar du en förfrÄgan till tjÀnst A men fÄr svaret i tjÀnst B.

Den första fördelen med detta tillvÀgagÄngssÀtt Àr möjligheten att avbryta anslutningen (i bred mening av ordet) under processen av ett lÄngvarigt begÀran. Som exempel kan vi ta en mer eller mindre standardiserad sekvens:

  1. Klienten skickade en begÀran till servern.
  2. Servern pÄbörjade en lÄngvarig bearbetning.
  3. Servern svarade klienten med resultatet.

TÀnk dig att det i punkt 2 intrÀffade ett avbrott i kommunikationen (antingen Äteranslöt nÀtverket, eller sÄ bytte anvÀndaren till en annan sida och avbröt anslutningen). I detta fall kommer det att vara svÄrt för servern att skicka svar till anvÀndaren med information om vad som faktiskt har behandlats. Genom att anvÀnda CQRS kommer sekvensen att vara nÄgot annorlunda:

  1. Klienten har prenumererat pÄ uppdateringar.
  2. Klienten skickade en begÀran till servern.
  3. Servern svarade 'begÀran mottagen'.
  4. Servern svarade med resultatet genom kanalen frÄn punkt '1'.

Praktiska arkitekturmönster

Som man kan se Àr schemat nÄgot mer komplext. Dessutom saknas det intuitiva tillvÀgagÄngssÀttet request-response hÀr. Men som man kan se kommer ett avbrott i kommunikationen under behandling av begÀran inte att leda till ett fel. Dessutom, om anvÀndaren verkligen Àr ansluten till tjÀnsten med flera enheter (till exempel med en mobiltelefon och en surfplatta), kan man se till att svaret kommer till bÄda enheterna.

Det intressanta Àr att koden för behandling av inkommande meddelanden blir densamma (inte till 100%) för bÄde hÀndelser som pÄverkats av sjÀlva klienten och för andra hÀndelser, inklusive frÄn andra kunder.

Men i praktiken fÄr vi ytterligare fördelar eftersom den enkla strömmen kan behandlas pÄ funktionellt sÀtt (med RX och liknande). Och det Àr redan en stor fördel, eftersom applikationen i stort sett kan göras helt reaktiv, dessutom med hjÀlp av ett funktionellt tillvÀgagÄngssÀtt. För mer omfattande program kan detta avsevÀrt spara resurser vid utveckling och underhÄll.

Om man kombinerar detta tillvÀgagÄngssÀtt med horisontell skalning fÄr vi bonusen av att kunna skicka begÀrningar till en server och fÄ svar frÄn en annan. PÄ sÄ sÀtt kan klienten sjÀlv vÀlja en tjÀnst som passar dem, och systemet kan pÄ insidan ÀndÄ korrekt bearbeta hÀndelser.

Event Sourcing

Som ni vet Ă€r en av de frĂ€msta egenskaperna hos ett distribuerat system avsaknaden av gemensam tid och gemensam kritisk sektion. För en enskild process kan du göra en synkronisering (pĂ„ samma mutexar), dĂ€r du Ă€r sĂ€ker pĂ„ att ingen annan kör den koden. Men för ett distribuerat system Ă€r detta riskabelt eftersom det skulle innebĂ€ra overhead och döda hela skönheten i skalbarhet – alla komponenter skulle Ă€ndĂ„ vĂ€nta pĂ„ varandra.

HĂ€r fĂ„r vi en viktig insikt – en snabb distribuerad system kan inte synkroniseras, för dĂ„ minskar vi prestandan. Å andra sidan behöver vi ofta viss koherens mellan komponenterna. För detta kan vi anvĂ€nda en metod med eventuell konsistens, dĂ€r det garanteras att, i avsaknad av dataĂ€ndringar under en viss tidsperiod efter den senaste uppdateringen, kommer alla förfrĂ„gningar att returnera det senaste uppdaterade vĂ€rdet.

Det Ă€r viktigt att förstĂ„ att strikt koherensofta tillĂ€mpas pĂ„ klassiska databaser, dĂ€r varje nod har identisk information (detta uppnĂ„s ofta nĂ€r en transaktion anses vara genomförd först efter ett svar frĂ„n den andra servern). HĂ€r finns vissa undantag pĂ„ grund av isoleringsnivĂ„er, men den övergripande essensen förblir densamma – du kan leva i en helt koherent vĂ€rld.

Men lÄt oss ÄtergÄ till den ursprungliga uppgiften. Om en del av systemet kan byggas med eventuell konsistens, sÄ kan vi bygga följande schema.

Praktiska arkitekturmönster

Viktiga egenskaper hos denna metod:

  • Varje inkommande förfrĂ„gan placeras i en kö.
  • Under behandlingen av förfrĂ„gan kan tjĂ€nsten ocksĂ„ placera uppgifter i andra köer.
  • Varje inkommande hĂ€ndelse har ett identifieringsnummer (som behövs för deduplication).
  • Kön fungerar ideologiskt enligt "append only"-modellen. Det gĂ„r inte att ta bort eller omorganisera element dĂ€rifrĂ„n.
  • Kön arbetar enligt FIFO (ursĂ€kta tautologin). Om det behövs för parallell bearbetning bör föremĂ„len flyttas till olika köer i en av stegen.

Jag pÄminner om att vi betraktar fallet av en online filserver. I detta fall kommer systemet att se ungefÀr ut sÄ hÀr:

Praktiska arkitekturmönster

Det Ă€r viktigt att tjĂ€nsterna i diagrammet inte nödvĂ€ndigtvis betyder enskilda servrar. Även processen kan vara densamma. Det viktiga Ă€r nĂ„got annat: ideologiskt Ă€r dessa saker Ă„tskilda pĂ„ ett sĂ„dant sĂ€tt att det enkelt gĂ„r att tillĂ€mpa horisontell skalning.

Och för tvÄ anvÀndare kommer schemat att se ut sÄ hÀr (tjÀnster avsedda för olika anvÀndare markeras med olika fÀrger):

Praktiska arkitekturmönster

Fördelarna med en sÄdan kombination:

  • TjĂ€nsterna för informationsbehandling Ă€r Ă„tskilda. Köerna Ă€r ocksĂ„ Ă„tskilda. Om vi behöver öka systemets genomströmning behöver vi bara starta fler tjĂ€nster pĂ„ fler servrar.
  • NĂ€r vi fĂ„r information frĂ„n en anvĂ€ndare behöver vi inte vĂ€nta pĂ„ att all data ska sparas helt. TvĂ€rtom, det rĂ€cker att vi svarar "ok", och sedan gradvis börja arbeta. Samtidigt jĂ€mnar kön ut toppar, eftersom tillĂ€gg av ett nytt objekt gĂ„r snabbt, och anvĂ€ndaren behöver inte vĂ€nta pĂ„ en fullstĂ€ndig genomgĂ„ng av hela cykeln.
  • Som exempel har jag lagt till en dedupliceringstjĂ€nst som försöker sammanfoga identiska filer. Om den jobbar lĂ€nge i 1 % av fallen kommer kunden praktiskt taget inte att mĂ€rka det (se ovan), vilket Ă€r en stor fördel, eftersom vi inte lĂ€ngre krĂ€ver hundra procent hastighet och tillförlitlighet.

Men nackdelarna blir ocksÄ tydliga:

  • VĂ„rt system har förlorat strikt konsistens. Det betyder att om man till exempel prenumererar pĂ„ olika tjĂ€nster kan man teoretiskt fĂ„ olika tillstĂ„nd (eftersom en av tjĂ€nsterna kan hinna ta emot meddelandet frĂ„n den interna kön). Som en annan följd har systemet nu ingen gemensam tid. Det gĂ„r alltsĂ„ inte att sortera alla hĂ€ndelser endast efter ankomsttid, eftersom klockorna mellan servrarna kan vara osynkroniserade (dessutom Ă€r samma tid pĂ„ tvĂ„ servrar en utopi).
  • Inga hĂ€ndelser kan nu helt enkelt Ă„terskapas (som skulle kunna göras med en databas). IstĂ€llet mĂ„ste man lĂ€gga till en ny hĂ€ndelse — compensation event, som kommer att Ă€ndra det senaste tillstĂ„ndet till det som krĂ€vs. Som ett exempel frĂ„n ett liknande omrĂ„de: utan att skriva om historien (vilket Ă€r negativt i vissa fall) kan man i git inte Ă„ngra en commit, men man kan göra en speciell rollback commit, som i grunden bara kommer att Ă„terstĂ€lla det gamla tillstĂ„ndet. Men i historiken kommer bĂ„de den felaktiga commit och rollback att sparas.
  • Dataschemat kan förĂ€ndras frĂ„n version till version, men gamla hĂ€ndelser kan nu inte uppdateras till den nya standarden (eftersom hĂ€ndelser i princip inte kan Ă€ndras).

Som vi ser passar Event Sourcing utmÀrkt med CQRS. Dessutom Àr det redan svÄrt att implementera ett system med effektiva och anvÀndarvÀnliga köer, utan att separera datastreams, eftersom vi dÄ mÄste lÀgga till synkroniseringspunkter som kommer att neutralisera hela den positiva effekten av köerna. NÀr man tillÀmpar bÄda tillvÀgagÄngssÀtten samtidigt mÄste koden för programmet justeras nÄgot. I vÄrt fall, nÀr en fil skickas till servern, fÄr vi bara "okej" som svar, vilket bara betyder att "filöverföringsoperationen har sparats". Formellt sett betyder det inte att datan redan Àr tillgÀnglig pÄ andra enheter (till exempel kan dedupliceringstjÀnsten omstrukturera indexet). Men efter en tid kommer klienten att fÄ en avisering i stil med "fil X har sparats".

Som ett resultat:

  • Antalet statuser för filöverföring ökar: istĂ€llet för den klassiska "fil skickad" fĂ„r vi tvĂ„: "fil tillagd i kön pĂ„ servern" och "fil sparad i lagringen". Det senare betyder att andra enheter redan kan börja ta emot filen (med förbehĂ„ll för att köerna arbetar med olika hastigheter).
  • Eftersom informationen om överföringen nu kommer via olika kanaler, mĂ„ste vi hitta lösningar för att fĂ„ status pĂ„ filbehandlingen. Som en följd av detta: i motsats till det klassiska request-response, kan klienten startas om under filbehandlingen, men statusen för just denna bearbetning kommer att vara korrekt. Detta punkt fungerar i praktiken "out of the box". Som en följd av detta Ă€r vi nu mer toleranta mot fel.

Sharding

Som redan beskrivits ovan, inom system med event sourcing finns det ingen strikt konsistens. Det betyder att vi kan anvÀnda flera lagringsutrymmen utan nÄgon synkronisering mellan dem. NÀr vi nÀrmar oss vÄr uppgift kan vi:

  • Dela upp filer efter typer. Till exempel kan bilder/video avkodas och ett mer effektivt format vĂ€ljas.
  • Separera konton efter lĂ€nder. PĂ„ grund av mĂ„nga lagar kan detta krĂ€vas, men denna arkitekturschema möjliggör automatiskt detta.

Praktiska arkitekturmönster

Om du vill överföra data frÄn en lagring till en annan behövs redan standardmetoder. TyvÀrr Àr det nödvÀndigt att stoppa kön, göra migrationen och sedan starta den igen. Generellt sett kan data inte överföras 'on-the-fly', men om hÀndelsekön lagras helt och du har kopior av tidigare tillstÄnd av lagringen kan vi Äterspela hÀndelser pÄ följande sÀtt:

  • I Event Source har varje hĂ€ndelse sitt eget identifierare (idealiskt — icke minskande). Det betyder att vi kan lĂ€gga till ett fĂ€lt i lagringen — id för den senaste behandlade posten.
  • Vi duplicerar kön sĂ„ att alla hĂ€ndelser kan behandlas för flera oberoende lagringar (den första Ă€r den dĂ€r data redan lagras, och den andra Ă€r ny, men för tillfĂ€llet tom). Den andra kön behandlas förstĂ„s inte för nĂ€rvarande.
  • Vi startar den andra kön (det vill sĂ€ga vi pĂ„börjar Ă„terspelningen av hĂ€ndelser).
  • NĂ€r den nya kön relativt sett Ă€r tom (dvs. den genomsnittliga tidsdifferensen mellan tillĂ€gg av ett objekt och dess uttag Ă€r acceptabel) kan vi börja vĂ€xla lĂ€sare till den nya lagringen.

Som vi kan se har vi i vÄrt system varken haft eller har strikt överensstÀmmelse. Det finns bara eventual consistency, det vill sÀga en garanti för att hÀndelser behandlas i samma ordning (men eventuellt med olika fördröjningar). Och genom att utnyttja detta kan vi relativt enkelt flytta data utan att stoppa systemet till andra sidan av jorden.

SÄledes, fortsÀtter vÄrt exempel om en online-lagring för filer, ger denna arkitektur oss redan ett antal fördelar:

  • Vi kan flytta objekt nĂ€rmare anvĂ€ndarna, och detta görs dynamiskt. PĂ„ sĂ„ sĂ€tt kan vi förbĂ€ttra kvaliteten pĂ„ tjĂ€nsten.
  • Vi kan lagra en del av data inom företagen. Till exempel krĂ€ver Enterprise-anvĂ€ndare ofta att lagra sina data i kontrollerade datacenter (för att förhindra dataintrĂ„ng). Genom sharding kan vi enkelt stödja detta. Och uppgiften förenklas ytterligare om kunden har en kompatibel molnlösning (till exempel, Azure self hosted).
  • Och det viktigaste Ă€r att vi kanske inte behöver göra det. För i början skulle ett enda lagringsutrymme för alla konton vara tillrĂ€ckligt (sĂ„ att vi snabbt kan börja arbeta). Och den centrala egenskapen hos detta system Ă€r, Ă€ven om det Ă€r skalbart, att det i början Ă€r ganska enkelt. Man behöver helt enkelt inte skriva kod som arbetar med en miljon separata oberoende köer och sĂ„ vidare direkt. Om det behövs kan det göras i framtiden.

Statisk InnehÄlls-hosting

Denna punkt kan verka helt uppenbar, men den Àr ÀndÄ nödvÀndig för ett mer eller mindre standardbelastat program. Dess kÀrna Àr enkel: allt statiskt innehÄll levereras inte frÄn samma server som applikationen, utan frÄn sÀrskilda servrar som Àr avsedda för detta. Som en följd av detta utförs dessa operationer snabbare (exempelvis levererar nginx filer snabbare och mer kostnadseffektivt Àn en Java-server). Dessutom möjliggör arkitekturen för CDN (InnehÄllsleveransnÀtverk) att placera vÄra filer nÀrmare slutanvÀndarna, vilket positivt pÄverkar anvÀndarvÀnligheten.

Det enklaste och mest standardiserade exemplet pĂ„ statiskt innehĂ„ll Ă€r en uppsĂ€ttning skript och bilder för en webbplats. Det Ă€r enkelt — de Ă€r kĂ€nda pĂ„ förhand, och arkivet laddas upp pĂ„ CDN-servrarna, frĂ„n vilka det distribueras till slutanvĂ€ndarna.

Men i verkligheten kan en nÄgot liknande metod som lambda-arkitekturen tillÀmpas för statiskt innehÄll. LÄt oss ÄtervÀnda till vÄrt uppdrag (ett online lagringsutrymme för filer), dÀr vi behöver distribuera filer till anvÀndarna. Den enklaste lösningen Àr att skapa en tjÀnst som för varje anvÀndarförfrÄgan gör alla nödvÀndiga kontroller (auktorisering och sÄ vidare) och sedan laddar ner filen direkt frÄn vÄrt lagringsutrymme. Huvudnackdelen med denna metod Àr att det statiska innehÄllet (och en fil med en specifik_revision fungerar i grunden som statiskt innehÄll) levereras av samma server som innehÄller affÀrslogiken. IstÀllet kan man göra följande schema:

  • Servern ger en URL för nedladdning. Den kan se ut som file_id + key, dĂ€r key Ă€r en miniatyrdigital signatur som ger tillgĂ„ng till resursen under de nĂ€rmaste 24 timmarna.
  • Nedladdningen av filen hanteras av en enkel nginx med följande alternativ:
    • Caching av innehĂ„ll. Eftersom den hĂ€r tjĂ€nsten kan vara pĂ„ en separat server har vi kvar en framtidslösning för att lagra alla de senaste nedladdade filerna pĂ„ disken.
    • Nyckelkontroll vid anslutningsskapande
  • Valfritt: strömmande bearbetning av innehĂ„ll. Till exempel, om vi komprimerar alla filer i tjĂ€nsten, kan vi göra dekomprimeringen direkt i denna modul. Som en följd: IO-operationer genomförs dĂ€r de hör hemma. En Java-arkivare kan lĂ€tt anvĂ€nda mycket extra minne, men att skriva om tjĂ€nsten med affĂ€rslogik pĂ„ rust/c++ kan visa sig vara ineffektivt. I vĂ„rt fall anvĂ€nds olika processer (eller till och med tjĂ€nster), sĂ„ vi kan effektivt separera affĂ€rslogik och IO-operationer.

Praktiska arkitekturmönster

Denna typ av schema liknar inte mycket pÄ distribution av statiskt innehÄll (eftersom vi inte laddar upp hela paketet med statik nÄgonstans), men i verkligheten hanterar denna metod just distribution av oförÀnderliga data. Dessutom kan detta schema generaliseras till andra fall dÀr innehÄllet inte bara Àr statiskt utan kan presenteras i form av en uppsÀttning oförÀnderliga och oelimineringbara block (Àven om de kan lÀggas till).

Som ett annat exempel (för att befÀsta): om du har arbetat med Jenkins/TeamCity vet du att bÄda lösningarna Àr skrivna i Java. BÄda Àr en Java-proces som hanterar bÄde orkestrering av byggen och innehÄllshantering. Specifikt har de bÄda uppgifter som "överföra fil/-mapp frÄn servern". Ett exempel: utfÀrdande av artefakter, överföring av kÀllkod (nÀr agenten inte laddar ner koden direkt frÄn repot utan det gör servern), Ätkomst till loggar. Alla dessa uppgifter skiljer sig i belastning pÄ IO. SÄledes blir det sÄ att servern, som ansvarar för komplex affÀrslogik, ocksÄ mÄste kunna effektivt hantera stora datamÀngder. Och det mest intressanta Àr att en sÄdan operation kan delegeras till nginx enligt exakt samma schema (med tillÀgget av nyckeldatan i begÀran).

Men om vi ÄtergÄr till vÄrt system, sÄ ser vi ett sÄdant schema:

Praktiska arkitekturmönster

Som det framgÄr har systemet blivit radikalt mer komplicerat. Det Àr inte lÀngre bara en mini-process som lagrar filer lokalt. Nu krÀvs inte bara enklare support, utan ocksÄ versionskontroll av API:er osv. DÀrför, efter att alla diagram har ritats, Àr det bÀst att noggrant övervÀga om kostnaderna för skalbarheten Àr vÀrda det. Men om du vill ha möjlighet att skala systemet (inklusive för att arbeta med Ànnu fler anvÀndare), mÄste du acceptera sÄdana lösningar. Som ett resultat Àr systemets arkitektur redo för ökad belastning (nÀstan varje komponent kan klonas för horisontell skalning). Systemet kan uppdateras utan att stÀngas ner (enbart vissa operationer kan bromsas nÄgot).

Som jag nÀmnde i början, har flera internet-tjÀnster nu fÄtt ökad belastning. Och vissa av dem har helt enkelt slutat fungera korrekt. I sjÀlva verket misslyckades systemen precis nÀr företagen skulle börja tjÀna pengar. Det vill sÀga, istÀllet för att skjuta fram leveranser, istÀllet för att erbjuda kunderna "planera leveranser över de kommande mÄnaderna", sa systemet helt enkelt "gÄ till konkurrenterna". Och det Àr kostnaden för lÄg prestanda: förluster kommer att intrÀffa precis nÀr vinsten skulle vara som högst.

Slutsats

Alla dessa tillvÀgagÄngssÀtt har varit kÀnda tidigare. VK har lÀnge anvÀnt idén om Static Content Hosting för att leverera bilder. MÄnga online-spel anvÀnder en Sharding-plan för att dela upp spelare efter regioner eller för att dela upp spelmiljöer (om vÀrlden Àr en enhet). Event Sourcing-ansatsen anvÀnds aktivt inom e-post. De flesta handelsapplikationer, dÀr data kontinuerligt kommer in, Àr faktiskt byggda pÄ CQRS-ansatsen för att kunna filtrera den mottagna datan. Och horisontell skalning har anvÀnts lÀnge i mÄnga tjÀnster.

Men det viktigaste Àr att alla dessa mönster har blivit mycket lÀtta att tillÀmpa i moderna applikationer (om de Àr relevanta, förstÄs). MolntjÀnster erbjuder sharding och horisontell skalning som standard, vilket Àr mycket enklare Àn att bestÀlla olika dedikerade servrar i olika datacenter sjÀlv. CQRS har blivit mycket enklare, delvis tack vare utvecklingen av bibliotek som RX. För tio Är sedan skulle en sÀllsynt webbplats kunna stödja sÄdana saker. Event sourcing stÀller in sig ocksÄ otroligt lÀtt tack vare redan fÀrdiga containrar med Apache Kafka. För tio Är sedan hade detta varit en innovation, nu Àr det en sjÀlvklarhet. PÄ samma sÀtt med statisk innehÄllshosting: pÄ grund av mer anvÀndarvÀnlig teknik (dÀribland för att det finns detaljerad dokumentation och en stor databas med svar), har denna metod blivit Ànnu enklare.

Som en följd har implementeringen av ett antal ganska komplexa arkitektoniska mönster nu blivit mycket enklare, vilket gör att man bör se nÀrmare pÄ dem i förvÀg. Om ett tioÄrigt program övergav en av de ovanstÄende lösningarna pÄ grund av höga kostnader för implementering och drift, kan man nu, i ett nytt program, eller efter refaktorisering, skapa en tjÀnst som arkitektoniskt redan kommer att vara bÄde skalbar (med avseende pÄ prestanda) och redo för nya krav frÄn kunderna (till exempel för lokalisering av personuppgifter).

Och det viktigaste: anvÀnd inte dessa metoder om ni har en enkel applikation. Ja, de Àr vackra och intressanta, men för en webbplats med en toppbesökare pÄ 100 personer kan man ofta klara sig med en klassisk monolit (Ätminstone utvÀndigt, internt kan allt brytas ner i moduler etc.).

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster