![Search icon](https://hackernoon.imgix.net/search-new.png?w=19&h=19)
Izgradnja pouzdanog, visoko dostupnog, skalabilnog distribuiranog sistema zahtijeva pridržavanje specifičnih tehnika, principa i obrazaca. Dizajn takvih sistema uključuje rješavanje bezbroj izazova. Među najčešćim i najosnovnijim problemima je problem dvostrukog pisanja .
„Problem dvostrukog pisanja“ je izazov koji se javlja u distribuiranim sistemima, uglavnom kada se radi sa više izvora podataka ili baza podataka koje treba da budu sinhronizovane. Odnosi se na poteškoću da se osigura da se promjene podataka dosljedno upisuju u različite skladišta podataka, kao što su baze podataka ili keš memorije, bez uvođenja problema kao što su nedosljednosti podataka, sukobi ili uska grla u performansama.
Arhitektura mikroservisa i baza podataka uzoraka po usluzi vam donose mnoge prednosti, kao što su neovisna implementacija i skaliranje, izolirani kvarovi i potencijalno povećanje brzine razvoja. Međutim, operacije zahtijevaju promjene među više mikrousluga, prisiljavajući vas da razmišljate o pouzdanom rješenju za rješavanje ovog problema.
Hajde da razmotrimo scenario u kojem naša domena uključuje prihvatanje zahteva za kredit, njihovu procenu, a zatim slanje obaveštenja klijentima.
U duhu principa jedinstvene odgovornosti, Konvejevog zakona i pristupa dizajna vođenog domenom, nakon nekoliko sesija koje su okupirale događaje, ceo domen je podeljen na tri poddomena sa definisanim ograničenim kontekstima koji imaju jasne granice, modele domena i sveprisutni jezik.
Prvi ima zadatak da uklopi i sastavi nove zahtjeve za kredit. Drugi sistem procjenjuje ove aplikacije i donosi odluke na osnovu dostavljenih podataka. Ovaj proces procene, uključujući KYC/KYB, provere protiv prevara i kreditnog rizika, može da potraje, što zahteva mogućnost istovremenog rukovanja hiljadama aplikacija. Shodno tome, ova funkcionalnost je delegirana namenskom mikroservisu sa sopstvenom bazom podataka, omogućavajući nezavisno skaliranje.
Nadalje, ovim podsistemima upravljaju dva različita tima, svaki sa svojim vlastitim ciklusima izdavanja, ugovorima o razini usluge (SLA) i zahtjevima skalabilnosti.
Konačno , uspostavljena je specijalizirana usluga obavještavanja za slanje upozorenja kupcima.
Evo pročišćenog opisa primarnog slučaja upotrebe sistema:
To je na prvi pogled prilično jednostavan i primitivan sistem, ali hajde da zaronimo u to kako usluga aplikacije za zajam obrađuje naredbu za slanje zahtjeva za kredit.
Možemo razmotriti dva pristupa za interakcije usluga:
First-Local-Commit-Then-Publish: U ovom pristupu, usluga ažurira svoju lokalnu bazu podataka (urezivanje) i zatim objavljuje događaj ili poruku drugim uslugama.
Prvo-objavi-pa-lokalno-urezivanje: S druge strane, ova metoda uključuje objavljivanje događaja ili poruke prije urezivanja promjena u lokalnu bazu podataka.
Obje metode imaju svoje nedostatke i samo su djelimično sigurne od greške za komunikaciju u distribuiranim sistemima.
Ovo je dijagram sekvence primjene prvog pristupa.
U ovom scenariju, usluga za podnošenje zahtjeva za zajam koristi pristup Prvo-lokalno-uredi-pa-objavi , gdje prvo izvrši transakciju, a zatim pokuša poslati obavještenje drugom sistemu. Međutim, ovaj proces je podložan neuspjehu ako, na primjer, postoje problemi s mrežom, usluga procjene je nedostupna ili usluga za podnošenje zahtjeva za kredit naiđe na grešku bez memorije (OOM) i padne. U takvim slučajevima, poruka bi bila izgubljena, ostavljajući Procjenu bez obavještenja o novom zahtjevu za kredit, osim ako se ne provedu dodatne mjere.
I drugi.
U scenariju prvo objavi-pa-lokalno povjeri , usluga zahtjeva za kredit suočava se sa značajnijim rizicima. Može obavijestiti Službu za procjenu o novoj aplikaciji, ali neće uspjeti lokalno sačuvati ovo ažuriranje zbog problema kao što su problemi s bazom podataka, greške u memoriji ili greške u kodu. Ovakav pristup može dovesti do značajnih nedosljednosti u podacima, što može uzrokovati ozbiljne probleme, ovisno o tome kako Služba za pregled kredita obrađuje pristigle zahtjeve.
Stoga moramo identificirati rješenje koje nudi robustan mehanizam za objavljivanje događaja vanjskim potrošačima. Ali, prije nego što uđemo u potencijalna rješenja, prvo bismo trebali razjasniti vrste garancija isporuke poruka koje se mogu postići u distribuiranim sistemima.
Postoje četiri vrste garancija koje možemo postići.
Nema garancija
Ne postoji garancija da će poruka biti dostavljena na odredište. Pristup Prvo-Lokalno-učini-pa-objavi upravo je o tome. Potrošači mogu primiti poruke jednom, više puta ili nikako.
Najviše jednom isporukom
Najviše jednokratna dostava znači da će poruka biti dostavljena na odredište najviše 1 put. Pristup Prvo-Lokalno-Uredi-Zatim-Objavi se može implementirati na ovaj način kao i sa politikom ponovnog pokušaja pokušaja sa jednom vrijednošću.
Najmanje jednom isporukom\Potrošači će primiti i obraditi svaku poruku, ali istu poruku mogu primiti više puta.
Tačno nakon isporuke\Tačno nakon isporuke znači da će potrošač primiti poruku efektivno jednom.
Tehnički, to je moguće postići Kafkinim transakcijama i specifičnom idempotentnom implementacijom proizvođača i potrošača.
U većini slučajeva, garancije isporuke 'barem jednom' rješavaju mnoge probleme osiguravajući da se poruke isporuče barem jednom, ali potrošači moraju biti idempotentni. Međutim, s obzirom na neizbježne kvarove na mreži, sva potrošačka logika mora biti idempotentna kako bi se izbjegla obrada duplih poruka, bez obzira na garancije proizvođača. Stoga, ovaj zahtjev nije toliko nedostatak koliko odražava stvarnost.
Postoji mnogo rješenja za ovaj problem, koja imaju svoje prednosti i nedostatke.
Prema Wikipediji, Two-Phase Commit (2PC) je protokol distribuirane transakcije koji se koristi u računarskim naukama i sistemima za upravljanje bazama podataka kako bi se osigurala konzistentnost i pouzdanost distribuiranih transakcija. Dizajniran je za situacije u kojima više resursa (npr. baze podataka) treba da učestvuju u jednoj transakciji, i osigurava da ili svi izvrše transakciju ili da je svi prekinu, čime se održava konzistentnost podataka. Zvuči upravo ono što nam treba, ali Two-Phase Commit ima nekoliko nedostataka:
Najočitije rješenje za arhitekturu mikroservisa je primjena uzorka (ili čak ponekad anti-uzorca) — dijeljene baze podataka. Ovaj pristup je vrlo intuitivan ako vam je potrebna konzistentnost transakcija u više tabela u različitim bazama podataka, samo koristite jednu zajedničku bazu podataka za ove mikroservise.
Nedostaci ovog pristupa uključuju uvođenje jedne tačke kvara, inhibiranje nezavisnog skaliranja baze podataka i ograničavanje mogućnosti korištenja različitih rješenja baze podataka najprikladnijih za specifične zahtjeve i slučajeve upotrebe. Dodatno, modifikacije kodnih baza mikroservisa bile bi neophodne da bi se podržao takav oblik distribuirane transakcije.
' Transakciono odlazno sanduče ' je obrazac dizajna koji se koristi u distribuiranim sistemima kako bi se osiguralo pouzdano širenje poruka, čak i kada su u pitanju nepouzdani sistemi za razmjenu poruka. To uključuje pohranjivanje događaja u označenu tablicu 'OutboxEvents' unutar iste transakcije kao i sama operacija. Ovaj pristup je dobro usklađen sa svojstvima ACID relacionih baza podataka. Nasuprot tome, mnoge No-SQL baze podataka ne podržavaju u potpunosti ACID svojstva, već se umjesto toga odlučuju za principe CAP teoreme i BASE filozofije, kojima je prioritet dostupnost i konačna konzistentnost u odnosu na strogu konzistentnost.
Transakcioni izlazni sandučić pruža barem jednom garanciju i može se implementirati na nekoliko pristupa:
Praćenje dnevnika transakcija
Polling publisher
Pristup praćenja evidencije transakcija podrazumijeva korištenje rješenja specifičnih za bazu podataka kao što je CDC (Change Data Capture). Glavni nedostaci ovog pristupa su:
Specifična rješenja za baze podataka
Povećana latencija zbog specifičnosti implementacije CDC-a
Drugi metod je Polling Publisher , koji olakšava otpremanje odlaznog sandučeta prozivanjem tabele odlaznog sandučeta. Primarni nedostatak ovog pristupa je potencijal za povećanje opterećenja baze podataka, što može dovesti do većih troškova. Nadalje, ne podržavaju sve No-SQL baze podataka efikasno upite za specifične segmente dokumenta. Izdvajanje cijelih dokumenata može, stoga, rezultirati degradacijom performansi.
Evo malog dijagrama sekvence koji objašnjava kako to funkcionira.
Primarni izazov sa obrascem Transakcionog izlaznog sandučeta leži u njegovoj zavisnosti od ACID svojstava baze podataka. To bi moglo biti jednostavno u tipičnim OLTP bazama podataka, ali predstavlja izazove u području NoSQL-a. Da bi se ovo riješilo, potencijalno rješenje je iskoristiti dnevnik dodavanja (na primjer, Kafka) odmah od pokretanja obrade zahtjeva.
Umjesto da direktno obrađujemo naredbu 'podnesi zahtjev za kredit', mi je odmah šaljemo internoj Kafkinoj temi, a zatim klijentu vraćamo 'prihvaćeni' rezultat. Međutim, pošto je velika vjerovatnoća da naredbu još treba obraditi, ne možemo odmah obavijestiti kupca o rezultatu. Da bismo upravljali ovom konačnom konzistentnošću, možemo koristiti tehnike kao što su dugo ispitivanje, anketiranje koje pokreće klijent, optimistična ažuriranja korisničkog sučelja ili korištenje WebSockets-a ili Server-Sent Events za obavještenja. Međutim, ovo je sasvim posebna tema, pa se vratimo na našu početnu temu.
Poslali smo poruku na internu Kafkinu temu. Usluga aplikacije za kredit tada konzumira ovu poruku — istu komandu koju je primila od klijenta — i započinje obradu. Prvo, izvršava neku poslovnu logiku; tek nakon što se ova logika uspješno izvrši i rezultati se potraju, objavljuje nove poruke o javnoj Kafkinoj temi.
Hajde da pogledamo malo pseudo-koda.
public async Task HandleAsync(SubmitLoanApplicationCommand command, ...) { //First, process business logic var loanApplication = await _loanApplicationService.HandleCommandAsync(command, ...); //Then, send new events to public Kafka topic producer.Send(new LoanApplicationSubmittedEvent(loanApplication.Id)); //Then, commit offset consumer.Commit(); }
Šta ako obrada poslovne logike ne uspije? Bez brige, budući da pomak još nije upisan, poruka će biti ponovljena.
Šta ako slanje novih događaja Kafki ne uspije? Bez brige, budući da je poslovna logika idempotentna, neće kreirati duplikat zahtjeva za kredit. Umjesto toga, pokušat će ponovo poslati poruke javnoj Kafkinoj temi.
Šta ako se poruke šalju Kafki, ali urezivanje pomaka ne uspije? Bez brige, budući da je poslovna logika idempotentna, neće stvoriti duplikat zahtjeva za kredit. Umjesto toga, ponovo će slati poruke javnoj Kafkinoj temi i nadati se da će offset urezivanje ovog puta uspjeti.
Glavni nedostaci ovog pristupa uključuju dodatnu složenost povezanu sa novim stilom programiranja, konačnu konzistentnost (pošto klijent neće odmah znati rezultat) i zahtjev da sva poslovna logika bude idempotentna.
Šta je izvor događaja i kako se može primijeniti ovdje? Izvor događaja je softverski arhitektonski obrazac koji se koristi za modeliranje stanja sistema hvatanjem svih promjena njegovih podataka kao niz nepromjenjivih događaja. Ovi događaji predstavljaju činjenice ili prelaze stanja i služe kao jedini izvor istine za trenutno stanje sistema. Dakle, tehnički, implementacijom sistema za izvor događaja, mi već imamo sve događaje u EventStore-u, a ovaj EventStore potrošači mogu koristiti kao jedini izvor istine o tome šta se dogodilo. Nema potrebe za specifičnim rješenjem baze podataka za praćenje svih promjena ili zabrinutosti oko naručivanja, jedini problem je sjediti na strani za čitanje jer je za dobivanje stvarnog stanja entiteta potrebno reproducirati sve događaje.
U ovom članku smo pregledali nekoliko pristupa za izgradnju pouzdane razmjene poruka u distribuiranim sistemima. Postoji nekoliko preporuka koje bismo mogli uzeti u obzir prilikom izgradnje sistema sa ovim karakteristikama
Sljedeći put ćemo pogledati praktičniji primjer implementacije Transakcionog Outbox-a. Vidi
ti!