Modelli architettonici convenienti

Ehi Habr!

In vista di l'avvenimenti attuali per via di coronavirus, una quantità di servizii Internet anu cuminciatu à riceve una carica aumentata. Per esempiu, Una di e catene di vendita di u Regnu Unitu hà solu cessatu u so situ di ordine in linea., perchè ùn ci era micca abbastanza capacità. È ùn hè micca sempre pussibule di accelerà un servitore solu per aghjunghje un equipamentu più putente, ma e richieste di i clienti devenu esse processate (o andaranu à i cuncurrenti).

In questu articulu vi parleraghju brevemente di e pratiche populari chì permettenu di creà un serviziu veloce è tolerante à i difetti. In ogni casu, da i pussibuli schemi di sviluppu, aghju sceltu solu quelli chì sò attualmente faciule d'utilizà. Per ogni articulu, o avete biblioteche pronti, o avete l'uppurtunità di risolve u prublema cù una piattaforma nuvola.

Scala horizontale

U puntu più simplice è cunnisciutu. Convenzionalmente, i dui schemi di distribuzione di carichi più cumuni sò scala horizontale è verticale. In u primu casu permette à i servizii di funziunà in parallelu, distribuendu cusì a carica trà elli. In u sicondu ordinate servitori più putenti o ottimisate u codice.

Per esempiu, pigliaraghju u almacenamentu di schedarii di nuvola astratta, vale à dì, qualchì analogu di OwnCloud, OneDrive, è cusì.

Una stampa standard di un tali circuitu hè quì sottu, ma solu mostra a cumplessità di u sistema. Dopu tuttu, avemu bisognu di sincronizà in qualchì modu i servizii. Chì succede se l'utilizatore salva un schedariu da a tableta è poi vole vede da u telefunu?

Modelli architettonici convenienti
A diffarenza trà l'avvicinamenti: in scala verticale, simu pronti per aumentà u putere di i nodi, è in scala horizontale, simu pronti per aghjunghje novi nodi per distribuisce a carica.

CQRS

Segregazione di Responsabilità di Query Command Un mudellu piuttostu impurtante, postu chì permette à diversi clienti micca solu per cunnette cù diversi servizii, ma ancu per riceve i stessi flussi di eventi. I so benefici ùn sò micca cusì evidenti per una applicazione simplice, ma hè assai impurtante (è simplice) per un serviziu occupatu. A so essenza: i flussi di dati in entrata è in uscita ùn deve micca intersecà. Questu hè, ùn pudete micca mandà una dumanda è aspittà una risposta; invece, mandate una dumanda à u serviziu A, ma riceve una risposta da u serviziu B.

U primu bonus di questu approcciu hè a capacità di rompe a cunnessione (in u sensu largu di a parolla) mentre eseguisce una longa dumanda. Per esempiu, pigliemu una sequenza più o menu standard:

  1. U cliente hà mandatu una dumanda à u servitore.
  2. U servitore hà iniziatu un longu tempu di prucessu.
  3. U servitore hà rispostu à u cliente cù u risultatu.

Fighjemu chì in u puntu 2 a cunnessione hè stata rotta (o a reta reconnected, o l'utilizatore andò à un'altra pagina, rumpia a cunnessione). In questu casu, serà difficiule per u servitore di mandà una risposta à l'utilizatore cù infurmazioni nantu à ciò chì hè stata trattata. Utilizendu CQRS, a sequenza serà ligeramente diversa:

  1. U cliente hà abbonatu à l'aghjurnamenti.
  2. U cliente hà mandatu una dumanda à u servitore.
  3. U servitore hà rispostu "a dumanda accettata".
  4. U servitore hà rispostu cù u risultatu attraversu u canali da u puntu "1".

Modelli architettonici convenienti

Comu pudete vede, u schema hè un pocu più cumplessu. Inoltre, l'approcciu intuitivu di dumanda-risposta manca quì. Tuttavia, cum'è pudete vede, una rottura di cunnessione durante a trasfurmazioni di una dumanda ùn porta micca à un errore. Inoltre, se in fattu l'utilizatore hè cunnessu à u serviziu da parechji dispositi (per esempiu, da un telefuninu è da una tableta), pudete assicurà chì a risposta vene à i dui dispositi.

Curiosamente, u codice per processà i missaghji entranti diventa u stessu (micca 100%) sia per l'avvenimenti chì anu influinzatu da u cliente stessu, sia per altri avvenimenti, cumpresi quelli di altri clienti.

In ogni casu, in realità avemu un bonus supplementu per u fattu chì u flussu unidirezionale pò esse trattatu in un stile funziunale (usendu RX è simili). È questu hè digià un plus seriu, postu chì in essenza l'applicazione pò esse fatta cumpletamente reattiva, è ancu cù un accostu funziunale. Per i prugrammi di grassu, questu pò salvà significativamente risorse di sviluppu è sustegnu.

Se combinemu stu approcciu cù scala horizontale, allora cum'è bonus avemu a capacità di mandà richieste à un servitore è riceve risposte da un altru. Cusì, u cliente pò sceglie u serviziu chì hè cunvenutu per ellu, è u sistema di l'internu serà sempre capaci di processà l'avvenimenti currettamente.

Acquisizione di l'avvenimenti

Comu sapete, una di e caratteristiche principali di un sistema distribuitu hè l'absenza di un tempu cumuni, una sezione critica cumuna. Per un prucessu, pudete fà una sincronizazione (in i stessi mutexes), in u quale site sicuru chì nimu altru hè esecutatu stu codice. In ogni casu, questu hè periculosu per un sistema distribuitu, postu chì avarà bisognu di sopra, è ancu tumbà tutta a bellezza di scala - tutti i cumpunenti aspittàranu sempre per unu.

Da quì avemu un fattu impurtante - un sistema distribuitu veloce ùn pò micca esse sincronizatu, perchè allora riduceremu u rendiment. Per d 'altra banda, avemu spessu bisognu di una certa coherenza trà cumpunenti. È per questu pudete aduprà l'approcciu cun eventuale coerenza, induve hè garantitu chì s'ellu ùn ci hè micca cambiamenti di dati per qualchì periudu di tempu dopu l'ultima aghjurnazione ("eventualmente"), tutte e dumande tornanu l'ultimu valore aghjurnatu.

Hè impurtante di capisce chì per e basa di dati classica hè abbastanza spessu usata forte coerenza, induve ogni node hà a listessa infurmazione (questu hè spessu ottenutu in u casu induve a transazzione hè cunsiderata stabilita solu dopu chì u sicondu servitore risponde). Ci hè qualchì rilassamentu quì per via di i livelli di isolamentu, ma l'idea generale resta a stessa - pudete campà in un mondu cumpletamente harmonisatu.

Tuttavia, vultemu à u compitu originale. Sè parte di u sistema pò esse custruitu cù eventuale coerenza, allora pudemu custruisce u schema seguente.

Modelli architettonici convenienti

Caratteristiche impurtanti di stu approcciu:

  • Ogni dumanda entrante hè posta in una fila.
  • Mentre trasfurmeghja una dumanda, u serviziu pò ancu mette in attività in altre file.
  • Ogni avvenimentu in entrata hà un identificatore (chì hè necessariu per a deduplicazione).
  • A fila funziona ideologicamente secondu u schema "append only". Ùn pudete micca sguassate elementi da ellu o riorganizzalli.
  • A fila travaglia secondu u schema FIFO (scusate per a tautologia). Sè avete bisognu di fà esecuzione parallela, allora in una tappa duvete spustà l'uggetti in diverse file.

Lasciami ricurdà chì avemu cunsiderà u casu di almacenamentu di schedari in linea. In questu casu, u sistema serà simile à questu:

Modelli architettonici convenienti

Hè impurtante chì i servizii in u diagramma ùn significanu micca necessariamente un servitore separatu. Ancu u prucessu pò esse u listessu. Un'altra cosa hè impurtante: ideologicamente, queste cose sò siparati in tale manera chì a scala horizontale pò esse facilmente applicata.

È per dui utilizatori u diagramma sarà cusì (i servizii destinati à diversi utilizatori sò indicati in culori diffirenti):

Modelli architettonici convenienti

Bonus da una tale cumminazione:

  • I servizii di trattamentu di l'infurmazioni sò separati. I fili sò ancu separati. Sè avemu bisognu di aumentà u throughput di u sistema, allora avemu solu bisognu di lancià più servizii in più servitori.
  • Quandu avemu ricevutu infurmazione da un utilizatore, ùn avemu a aspittà finu à chì i dati hè cumplitamenti salvatu. À u cuntrariu, ci vole solu à risponde "ok" è poi à pocu à pocu cumincià à travaglià. À u listessu tempu, a fila liscia i cimi, postu chì l'aghjunghje un novu ughjettu si faci rapidamente, è l'utilizatore ùn deve micca aspittà per un passaghju cumpletu per tuttu u ciculu.
  • Per esempiu, aghju aghjustatu un serviziu di deduplicazione chì prova di unisce i schedari idèntici. S'ellu travaglia per un bellu pezzu in u 1% di i casi, u cliente ùn hà micca avvistu (vede sopra), chì hè un grande plus, postu chì ùn avemu più bisognu di esse XNUMX% veloce è affidabile.

Tuttavia, i svantaghji sò immediatamente visibili:

  • U nostru sistema hà persu a so cuerenza stretta. Questu significa chì se, per esempiu, abbonate à diversi servizii, allora in teoria pudete ottene un statu diversu (perchè unu di i servizii ùn pò micca avè u tempu di riceve una notificazione da a fila interna). Cum'è una altra cunsiguenza, u sistema avà ùn hà micca tempu cumuni. Questu hè, hè impussibile, per esempiu, di sorte tutti l'avvenimenti solu per l'ora d'arrivu, postu chì l'orologi trà i servitori ùn ponu micca esse sincroni (in più, u stessu tempu in dui servitori hè una utopia).
  • Nisun avvenimentu ùn pò avà esse ritruvatu solu (cum'è puderia esse fattu cù una basa di dati). Invece, avete bisognu di aghjunghje un novu avvenimentu - avvenimentu di compensazione, chì cambierà l'ultimu statu à quellu necessariu. Per esempiu da una zona simili: senza riscrittura di a storia (chì hè male in certi casi), ùn pudete micca retrocede un cummit in git, ma pudete fà un speziale rollback commit, chì essenzialmente torna solu u vechju statu. Tuttavia, tramindui l'ingaghjamentu sbagliatu è u rollback resteranu in a storia.
  • U schema di dati pò cambià da a liberazione à a liberazione, ma i vechji avvenimenti ùn puderanu più esse aghjurnati à u novu standard (perchè l'avvenimenti ùn ponu micca esse cambiati in principiu).

Comu pudete vede, Event Sourcing funziona bè cù CQRS. Inoltre, l'implementazione di un sistema cù fili efficienti è convenienti, ma senza separà i flussi di dati, hè digià difficiule in sè stessu, perchè avete da aghjunghje punti di sincronizazione chì neutralizà tuttu l'effettu pusitivu di e fila. Appliendu i dui approcci à una volta, hè necessariu di aghjustà ligeramente u codice di u prugramma. In u nostru casu, quandu mandate un schedariu à u servitore, a risposta vene solu "ok", chì significa solu chì "l'operazione di aghjunghje u schedariu hè stata salvata". Formalmenti, questu ùn significa micca chì e dati sò digià dispunibili nantu à altri dispositi (per esempiu, u serviziu di deduplicazione pò ricustruisce l'indici). Tuttavia, dopu qualchì tempu, u cliente riceverà una notificazione in u stilu di "file X hè statu salvatu".

Di cunsiguenza:

  • U numaru di stati di mandatu di u schedariu hè crescente: invece di u classicu "fichiu mandatu", avemu dui: "u schedariu hè statu aghjuntu à a fila di u servitore" è "u schedariu hè statu salvatu in almacenamiento". L'ultime significa chì l'altri dispositi ponu digià principià à riceve u schedariu (aghjustatu per u fattu chì e fila operanu à diverse velocità).
  • Duvuta à u fattu chì l 'infurmazione sottumissione avà vene à traversu differente canali, avemu bisognu di cullà suluzioni pè riceve u statutu di trasfurmazioni di u schedariu. In cunseguenza di questu: à u cuntrariu di a dumanda-risposta classica, u cliente pò esse riavviatu durante u processu di u schedariu, ma u statutu di stu prucessu stessu serà currettu. Inoltre, questu articulu funziona, essenzialmente, fora di a scatula. In cunseguenza: simu avà più tolleranti di i fallimenti.

Spartuta

Cum'è descrittu sopra, i sistemi d'approvvigionamentu di l'eventi mancanu una coerenza stretta. Questu significa chì pudemu usà parechji almacenamenti senza alcuna sincronizazione trà elli. Avvicinendu u nostru prublema, pudemu:

  • Separate i schedari per tipu. Per esempiu, ritratti / video ponu esse decodificati è un furmatu più efficau pò esse sceltu.
  • Conti separati per paese. A causa di parechje lege, questu pò esse necessariu, ma questu schema di l'architettura furnisce una tale opportunità automaticamente

Modelli architettonici convenienti

Sè vo vulete trasfiriri dati da un almacenamentu à un altru, allura i mezi standard ùn sò più abbastanza. Sfurtunatamente, in questu casu, avete bisognu di piantà a fila, fate a migrazione, è poi principià. In u casu generale, i dati ùn ponu esse trasferiti "à a mosca", però, se a fila di l'avvenimenti hè almacenata cumplettamente, è avete snapshots di stati di almacenamento previ, allora pudemu riproduce l'avvenimenti cum'è seguita:

  • In l'Event Source, ogni avvenimentu hà u so propiu identificatore (idealmente, micca decrescente). Questu significa chì pudemu aghjunghje un campu à l'almacenamiento - l'id di l'ultimu elementu processatu.
  • Duplicemu a fila in modu chì tutti l'avvenimenti ponu esse processati per parechji almacenamenti indipendenti (u primu hè quellu chì a dati hè digià guardatu, è u sicondu hè novu, ma sempre viotu). A seconda fila, di sicuru, ùn hè micca trattatu ancu.
  • Lancemu a seconda fila (vale à dì, cuminciamu à ripiglià l'avvenimenti).
  • Quandu a nova fila hè relativamente viota (vale à dì, a diffarenza di u tempu mediu trà l'aghjunghje un elementu è a ricuperazione hè accettabile), pudete inizià à cambià i lettori à u novu almacenamiento.

Comu pudete vede, ùn avemu micca, è ùn avemu micca sempre, una coherenza stretta in u nostru sistema. Ci hè solu una custanza eventuale, vale à dì una guaranzia chì l'avvenimenti sò trattati in u stessu ordine (ma possibbilmente cù ritardi diffirenti). È, cù questu, pudemu trasfiriri dati relativamente facilmente senza firmà u sistema à l'altra parte di u globu.

Cusì, cuntinuendu u nostru esempiu nantu à u almacenamentu in linea per i schedari, una tale architettura ci dà digià una quantità di bonus:

  • Pudemu spustà l'uggetti più vicinu à l'utilizatori in modu dinamicu. Questu modu pudete migliurà a qualità di serviziu.
  • Pudemu almacenà alcune dati in l'imprese. Per esempiu, l'utilizatori di l'Intraprisa spessu necessitanu chì i so dati sò almacenati in centri di dati cuntrullati (per evità fughe di dati). Per mezu di sharding pudemu facilmente sustene questu. È u compitu hè ancu più faciule se u cliente hà una nuvola cumpatibile (per esempiu, Azure self hosted).
  • È u più impurtante hè chì ùn avemu micca bisognu di fà questu. Dopu tuttu, per cumincià, seremu abbastanza cuntenti cù un almacenamentu per tutti i cunti (per cumincià à travaglià rapidamente). È a funzione chjave di stu sistema hè chì ancu s'ellu hè expandable, in u stadiu iniziale hè abbastanza simplice. Ùn avete micca bisognu di scrive immediatamente codice chì travaglia cù un milione di file indipendenti separati, etc. Se necessariu, questu pò esse fattu in u futuru.

Hosting di cuntenutu staticu

Stu puntu pò esse abbastanza ovvi, ma hè sempre necessariu per una applicazione caricata più o menu standard. A so essenza hè simplice: tuttu u cuntenutu staticu hè distribuitu micca da u stessu servitore induve si trova l'applicazione, ma da quelli speciali dedicati specificamente à questu compitu. In u risultatu, sti operazioni sò realizati più veloce (nginx cundizionale serve i schedari più rapidamente è menu caru chè un servitore Java). Plus l'architettura CDN (Riticamentu di Cunnette) ci permette di localizà i nostri schedari più vicinu à l'utilizatori finali, chì hà un effettu pusitivu nantu à a cunvenzione di travaglià cù u serviziu.

L'esempiu più simplice è standard di cuntenutu staticu hè un settore di scripts è imagine per un situ web. Tuttu hè simplice cun elli - sò cunnisciuti in anticipu, dopu l'archiviu hè caricatu à i servitori CDN, da induve sò distribuiti à l'utilizatori finali.

Tuttavia, in realtà, per u cuntenutu staticu, pudete aduprà un approcciu un pocu simili à l'architettura lambda. Riturnemu à u nostru compitu (almacenamiento di schedari in linea), in quale avemu bisognu di distribuisce i schedari à l'utilizatori. A suluzione più simplice hè di creà un serviziu chì, per ogni dumanda di l'utilizatori, faci tutti i cuntrolli necessarii (autorizazione, etc.), è dopu scaricate u schedariu direttamente da u nostru almacenamentu. U principale disadvantage di questu approcciu hè chì u cuntenutu staticu (è un schedariu cù una certa rivisione hè, in fattu, cuntenutu staticu) hè distribuitu da u stessu servitore chì cuntene a logica cummerciale. Invece, pudete fà u schema seguente:

  • U servitore furnisce un URL di scaricamentu. Pò esse di a forma file_id + chjave, induve a chjave hè una signatura mini-digitale chì dà u dirittu di accede à a risorsa per e 24 ore dopu.
  • U schedariu hè distribuitu da nginx simplice cù e seguenti opzioni:
    • Cache di cuntenutu. Siccomu stu serviziu pò esse situatu nantu à un servitore separatu, avemu lasciatu una riserva per u futuru cù a capacità di almacenà tutti l'ultimi fugliali scaricati nantu à u discu.
    • Verificate a chjave à u mumentu di a creazione di cunnessione
  • Opcional: trasfurmazioni di cuntenutu in streaming. Per esempiu, se cumpressemu tutti i schedari in u serviziu, allora pudemu fà unzipping direttamente in stu modulu. In cunseguenza: l'operazioni IO sò fatti induve appartene. Un archiver in Java facilmente attribuirà assai memoria extra, ma a riscrittura di un serviziu cù logica cummerciale in Rust / C ++ cundiziunali pò ancu esse inefficace. In u nostru casu, diversi prucessi (o ancu servizii) sò usati, è per quessa pudemu separà in modu abbastanza efficace a logica cummerciale è l'operazioni IO.

Modelli architettonici convenienti

Stu schema ùn hè micca assai simili à a distribuzione di u cuntenutu staticu (perchè ùn carichemu micca tuttu u pacchettu staticu in un locu), ma in a realità, questu approcciu hè precisamente cuncernatu di distribuzione di dati immutable. Inoltre, stu schema pò esse generalizati à altri casi induve u cuntenutu ùn hè micca solu staticu, ma pò esse rapprisintatu cum'è un inseme di blocchi immutable è non-deletable (ancu si ponu esse aghjuntu).

Comu un altru esempiu (per rinfurzà): se avete travagliatu cù Jenkins / TeamCity, allora sapete chì e duie suluzioni sò scritte in Java. Tramindui sò un prucessu Java chì gestisce sia l'orchestrazione di custruisce sia a gestione di cuntenutu. In particulare, tramindui anu compiti cum'è "trasferisce un schedariu / cartulare da u servitore". Per esempiu: emissione di artefatti, trasferimentu di codice fonte (quandu l'agente ùn scarica micca u codice direttamente da u repository, ma u servitore faci per ellu), accessu à i logs. Tutti issi compiti sò diffirenti in a so carica IO. Questu hè, risulta chì u servitore rispunsevuli di a logica cummerciale cumplessa deve à u stessu tempu esse capace di spinghje in modu efficace grandi flussi di dati per ellu stessu. E ciò chì hè più interessante hè chì una tale operazione pò esse delegata à u stessu nginx secondu esattamente u listessu schema (salvu chì a chjave di dati deve esse aghjuntu à a dumanda).

Tuttavia, se vultemu à u nostru sistema, avemu un diagramma simili:

Modelli architettonici convenienti

Comu pudete vede, u sistema hè diventatu radicalmente più cumplessu. Avà ùn hè micca solu un mini-processu chì guarda i schedari in u locu. Avà ciò chì hè necessariu ùn hè micca u supportu più simplice, u cuntrollu di versione API, etc. Dunque, dopu chì tutti i diagrammi sò stati disegnati, hè megliu valutà in dettagliu se l'extensibilità vale u costu. In ogni casu, sè vo vulete esse capace di espansione u sistema (cumpresu per travaglià cù un numeru ancu più grande di utilizatori), allora avete da andà per suluzioni simili. Ma, com'è u risultatu, u sistema hè architetturale prontu per una carica aumentata (quasi ogni cumpunente pò esse clonatu per scala horizontale). U sistema pò esse aghjurnatu senza piantà (semplicemente alcune operazioni seranu un pocu rallentate).

Cumu l'aghju dettu à u principiu, avà una quantità di servizii Internet anu cuminciatu à riceve una carica aumentata. È certi di elli simpricimenti cuminciaru à piantà di travaglià bè. In fatti, i sistemi fiascatu precisamente à u mumentu chì l'affari era suppostu di guadagnà soldi. Questu hè, invece di consegna differita, invece di suggerisce à i clienti "pianu a vostra consegna per i prossimi mesi", u sistema hà dettu solu "andà à i vostri cuncurrenti". In fatti, questu hè u prezzu di a produtividade bassa: i perditi saranu precisamente quandu i prufitti seranu più altu.

cunchiusioni

Tutti questi approcci eranu cunnisciuti prima. U stessu VK hà longu aduprà l'idea di Hosting di cuntenutu staticu per vede l'imaghjini. A lot of games online use the Sharding scheme to divide players into regions or to separate games locations (se u mondu stessu hè unu). L'approcciu di l'Event Sourcing hè attivamente utilizatu in e-mail. A maiò parte di l'applicazioni di cummerciale induve e dati sò sempre ricevuti sò in realtà custruiti nantu à un approcciu CQRS per pudè filtrà e dati ricevuti. Eppo, a scala horizontale hè stata aduprata in parechji servizii per un bellu pezzu.

In ogni casu, u più impurtante, tutti questi mudelli sò diventati assai faciuli d'applicà in l'applicazioni muderne (se sò adattati, sicuru). Nuvole offre Sharding è scala horizontale subitu, chì hè assai più faciule ch'è urdinà diversi servitori dedicati in diversi centri di dati. CQRS hè diventatu assai più faciule, solu per via di u sviluppu di biblioteche cum'è RX. Circa 10 anni fà, un situ web raru puderia sustene questu. Event Sourcing hè ancu incredibbilmente faciule da stallà grazia à i cuntenituri pronti cù Apache Kafka. 10 anni fà questu seria una innuvazione, avà hè cumune. Hè listessa cù Static Content Hosting: per via di tecnulugii più convenienti (cumpresu u fattu chì ci hè una documentazione dettagliata è una grande basa di dati di risposte), stu approcciu hè diventatu ancu più simplice.

In u risultatu, l'implementazione di una quantità di mudelli architetturali piuttostu cumplessi hè diventata assai più simplice, chì significa chì hè megliu guardà in anticipu. Se in una applicazione di deci anni una di e soluzioni sopra hè stata abbandunata per via di l'altu costu di implementazione è operazione, avà, in una nova applicazione, o dopu à refactoring, pudete creà un serviziu chì serà digià architetturale sia extensible ( in termini di prestazione) è pronta à e novi richieste da i clienti (per esempiu, per localizà e dati persunali).

È più impurtante: per piacè ùn aduprate micca questi approcci se avete una applicazione simplice. Iè, sò belli è interessanti, ma per un situ cù una visita di punta di 100 persone, pudete spessu cun un monolitu classicu (almenu à l'esternu, tuttu ciò chì dentru pò esse divisu in moduli, etc.).

Source: www.habr.com

Add a comment