Praktični arhitektonski obrasci

Bok, Habr!

S obzirom na trenutne događaje uzrokovane koronavirusom, nekoliko internetskih usluga počelo je primati veće opterećenje. Na primjer, jedna trgovačka mreža u Ujedinjenom Kraljevstvu jednostavno je obustavila web stranicu s online narudžbama, jer nije bilo dovoljnih resursa. A nije uvijek moguće ubrzati server jednostavno dodavanjem moćnijih uređaja, međutim, zahtjeve klijenata treba obraditi (ili će otići konkurentima).

U ovom članku ću ukratko opisati popularne prakse koje omogućuju brzu i otpornu uslugu. Međutim, od svih mogućih shema razvoja odabrao sam samo one koje su trenutno lako dostupne.Za svaku točku već imate pripremljene biblioteke ili mogućnost rješavanja zadatka putem oblačne platforme.

Horizontalno skaliranje

Najjednostavnija i svima poznata točka. U principu, najčešće se susreću dva schematska pristupa raspodjeli opterećenja — horizontalno i vertikalno skaliranje. U prvom slučaju dopuštate uslugama da rade paralelno, čime se raspoređuje opterećenje među njima. U drugom naručujete moćnije servere ili optimizirate kod.

Za primjer ću uzeti apstraktno oblačno pohranjivanje datoteka, odnosno neki analog OwnCloud, OneDrive i slično.

Standardna slika takve sheme je ispod, međutim ona samo prikazuje složenost sustava. Naime, potrebno je na neki način sinkronizirati usluge. Što će se dogoditi ako korisnik na tabletu spremi datoteku, a zatim je želi pregledati s telefona?

Praktični arhitektonski obrasci
Razlika između pristupa: u vertikalnom skaliranju spremni smo povećati snagu čvorova, dok u horizontalnom dodajemo nove čvorove kako bismo rasporedili opterećenje.

CQRS

Razdvajanje odgovornosti za naredbe i upite je prilično važan obrazac, jer omogućuje različitim klijentima da se povežu s različitim uslugama, ali i da primaju iste tokove događaja. Njegove prednosti nisu toliko očite za jednostavne aplikacije, međutim, iznimno su važne (i jednostavne) za opterećene usluge. Njegova suština: ulazni i izlazni tokovi podataka ne smiju se prekrižiti. To jest, ne možete poslati zahtjev i očekivati odgovor, umjesto toga šaljete zahtjev u uslugu A, ali dobivate odgovor u usluzi B.

Prva prednost ovog pristupa je mogućnost prekida veze (u širokom smislu te riječi) tijekom izvršavanja dugog zahtjeva. Kao primjer, uzmimo prilično standardnu sekvencu:

  1. Klijent je poslao zahtjev serveru.
  2. Server je pokrenuo dug proces obrade.
  3. Server je odgovorio klijentu rezultatom.

Zamislimo da je u koraku 2 došlo do prekida veze (ili se mreža ponovno povezala, ili je korisnik prešao na drugu stranicu, prekinuvši vezu). U tom će slučaju serveru biti teško poslati odgovor korisniku s informacijama o tome što je konkretno obrađeno. Primjenom CQRS-a, sekvenca će biti malo drugačija:

  1. Klijent se pretplatio na ažuriranja.
  2. Klijent je poslao zahtjev serveru.
  3. Server je odgovorio "zahtjev primljen".
  4. Server je odgovorio rezultatom putem kanala iz točke "1".

Praktični arhitektonski obrasci

Kao što se može vidjeti, shema je malo složenija. Štoviše, intuitivni pristup zahtjev-odgovor ovdje nedostaje. Međutim, kao što se može vidjeti, prekid veze tijekom obrade zahtjeva neće uzrokovati grešku. Štoviše, ako je korisnik zapravo povezan na servis s nekoliko uređaja (na primjer, s mobilnog telefona i s tableta), može se omogućiti da odgovor stigne na oba uređaja.

Ono što je zanimljivo, kod obrade dolaznih poruka postaje gotovo identičan (ne 100%) za događaje na koje utječe sam klijent, kao i za ostale događaje, uključujući one od drugih klijenata.

Međutim, u stvarnosti dobivamo dodatne prednosti s obzirom na to da se jednosmjerni tok može obraditi u funkcionalnom stilu (koristeći RX i slične). I to je već ozbiljna prednost, jer se u osnovi aplikacija može učiniti potpuno reaktivnom, još s primjenom funkcionalnog pristupa. Za složene programe to može značajno uštedjeti resurse za razvoj i podršku.

Ako spojimo ovaj pristup s horizontalnim skaliranjem, kao bonus dobivamo mogućnost slanja zahtjeva na jedan server, a primanja odgovora s drugog. Tako klijent može odabrati najprikladniju uslugu, a sustav unutar nje i dalje može ispravno obraditi događaje.

Event Sourcing

Kao što znate, jedna od osnovnih karakteristika distribuiranog sistema je odsutnost zajedničkog vremena i zajedničke kritične sekcije. Za jedan proces možete izvršiti sinhronizaciju (na istim mutexima), unutar koje ste sigurni da niko drugi ne izvršava taj kod. Međutim, za distribuirani sistem nešto slično je opasno, jer će to zahtevati dodatne troškove, a takođe će pokvariti svu čar obrade - svi će komponenti u svakom slučaju čekati jednog.

Odatle dolazimo do važnog zaključka - brzi distribuirani sistem ne može biti sinhronizovan, jer bismo tada smanjili performanse. S druge strane, često imamo potrebu za određenom doslednošću komponenti. I za to možemo koristiti pristup s eventual consistency, gde je garantirano da u odsustvu izmene podataka kroz određeni period nakon poslednjeg ažuriranja ("na kraju") svi zahtevi će vraćati poslednju ažuriranu vrednost.

Važno je razumeti da se za klasične baze podataka prilično često primenjuje stroga doslednost, gde svaki čvor poseduje iste informacije (to se često postiže u slučaju kada se transakcija smatra postavljenom tek nakon odgovora drugog servera). Postoje neka olakšanja zbog nivoa izolacije, ali suština ostaje ista - možete živeti u potpuno doslednom svetu.

Međutim, vratimo se na prvobitni zadatak. Ako deo sistema može biti izgrađen sa eventual consistency, onda se može izraditi sledeća shema.

Praktični arhitektonski obrasci

Važne karakteristike ovog pristupa:

  • Svaki dolazni zahtev se stavlja u jedan red.
  • Tijekom obrade zahtjeva, usluga može takođe stavljati zadatke u druge redove.
  • Svaki dolazni događaj ima identifikator (koji je potreban za deduplikaciju).
  • Red ideološki radi po shemi "samo dodavanje". Iz njega nije moguće brisati elemente ili ih premestiti.
  • Red radi po FIFO principu (izvini na tautologiji). Ako je potrebno izvršiti paralelno izvršavanje, tada bi trebalo u jednoj od faza premestiti objekte u različite redove.

Podsećam vas da razmatramo slučaj online skladištenja datoteka. U ovom slučaju, sistem će izgledati otprilike ovako:

Praktični arhitektonski obrasci

Važno je napomenuti da usluge na dijagramu ne moraju nužno značiti zaseban poslužitelj. Čak i proces može biti isti. Ono što je bitno je da su ideološki ove stvari razdvojene na način koji omogućava lako primjenjivo horizontalno skaliranje.

Za dva korisnika shema će izgledati ovako (usluge namijenjene različitim korisnicima označene su različitim bojama):

Praktični arhitektonski obrasci

Prednosti ovakve kombinacije:

  • Usluge obrade informacija su razdvojene. Redovi su također razdvojeni. Ako trebamo povećati propusnost sustava, samo trebamo pokrenuti više usluga na većem broju poslužitelja.
  • Kada primamo informacije od korisnika, ne moramo čekati potpuno snimanje podataka. Naprotiv, dovoljno je da odgovorimo s "ok", a zatim postupno započnemo rad. Pritom red smanjuje vrhove, jer se dodavanje novog objekta odvija brzo, a korisnik ne mora čekati cijeli ciklus.
  • Za primjer sam dodao uslugu deduplikacije koja pokušava spojiti iste datoteke. Ako ona dugo radi u 1% slučajeva, korisnik to praktički neće primijetiti (vidi gore), što je veliki plus jer ne zahtijevamo 100% brzinu i pouzdanost.

Međutim, odmah su vidljivi i nedostaci:

  • Naš sustav je izgubio strogu dosljednost. To znači da, na primjer, ako se pretplatimo na različite usluge, teoretski možemo dobiti različito stanje (jer jedan od servisa možda neće stići primiti obavijest iz unutarnjeg reda). Kao drugo, sustav više nema zajedničko vrijeme. To jest, ne možemo, na primjer, sortirati sve događaje jednostavno prema vremenu dolaska, jer sati između poslužitelja možda nisu sinkronizirani (više od toga, isto vrijeme na dva poslužitelja je utopija).
  • Nikakvi događaji se više ne mogu jednostavno poništiti (kao što se može učiniti s bazom podataka). Umjesto toga potrebno je dodati novi događaj — kompenzacijski događaj, koji će promijeniti posljednje stanje u potrebno. Kao primjer iz sličnog područja: bez prepisivanja povijesti (što je loše u nekim slučajevima) u gitu ne može se poništiti commit, ali se može napraviti posebni rollback commit, koji će u suštini jednostavno vratiti staro stanje. Međutim, u povijesti će ostati i pogrešni commit i rollback.
  • Shema podataka može se mijenjati od izdanja do izdanja, no stari događaji sada se ne mogu ažurirati na novi standard (jer se događaji u načelu ne mogu mijenjati).

Kao što se može vidjeti, Event Sourcing se odlično uklapa s CQRS-om. Štoviše, implementacija sustava s učinkovitim i praktičnim redovima, ali bez odvajanja tokova podataka, sama po sebi je teška, jer će morati dodavati točke sinkronizacije koje će neutralizirati cijeli pozitivan učinak redova. Primjenom oba pristupa istovremeno, potrebno je neznatno prilagoditi kod rada programa. U našem slučaju, prilikom slanja datoteke na server, u odgovoru dolazi samo "ok", što znači da je "operacija dodavanja datoteke spremljena". Formalno, to ne znači da su podaci već dostupni na drugim uređajima (na primjer, usluga deduplikacije može rekonstrukcijom rekonstruirati indeks). Međutim, nakon nekog vremena klijent će primiti obavijest u stilu "datoteka X spremljena".

Kao rezultat:

  • Broj statusa slanja datoteka se povećava: umjesto klasičnog "datoteka poslana" dobivamo dva: "datoteka dodana u red na serveru" i "datoteka spremljena u skladištu". Potonje znači da drugi uređaji već mogu početi primati datoteku (s napomenom da redovi rade različitom brzinom).
  • Zbog toga što informacije o slanju sada dolaze putem različitih kanala, moramo smisliti rješenja za dobivanje statusa obrade datoteke. Kao posljedica toga: za razliku od klasičnog request-response modela, klijent može biti ponovno pokrenut tijekom obrade datoteke, a status same obrade će biti točan. Ovaj aspekt funkcioniše, u suštini, iz kutije. Kao posljedica: sada smo tolerantniji na greške.

Sharding

Kao što je već opisano, u sustavima s event sourcingom nema stroge dosljednosti. To znači da možemo koristiti više skladišta bez ikakve sinkronizacije među njima. Približavajući se našem zadatku, možemo:

  • Dijeliti datoteke po vrstama. Na primjer, slike/videozapis možemo dekodirati i odabrati učinkovitiji format.
  • Razdvojite račune prema državama. Zbog mnogih zakona, ovakva mogućnost može biti potrebna, no ova arhitektura omogućuje automatsko ostvarenje te opcije.

Praktični arhitektonski obrasci

Ako želite prebaciti podatke iz jedne pohrane u drugu, standardna sredstva neće biti dovoljna. Nažalost, u tom slučaju morate zaustaviti red, izvršiti migraciju, a zatim ga ponovno pokrenuti. Općenito, podaci se ne mogu prebaciti 'u hodu', međutim, ako se red događaja potpuno pohranjuje i imate snimke prethodnih stanja pohrane, možemo ponovno reproducirati događaje na sljedeći način:

  • U Event Source svaki događaj ima svoj identifikator (idealno — ne smanjujući se). To znači da možemo dodati polje u pohranu — id posljednjeg obrađenog elementa.
  • Dubliciramo red kako bi se svi događaji mogli obrađivati za nekoliko neovisnih pohrana (prvi je onaj u kojem se trenutno pohranjuju podaci, a drugi je novi, koji je trenutno prazan). Drugi red, naravno, još nije obrađen.
  • Pokrećemo drugi red (tj. počinjemo s ponovnim reprodukcijom događaja).
  • Kada novi red postane relativno prazan (tj. prosječna razlika u vremenu između dodavanja elementa i njegovog vađenja bude prihvatljiva), možemo početi prebacivati čitatelje na novu pohranu.

Kao što se može vidjeti, u našem sustavu nije bilo i nema stroge dosljednosti. Postoji samo eventualna dosljednost, što znači jamstvo da se događaji obrađuju u istom redoslijedu (međutim, moguće je s različitim odgodama). Koristeći to, možemo relativno lako prenijeti podatke bez zaustavljanja sustava na drugu stranu svijeta.

Tako, nastavljajući naš primjer o online pohrani za datoteke, ova arhitektura nam već donosi niz prednosti:

  • Možemo premještati objekte bliže korisnicima, na dinamičan način. Time možemo poboljšati kvalitetu usluge.
  • Možemo pohraniti dio podataka unutar kompanija. Na primjer, Enterprise korisnici često zahtijevaju pohranu svojih podataka u kontroliranim podatkovnim centrima (kako bi se izbjegla curenja podataka). Kroz sharding, možemo lako podržati ovu opciju. A zadatak je još jednostavniji ako klijent ima kompatibilnu oblačnu infrastrukturu (na primjer, Azure self hosted).
  • A najvažnije je da to ne moramo raditi. Za početak, sasvim bi nam odgovaralo jedno skladište za sve račune (kako bismo brže počeli raditi). Ključna karakteristika ovog sustava je da, iako je proširiv, u početnoj fazi je dovoljno jednostavan. Jednostavno ne trebate odmah pisati kod koji radi s milijun odvojenih neovisnih redova itd. Ako zatreba, to se može učiniti u budućnosti.

Hosting statičkog sadržaja

Ova točka može se činiti potpuno očitom, ali je svejedno potrebna za više-manje standardnu opterećenu aplikaciju. Njegova suština je jednostavna: sav statički sadržaj ne distribuira se s istog poslužitelja na kojem se nalazi aplikacija, već s posebnih poslužitelja, namijenjenih isključivo za tu svrhu. Kao rezultat, ove operacije se izvode brže (hipotetski, nginx isporučuje datoteke brže i uz manje troškove od Java poslužitelja). Osim toga, arhitektura CDN (Mreža za isporuku sadržaja) omogućava nam da smjestimo naše datoteke bliže krajnjim korisnicima, što pozitivno utječe na upotrebljivost usluge.

Najjednostavniji i standardni primjer statičkog sadržaja je skup skripti i slika za web stranicu. S njima je sve jednostavno — poznate su unaprijed, zatim se arhiva učitava na CDN poslužitelje, odakle se distribuiraju krajnjim korisnicima.

Međutim, u praksi se za statički sadržaj može primijeniti pristup sličan lambda arhitekturi. Vratimo se našem zadatku (online skladište datoteka) u kojem trebamo isporučivati datoteke korisnicima. Najjednostavnije rješenje je izraditi uslugu koja za svaki korisnički zahtjev izvršava sve potrebne provjere (autentifikacija itd.), a zatim preuzima datoteku izravno iz našeg skladišta. Glavni nedostatak ovog pristupa je to što se statički sadržaj (a datoteka s određenom revizijom, u suštini, jest statički sadržaj) isporučuje s istog poslužitelja koji sadrži poslovnu logiku. Umjesto toga, moguće je stvoriti sljedeću shemu:

  • Poslužitelj generira URL za preuzimanje. Može biti u obliku file_id + key, gdje je key mini-digitalni potpis koji daje pravo na pristup resursu sljedeća 24 sata.
  • Isporuku datoteke obavlja jednostavan nginx s sljedećim opcijama:
    • Keširanje sadržaja. Budući da ova usluga može biti na zasebnom serveru, ostavili smo prostor za budućnost s mogućnošću pohrane svih posljednjih preuzetih datoteka na disku.
    • Provjera ključa u trenutku uspostavljanja veze
  • Opcionalno: streaming sadržaja. Na primjer, ako komprimiramo sve datoteke u servisu, moguće je izvršiti dekompresiju direktno u ovom modulu. Kao posljedica: IO operacije se obavljaju tamo gdje im je mjesto. Arhivator napisan u Javi lako može zauzeti puno dodatne memorije, no prepisivanje servisa s poslovnom logikom na uvjetni Rust/C++ može se također pokazati neučinkovitim. U našem slučaju koristimo različite procese (ili čak servise), pa možemo prilično učinkovito odvojiti poslovnu logiku i IO operacije.

Praktični arhitektonski obrasci

Slična shema ne izgleda kao raspodjela statičkog sadržaja (budući da ne prebacujemo cijeli paket statičkih datoteka negdje), međutim u stvarnosti ovaj pristup se upravo bavi raspodjelom nepromjenjivih podataka. Štoviše, ovu shemu možemo generalizirati i na druge slučajeve kada sadržaj nije samo statičan, već se može predstaviti kao skup nepromjenjivih i neizbrisivih blokova (iako se mogu dodavati).

Kao dodatni primjer (za učvršćivanje): ako ste radili s Jenkins/TeamCity, znate da su oba rješenja napisana u Javi. Oba predstavljaju Java proces koji se bavi kako orkestracijom buildova, tako i upravljanjem sadržajem. Konkretno, oba imaju zadatke poput "predati datoteku/mape s servera". Kao primjer: isporuka artefakata, prijenos izvornog koda (kada agent ne preuzima kod direktno iz repozitorija, već to radi server), pristup logovima. Svi ovi zadaci se razlikuju po opterećenju na IO. Dakle, ispada da server koji se bavi složenom poslovnom logikom također mora učinkovito propuštati velike količine podataka. I što je najzanimljivije, takvu operaciju možemo delegirati istom nginx-u po istoj shemi (samo što u zahtjev treba dodati ključ podataka).

Međutim, ako se vratimo našoj sustavu, dobijamo sličnu shemu:

Praktični arhitektonski obrasci

Kao što se može vidjeti, sustav je radikalno zakomplikan. Sada to nije samo mini-proces koji pohranjuje datoteke lokalno. Sada su potrebni i kompleksnija podrška, upravljanje verzijama API-a itd. Stoga, nakon što su sve dijagrame nacrtane, najbolje je detaljno procijeniti isplati li se slična proširivost. No, ako želite imati mogućnost proširivanja sustava (uključujući i rad s većim brojem korisnika), morat ćete se odlučiti za slična rješenja. Kao rezultat, arhitektura sustava je spremna za povećanje opterećenja (praktički, svaki komponent može se klonirati za horizontalno skaliranje). Sustav se može ažurirati bez njegovog zaustavljanja (samo će neke operacije neznatno usporiti).

Kao što sam već rekao na početku, sada niz internetskih usluga doživljava povećano opterećenje. A neke od njih jednostavno prestaju ispravno raditi. U suštini, sustavi su otkazali točno u trenutku kada bi posao trebao donositi novac. To znači da umjesto odgođene isporuke, umjesto ponude klijentima da 'planiraju isporuke za sljedećih nekoliko mjeseci', sustav jednostavno kaže 'idite konkurentima'. Uvijeno, to je cijena niske performanse: gubici će se dogoditi upravo kada bi profit bio najviši.

Zaključak

Svi ti pristupi bili su poznati i ranije. Isti VK već dugo koristi ideju Static Content Hosting za isporuku slika. Mnoštvo online igara koristi sharding shemu za razdvajanje igrača po regijama ili za razdvajanje igračkih lokacija (ako je svijet jedinstven). Event sourcing pristup aktivno se koristi u elektroničkoj pošti. Većina aplikacija za trgovce, gdje kontinuirano dolaze podaci, zapravo je izgrađena na CQRS pristupu, kako bi se mogla filtrirati primljeni podaci. Pa, horizontalno skaliranje već dugo se primjenjuje u mnogim uslugama.

Međutim, ono što je najvažnije, svi ovi obrasci postali su vrlo lako primjenjivi u modernim aplikacijama (ako su naravno primjereni). Oblaci nude Sharding i horizontalno skaliranje odmah, što je puno lakše nego naručivanje različitih namjenskih poslužitelja u različitim podatkovnim centrima ručno. CQRS je postao mnogo lakši, barem zbog razvoja knjižnica kao što je RX. Prije 10 godina rijetka web stranica mogla bi podržati takvo što. Event Sourcing također se configure veoma lako zahvaljujući već gotovim kontejnerima s Apache Kafka. Prije 10 godina to bi bila inovacija, sada je to svakodnevica. Slično vrijedi i za Static Content Hosting: zbog praktičnijih tehnologija (uključujući i to što postoji detaljna dokumentacija i velika baza odgovora), sličan pristup postao je još jednostavniji.

Kao rezultat, implementacija niza prilično složenih arhitektonskih obrazaca sada je mnogo jednostavnija, što znači da ih treba bolje unaprijed proučiti. Ako je u desetogodišnjoj aplikaciji odbačeno jedno od rješenja iznad zbog visoke cijene implementacije i održavanja, sada, u novoj aplikaciji, ili nakon refaktoringa, moguće je napraviti uslugu koja će arhitektonski već biti i proširiva (s aspekta performansi), i spremna za nove zahtjeve klijenata (npr. za lokalizaciju osobnih podataka).

I najvažnije: molimo vas, nemojte koristiti ove pristupe ako imate jednostavnu aplikaciju. Da, oni su lijepi i zanimljivi, međutim za stranicu s maksimalnim posjetom od 100 ljudi često se može zadovoljiti klasičnim monolitom (barem izvana, iznutra se sve može podijeliti na module itd.).

Izvor: habr.com

Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere 🔥 Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere | ProHoster