Patogūs architektūriniai raštai

Sveiki, Habr!

Atsižvelgiant į dabartinius įvykius dėl koronaviruso, daugelis interneto paslaugų pradėjo gauti didesnį apkrovą. Pavyzdžiui, Vienas iš JK mažmeninės prekybos tinklų tiesiog sustabdė savo internetinių užsakymų svetainę., nes nebuvo pakankamai talpos. Ir ne visada įmanoma pagreitinti serverį tiesiog pridedant galingesnę įrangą, tačiau klientų užklausos turi būti apdorotos (arba jos atiteks konkurentams).

Šiame straipsnyje trumpai papasakosiu apie populiarias praktikas, kurios leis sukurti greitą ir gedimams atsparią paslaugą. Tačiau iš galimų plėtros schemų pasirinkau tik tas, kurios yra šiuo metu paprasta naudoti. Kiekvienam elementui turite paruoštas bibliotekas arba turite galimybę išspręsti problemą naudodami debesies platformą.

Horizontalus mastelio keitimas

Paprasčiausias ir labiausiai žinomas punktas. Tradiciškai labiausiai paplitusios dvi apkrovos paskirstymo schemos yra horizontalus ir vertikalus mastelio keitimas. Pirmuoju atveju leidžiate paslaugoms veikti lygiagrečiai, taip paskirstydami apkrovą tarp jų. Antrojoje užsakote galingesnius serverius arba optimizuojate kodą.

Pavyzdžiui, paimsiu abstrakčią debesies failų saugyklą, tai yra, tam tikrą OwnCloud, OneDrive ir tt analogą.

Žemiau pateikiamas standartinis tokios grandinės paveikslėlis, tačiau jis tik parodo sistemos sudėtingumą. Juk reikia kažkaip sinchronizuoti paslaugas. Kas atsitiks, jei vartotojas išsaugo failą iš planšetinio kompiuterio ir nori jį peržiūrėti telefone?

Patogūs architektūriniai raštai
Skirtumas tarp požiūrių: vertikalaus mastelio keitimo metu esame pasiruošę padidinti mazgų galią, o horizontaliajame mastelyje esame pasirengę pridėti naujų mazgų, kad paskirstytume apkrovą.

CQRS

Komandos užklausos atsakomybės atskyrimas Gana svarbus modelis, nes leidžia skirtingiems klientams ne tik prisijungti prie skirtingų paslaugų, bet ir gauti tuos pačius įvykių srautus. Jo pranašumai nėra tokie akivaizdūs naudojant paprastą programą, tačiau ji yra labai svarbi (ir paprasta) užimtai paslaugai. Jo esmė: įeinantys ir išeinantys duomenų srautai neturėtų susikirsti. Tai yra, jūs negalite išsiųsti užklausos ir tikėtis atsakymo; vietoj to siunčiate užklausą tarnybai A, bet gaunate atsakymą iš tarnybos B.

Pirmasis šio požiūrio pranašumas yra galimybė nutraukti ryšį (plačiąja šio žodžio prasme) vykdant ilgą užklausą. Pavyzdžiui, paimkime daugiau ar mažiau standartinę seką:

  1. Klientas išsiuntė užklausą serveriui.
  2. Serveris pradėjo ilgą apdorojimo laiką.
  3. Serveris atsakė klientui su rezultatu.

Įsivaizduokime, kad 2 punkte ryšys nutrūko (arba tinklas vėl prisijungė, arba vartotojas nuėjo į kitą puslapį, nutraukdamas ryšį). Tokiu atveju serveriui bus sunku išsiųsti vartotojui atsakymą su informacija apie tai, kas tiksliai buvo apdorota. Naudojant CQRS, seka bus šiek tiek kitokia:

  1. Klientas užsiprenumeravo atnaujinimus.
  2. Klientas išsiuntė užklausą serveriui.
  3. Serveris atsakė „užklausa priimta“.
  4. Serveris atsakė rezultatu per kanalą iš taško „1“.

Patogūs architektūriniai raštai

Kaip matote, schema yra šiek tiek sudėtingesnė. Be to, čia trūksta intuityvaus užklausos ir atsakymo metodo. Tačiau, kaip matote, ryšio pertrauka apdorojant užklausą nesukels klaidos. Be to, jei iš tikrųjų vartotojas yra prisijungęs prie paslaugos iš kelių įrenginių (pavyzdžiui, iš mobiliojo telefono ir planšetinio kompiuterio), galite įsitikinti, kad atsakymas ateina į abu įrenginius.

Įdomu tai, kad gaunamų pranešimų apdorojimo kodas tampa vienodas (ne 100%) tiek įvykiams, kuriems įtakos turėjo pats klientas, tiek kitiems įvykiams, įskaitant ir kitų klientų.

Tačiau realiai mes gauname papildomą premiją dėl to, kad vienakryptis srautas gali būti tvarkomas funkciniu stiliumi (naudojant RX ir panašiai). Ir tai jau yra rimtas pliusas, nes iš esmės programa gali būti visiškai reaktyvi, taip pat naudojant funkcinį požiūrį. Riebalų programų atveju tai gali žymiai sutaupyti plėtros ir paramos išteklių.

Jei šį metodą derinsime su horizontaliu mastelio keitimu, tada kaip premiją gausime galimybę siųsti užklausas į vieną serverį ir gauti atsakymus iš kito. Taigi klientas gali pasirinkti jam patogią paslaugą, o viduje esanti sistema vis tiek galės teisingai apdoroti įvykius.

Renginių tiekimas

Kaip žinote, viena iš pagrindinių paskirstytos sistemos ypatybių yra bendro laiko, bendros kritinės dalies nebuvimas. Vienam procesui galite atlikti sinchronizavimą (tais pačiais mutexais), kurių metu esate tikri, kad niekas kitas šio kodo nevykdo. Tačiau tai pavojinga paskirstytai sistemai, nes tai pareikalaus papildomų išlaidų, be to, sunaikins visą mastelio keitimo grožį - visi komponentai vis tiek lauks.

Iš čia gauname svarbų faktą – greitai paskirstytos sistemos negalima sinchronizuoti, nes tada sumažinsime našumą. Kita vertus, mums dažnai reikia tam tikro komponentų nuoseklumo. Ir tam galite naudoti metodą su galutinis nuoseklumas, kur garantuojama, kad jei po paskutinio atnaujinimo tam tikrą laikotarpį nebus jokių duomenų pasikeitimų („galų gale“), visos užklausos grąžins paskutinę atnaujintą reikšmę.

Svarbu suprasti, kad klasikinėms duomenų bazėms jis naudojamas gana dažnai stipri konsistencija, kur kiekvienas mazgas turi tą pačią informaciją (tai dažnai pasiekiama tuo atveju, kai sandoris laikomas įvykdytu tik po to, kai atsako antrasis serveris). Čia yra šiek tiek atsipalaidavimo dėl izoliacijos lygių, tačiau bendra mintis išlieka ta pati – galima gyventi visiškai harmonizuotame pasaulyje.

Tačiau grįžkime prie pradinės užduoties. Jei dalį sistemos galima sukurti naudojant galutinis nuoseklumas, tada galime sudaryti tokią diagramą.

Patogūs architektūriniai raštai

Svarbios šio metodo savybės:

  • Kiekviena gaunama užklausa dedama į vieną eilę.
  • Apdorodama užklausą, paslauga taip pat gali įdėti užduotis į kitas eiles.
  • Kiekvienas gaunamas įvykis turi identifikatorių (kuris būtinas norint panaikinti dubliavimą).
  • Eilė ideologiškai veikia pagal „pridėti tik“ schemą. Negalite iš jo pašalinti elementų arba jų pertvarkyti.
  • Eilė veikia pagal FIFO schemą (atsiprašau už tautologiją). Jei jums reikia atlikti lygiagretų vykdymą, tada viename etape turėtumėte perkelti objektus į skirtingas eiles.

Priminsiu, kad svarstome failų saugojimo internete atvejį. Tokiu atveju sistema atrodys maždaug taip:

Patogūs architektūriniai raštai

Svarbu, kad diagramoje pateiktos paslaugos nebūtinai reiškia atskirą serverį. Netgi procesas gali būti toks pat. Svarbus ir kitas dalykas: ideologiškai šie dalykai atskirti taip, kad būtų galima lengvai pritaikyti horizontalų mastelį.

O dviem vartotojams schema atrodys taip (skirtingiems vartotojams skirtos paslaugos nurodytos skirtingomis spalvomis):

Patogūs architektūriniai raštai

Premijos iš tokio derinio:

  • Informacijos apdorojimo paslaugos yra atskirtos. Eilės taip pat atskirtos. Jei mums reikia padidinti sistemos pralaidumą, tereikia paleisti daugiau paslaugų daugiau serverių.
  • Kai gauname informaciją iš vartotojo, mums nereikia laukti, kol duomenys bus visiškai išsaugoti. Priešingai, tereikia atsakyti „gerai“ ir tada palaipsniui pradėti dirbti. Tuo pačiu metu eilė išlygina smailes, nes naujas objektas pridedamas greitai ir vartotojui nereikia laukti, kol bus atliktas visas ciklas.
  • Kaip pavyzdį pridėjau dubliavimo panaikinimo paslaugą, kuri bando sujungti identiškus failus. Jei jis veikia ilgą laiką 1% atvejų, klientas beveik nepastebės (žr. aukščiau), o tai yra didelis pliusas, nes iš mūsų nebereikia būti XNUMX% greitu ir patikimu.

Tačiau trūkumai matomi iš karto:

  • Mūsų sistema prarado griežtą nuoseklumą. Tai reiškia, kad jei, pavyzdžiui, užsiprenumeruojate skirtingas paslaugas, teoriškai galite gauti kitokią būseną (nes viena iš paslaugų gali neturėti laiko gauti pranešimo iš vidinės eilės). Kita pasekmė – sistema dabar neturi bendro laiko. Tai yra, neįmanoma, pavyzdžiui, surūšiuoti visų įvykių tiesiog pagal atvykimo laiką, nes laikrodžiai tarp serverių gali būti nesinchroniški (be to, tas pats laikas dviejuose serveriuose yra utopija).
  • Dabar jokie įvykiai negali būti tiesiog atšaukti (kaip galima padaryti naudojant duomenų bazę). Vietoj to turite pridėti naują įvykį − kompensacinis įvykis, kuris pakeis paskutinę būseną į reikiamą. Pavyzdys iš panašios srities: neperrašant istorijos (tai kai kuriais atvejais yra blogai), negalite atšaukti įsipareigojimo git, bet galite padaryti specialų atšaukti įsipareigojimą, kuris iš esmės tiesiog grąžina seną būseną. Tačiau ir klaidingas įsipareigojimas, ir atšaukimas liks istorijoje.
  • Duomenų schema gali keistis nuo leidimo iki leidimo, tačiau senų įvykių nebebus galima atnaujinti į naują standartą (nes įvykių iš esmės keisti negalima).

Kaip matote, įvykių šaltinis gerai veikia su CQRS. Be to, įdiegti sistemą su efektyviomis ir patogiomis eilėmis, tačiau neatskiriant duomenų srautų, jau savaime yra sunku, nes teks pridėti sinchronizavimo taškus, kurie neutralizuos visą teigiamą eilių poveikį. Taikant abu būdus vienu metu, reikia šiek tiek pakoreguoti programos kodą. Mūsų atveju, siunčiant failą į serverį, atsakoma tik „ok“, o tai reiškia tik tai, kad „failo pridėjimo operacija buvo išsaugota“. Formaliai tai nereiškia, kad duomenys jau yra kituose įrenginiuose (pavyzdžiui, deduplikacijos paslauga gali atkurti indeksą). Tačiau po kurio laiko klientas gaus pranešimą „X failas išsaugotas“.

Kaip rezultatas:

  • Failų siuntimo būsenų skaičius didėja: vietoj klasikinio „failas išsiųstas“ gauname dvi: „failas įtrauktas į eilę serveryje“ ir „failas išsaugotas saugykloje“. Pastaroji reiškia, kad kiti įrenginiai jau gali pradėti priimti failą (pataisyta atsižvelgiant į tai, kad eilės veikia skirtingu greičiu).
  • Dėl to, kad pateikimo informacija dabar gaunama skirtingais kanalais, turime sugalvoti sprendimus, kaip gauti failo apdorojimo būseną. Dėl to: skirtingai nei klasikinis užklausa-atsakymas, apdorojant failą klientas gali būti paleistas iš naujo, tačiau paties apdorojimo būsena bus teisinga. Be to, šis elementas iš esmės veikia iš dėžutės. Dėl to dabar esame tolerantiškesni nesėkmėms.

Skaldymas

Kaip aprašyta aukščiau, įvykių šaltinių sistemoms trūksta griežto nuoseklumo. Tai reiškia, kad galime naudoti kelias saugyklas be jokio sinchronizavimo tarp jų. Spręsdami savo problemą galime:

  • Atskirkite failus pagal tipą. Pavyzdžiui, nuotraukas / vaizdo įrašus galima iššifruoti ir pasirinkti efektyvesnį formatą.
  • Atskiros sąskaitos pagal šalį. Dėl daugelio įstatymų to gali prireikti, tačiau ši architektūros schema tokią galimybę suteikia automatiškai

Patogūs architektūriniai raštai

Jei norite perkelti duomenis iš vienos saugyklos į kitą, standartinių priemonių nebeužtenka. Deja, tokiu atveju reikia sustabdyti eilę, atlikti perkėlimą ir tada pradėti. Paprastai duomenų negalima perkelti „skraidydamas“, tačiau jei įvykių eilė yra visiškai išsaugota ir turite ankstesnių saugojimo būsenų momentines nuotraukas, įvykius galime atkurti taip:

  • Įvykio šaltinyje kiekvienas įvykis turi savo identifikatorių (idealiu atveju, nemažėjantį). Tai reiškia, kad į saugyklą galime įtraukti lauką – paskutinio apdoroto elemento ID.
  • Dubliuojame eilę, kad visus įvykius būtų galima apdoroti kelioms nepriklausomoms saugykloms (pirmoji yra ta, kurioje jau saugomi duomenys, o antroji yra nauja, bet dar tuščia). Antroji eilė, žinoma, dar neapdorojama.
  • Paleidžiame antrąją eilę (tai yra, pradedame leisti įvykius).
  • Kai naujoji eilė yra gana tuščia (ty vidutinis laiko skirtumas tarp elemento pridėjimo ir jo gavimo yra priimtinas), galite pradėti perjungti skaitytuvus į naują saugyklą.

Kaip matote, mūsų sistemoje nebuvo ir vis dar nėra griežto nuoseklumo. Yra tik galutinis nuoseklumas, ty garantija, kad įvykiai bus apdorojami ta pačia tvarka (bet galbūt su skirtingais vėlavimais). Ir tai naudodamiesi galime palyginti lengvai perkelti duomenis, nesustabdydami sistemos į kitą Žemės rutulio pusę.

Taigi, tęsiant mūsų pavyzdį apie internetinę failų saugyklą, tokia architektūra jau suteikia mums daug privalumų:

  • Mes galime dinamiškai perkelti objektus arčiau vartotojų. Taip galite pagerinti paslaugų kokybę.
  • Kai kuriuos duomenis galime saugoti įmonėse. Pavyzdžiui, įmonės vartotojai dažnai reikalauja, kad jų duomenys būtų saugomi kontroliuojamuose duomenų centruose (siekiant išvengti duomenų nutekėjimo). Skaldydami mes galime tai lengvai paremti. O užduotis dar paprastesnė, jei klientas turi suderinamą debesį (pvz., Azure savarankiškai priglobta).
  • Ir svarbiausia, kad mes neturime to daryti. Galų gale, pirmiausia būtume patenkinti, jei visoms paskyroms būtų suteikta viena saugykla (kad būtų galima greitai pradėti dirbti). Ir pagrindinis šios sistemos bruožas yra tai, kad nors ji yra plečiama, pradiniame etape ji yra gana paprasta. Jums tiesiog nereikia iš karto rašyti kodo, kuris veiktų su milijonu atskirų nepriklausomų eilių ir pan. Jei reikia, tai galima padaryti ateityje.

Statinio turinio priegloba

Šis punktas gali atrodyti gana akivaizdus, ​​tačiau jis vis tiek būtinas daugiau ar mažiau standartiškai įkeltai programai. Jo esmė paprasta: visas statinis turinys platinamas ne iš to paties serverio, kuriame yra aplikacija, o iš specialių, skirtų būtent šiai užduočiai. Dėl to šios operacijos atliekamos greičiau (sąlyginis nginx aptarnauja failus greičiau ir pigiau nei Java serveris). Plius CDN architektūra (Turinio Pristatymas tinklas) leidžia mums rasti failus arčiau galutinių vartotojų, o tai teigiamai veikia darbo su paslauga patogumą.

Paprasčiausias ir standartiškiausias statinio turinio pavyzdys yra svetainės scenarijų ir vaizdų rinkinys. Su jais viskas paprasta – jie žinomi iš anksto, tada archyvas įkeliamas į CDN serverius, iš kurių paskirstomas galutiniams vartotojams.

Tačiau iš tikrųjų statiniam turiniui galite naudoti metodą, šiek tiek panašų į lambda architektūrą. Grįžkime prie užduoties (internetinė failų saugykla), kurioje turime paskirstyti failus vartotojams. Paprasčiausias sprendimas – sukurti paslaugą, kuri pagal kiekvieną vartotojo užklausą atlieka visus reikalingus patikrinimus (autorizacijos ir pan.), o tada atsisiunčia failą tiesiai iš mūsų saugyklos. Pagrindinis šio metodo trūkumas yra tas, kad statinį turinį (o failas su tam tikra versija iš tikrųjų yra statinis) platina tas pats serveris, kuriame yra verslo logika. Vietoj to galite sudaryti šią diagramą:

  • Serveris pateikia atsisiuntimo URL. Jis gali būti failo_id + raktas, kur raktas yra mažas skaitmeninis parašas, suteikiantis teisę pasiekti išteklius ateinančias 24 valandas.
  • Failą platina paprastas nginx su šiomis parinktimis:
    • Turinio kaupimas talpykloje. Kadangi ši paslauga gali būti įrengta atskirame serveryje, palikome sau rezervą ateičiai su galimybe visus naujausius atsisiųstus failus saugoti diske.
    • Rakto tikrinimas ryšio sukūrimo metu
  • Pasirenkama: srautinio turinio apdorojimas. Pavyzdžiui, jei suspaudžiame visus paslaugos failus, išpakavimą galime atlikti tiesiogiai šiame modulyje. Dėl to: IO operacijos atliekamos ten, kur jos priklauso. „Java“ archyvavimo priemonė nesunkiai paskirs daug papildomos atminties, tačiau paslaugos su verslo logika perrašymas į Rust/C++ sąlygas taip pat gali būti neefektyvus. Mūsų atveju naudojami skirtingi procesai (ar net paslaugos), todėl gana efektyviai galime atskirti verslo logiką ir IO operacijas.

Patogūs architektūriniai raštai

Ši schema nėra labai panaši į statinio turinio platinimą (kadangi mes kažkur neįkeliame viso statinio paketo), tačiau iš tikrųjų šis metodas yra susijęs su nekintamų duomenų platinimu. Be to, šią schemą galima apibendrinti ir kitais atvejais, kai turinys nėra tiesiog statinis, bet gali būti pavaizduotas kaip nekintančių ir neištrinamų blokų rinkinys (nors juos galima pridėti).

Kitas pavyzdys (pastiprinimui): jei dirbote su Jenkins / TeamCity, žinote, kad abu sprendimai yra parašyti Java. Abu jie yra „Java“ procesas, kuris tvarko ir kūrimo orkestravimą, ir turinio valdymą. Visų pirma, jie abu turi tokias užduotis kaip „perkelti failą / aplanką iš serverio“. Kaip pavyzdys: artefaktų išdavimas, šaltinio kodo perkėlimas (kai agentas neatsisiunčia kodo tiesiai iš saugyklos, o už jį tai atlieka serveris), prieiga prie žurnalų. Visos šios užduotys skiriasi savo IO apkrova. Tai yra, pasirodo, kad serveris, atsakingas už sudėtingą verslo logiką, tuo pat metu turi sugebėti efektyviai išstumti didelius duomenų srautus per save. Ir kas įdomiausia, kad tokia operacija gali būti deleguota tam pačiam nginx pagal lygiai tą pačią schemą (išskyrus tai, kad duomenų raktas turi būti pridėtas prie užklausos).

Tačiau, jei grįšime į savo sistemą, gausime panašią diagramą:

Patogūs architektūriniai raštai

Kaip matote, sistema tapo radikaliai sudėtingesnė. Dabar tai ne tik mažas procesas, kuris saugo failus vietoje. Dabar reikalingas ne pats paprasčiausias palaikymas, API versijos valdymas ir pan. Todėl, nubraižius visas diagramas, geriausia detaliai įvertinti, ar išplečiamumas yra vertas išlaidų. Tačiau jei norite išplėsti sistemą (įskaitant dirbti su dar didesniu vartotojų skaičiumi), tuomet turėsite ieškoti panašių sprendimų. Tačiau dėl to sistema yra architektūriškai paruošta padidintai apkrovai (beveik kiekvienas komponentas gali būti klonuotas horizontaliam mastelio keitimui). Sistema gali būti atnaujinama jos nestabdant (tiesiog kai kurios operacijos bus šiek tiek sulėtintos).

Kaip sakiau pačioje pradžioje, dabar daugelis interneto paslaugų pradėjo gauti didesnį apkrovą. Ir kai kurie iš jų tiesiog nustojo tinkamai veikti. Tiesą sakant, sistemos sugedo būtent tuo metu, kai verslas turėjo užsidirbti pinigų. Tai yra, vietoj atidėto pristatymo, užuot siūlius klientams „suplanuoti pristatymą ateinantiems mėnesiams“, sistema tiesiog pasakė „eikite pas konkurentus“. Tiesą sakant, tai yra mažo produktyvumo kaina: nuostoliai atsiras būtent tada, kai pelnas bus didžiausias.

išvada

Visi šie metodai buvo žinomi anksčiau. Tas pats VK jau seniai naudoja statinio turinio prieglobos idėją vaizdams rodyti. Daugelis internetinių žaidimų naudoja „Sharding“ schemą, kad suskirstytų žaidėjus į regionus arba atskirtų žaidimų vietas (jei pats pasaulis yra vienas). Įvykių šaltinio metodas aktyviai naudojamas el. Dauguma prekybos programų, kuriose nuolat gaunami duomenys, iš tikrųjų yra sukurtos CQRS metodu, kad būtų galima filtruoti gautus duomenis. Na, o horizontalus mastelio keitimas daugelyje paslaugų naudojamas gana ilgą laiką.

Tačiau, svarbiausia, visus šiuos modelius tapo labai lengva pritaikyti šiuolaikinėse programose (jei jie tinkami, žinoma). Debesys siūlo dalijimąsi ir horizontalų mastelio keitimą iš karto, o tai yra daug lengviau, nei pačiam užsisakyti skirtingus dedikuotus serverius skirtinguose duomenų centruose. CQRS tapo daug lengvesnis, jei tik dėl bibliotekų, tokių kaip RX, kūrimo. Maždaug prieš 10 metų reta svetainė galėjo tai palaikyti. Dėl paruoštų konteinerių su Apache Kafka taip pat neįtikėtinai lengva nustatyti įvykių šaltinį. Prieš 10 metų tai būtų buvusi naujovė, dabar tai įprasta. Tas pats yra ir su statinio turinio priegloba: dėl patogesnių technologijų (įskaitant tai, kad yra išsami dokumentacija ir didelė atsakymų duomenų bazė), šis metodas tapo dar paprastesnis.

Dėl to daugelio gana sudėtingų architektūrinių modelių įgyvendinimas dabar tapo daug paprastesnis, o tai reiškia, kad geriau iš anksto į tai atidžiau pažvelgti. Jei dešimties metų senumo programoje vieno iš aukščiau pateiktų sprendimų buvo atsisakyta dėl didelių diegimo ir eksploatavimo sąnaudų, dabar naujoje programoje arba po pertvarkymo galite sukurti paslaugą, kuri jau bus architektūriškai išplečiama ( našumo požiūriu) ir paruošti naujiems klientų užklausoms (pavyzdžiui, lokalizuoti asmens duomenis).

Ir svarbiausia: nenaudokite šių metodų, jei turite paprastą programą. Taip, jie gražūs ir įdomūs, bet svetainėje, kurioje daugiausiai apsilanko 100 žmonių, dažnai galima apsieiti su klasikiniu monolitu (bent jau išorėje viską viduje galima suskirstyti į modulius ir pan.).

Šaltinis: www.habr.com

Добавить комментарий