Kuptimi i brokerëve të mesazheve. Studimi i mekanizmave të shkëmbimit të mesazheve përmes ActiveMQ dhe Kafka. KREU 3. Kafka

Vazhdimi i përkthimit të një libri të vogël:
«Kuptimi i Brokerëve të Mesazheve»,
autori: Jakub Korab, botuesi: O’Reilly Media, Inc., data e botimit: Qershor 2017, ISBN: 9781492049296.

Pjesa e mëparshme e përkthyer: Kuptimi i brokerëve të mesazheve. Studimi i mekanizmave të shkëmbimit të mesazheve përmes ActiveMQ dhe Kafka. KREU 1. Hyrje

KREU 3

Kafka

Kafka u zhvillua në LinkedIn për të anashkaluar disa kufizime të brokerëve tradicionalë të mesazheve dhe për të shmangur nevojën për të konfiguruar disa brokerë mesazhesh për ndërveprime të ndryshme "pikë-pikë", siç përshkruhet në këtë libër në seksionin "Shkallëzimi vertikal dhe horizontal" në fq. 28. Skenarët e përdorimit në LinkedIn bazoheshin kryesisht në thithjen unidireksionale të volumit të madh të të dhënave, të tilla si klikimet në faqe dhe regjistrat e qasjes, duke lejuar njëkohësisht që këto të dhëna të përdoren nga disa sisteme pa ndikuar në performancën e prodhuesve ose konsumerëve të tjerë. Në fakt, arsyeja për ekzistencën e Kafka është të krijohet një arkitekturë e tillë e shkëmbimit të mesazheve, e cila përshkruhet nga Universal Data Pipeline.

Duke marrë parasysh këtë qëllim përfundimtar, patjetër që u shfaqën edhe kërkesa të tjera. Kafka duhet të:

  • I jetëshkurtër jashtëzakonisht i shpejtë
  • Të ofrojë një kapacitet të madh gjatë përpunimit të mesazheve
  • Të mbështesë modelet "Botues-Abonent" dhe "Pikë-Pikë"
  • Mos u vonit në shtimin e konsumatorëve. Për shembull, performanca e rrjedhave dhe tematikave në ActiveMQ përkeqësohet me rritjen e numrit të konsumatorëve në destinacion.
  • Të jetë e shkallëzueshme horizontalisht; nëse një broker që ruan mesazhet mund ta bëjë këtë vetëm me shpejtësinë maksimale të diskut, ka kuptim të dalësh jashtë një instance brokeri për të rritur performancën.
  • Të kufizoni aksesin në ruajtjen dhe rikthimin e mesazheve.

Për të arritur gjithçka këtë, në Kafka është pranuar një arkitekturë që ka ri-definuar rolet dhe detyrat e klientëve dhe brokerëve të mesazheve. Modeli JMS është shumë orientuar ndaj brokerit, ku ai është përgjegjës për shpërndarjen e mesazheve, ndërsa klientët duhet të merren vetëm me dërgimin dhe marrjen e mesazheve. Kafka, nga ana tjetër, është orientuar ndaj klientit, ku klienti merr përsipër shumë funksione të brokerit tradicional, siç është shpërndarja e ndershme e mesazheve përkatëse midis konsumatorëve, duke fituar në këmbim një broker jashtëzakonisht të shpejtë dhe të shkallëzuar. Për ata që kanë punuar me sisteme tradicionale të mesazheve, puna me Kafka kërkon ndryshime themelore në pikëpamje.
Ky drejtim inxhinierik ka çuar në krijimin e një infrastrukture për exchange mesazhesh e cila është në gjendje të rrisë shumëfish kapacitetin në krahasim me një broker të zakonshëm. Siç do të shohim, kjo qasje është e shoqëruar me kompromise që nënkuptojnë se Kafka nuk është e përshtatshme për disa lloje ngarkesash dhe softueri të instaluar.

Modeli i unifikuar i adresat

Për të përmbushur kërkesat e përshkruara më sipër, Kafka ka kombinuar shkëmbimin e mesazheve të tipit "publikim-subskripsion" dhe "pikë-në-pikë" brenda një lloji adresuesi — temën. Kjo i ngatërron njerëzit që kanë punuar me sisteme shkëmbimi mesazhesh, ku fjala "temë" i referohet mekanizmit të transmetimit masiv, nga i cili (nga tema) leximi nuk është i besueshëm (është jo i qëndrueshëm). Të temave të Kafka duhet t’u qaset si një lloj hibrid i adresuesit, në përputhje me definicionin e dhënë në hyrjen e kësaj libri.

Në pjesën e mbetur të këtij kapitulli, nëse ne në mënyrë të qartë nuk tregojmë ndryshe, termi "temë" do t'i referohet temës së Kafka.

Për të kuptuar plotësisht se si veprojnë temat dhe çfarë garancish ofrojnë, së pari duhet të shqyrtojmë se si ato janë realizuar në Kafka.
Çdo temë në Kafka ka ditarin e saj.
Prodhuesit që dërgojnë mesazhe në Kafka shkruajnë në këtë jurnal, ndërsa konsumatorët i lexojnë nga journal me ndihmën e treguesve që lëvizin vazhdimisht përpara. Herë pas here, Kafka heq pjesët më të vjetra të jurnalit, pavarësisht nëse mesazhet në këto pjesë janë lexuar apo jo. Një aspekt qendror i dizajnit të Kafka është se brokeri nuk e konsideron nëse mesazhet janë lexuar apo jo – kjo është përgjegjësi e klientit.

Termat "jurnal" dhe "tregues" nuk shfaqen në dokumentacionin e Kafka. Këto terma të njohura përdoren këtu për të ndihmuar në kuptimin.

Ky model është tërësisht ndryshe nga ActiveMQ, ku mesazhet nga të gjitha radhët ruhen në një jurnal të vetëm, dhe brokeri i shënon mesazhet si të fshira pasi ato lexohen.
Tani le të thellohemi pak dhe të shqyrtojmë jurnalin e temës më në detaje.
Jurnali i Kafka-s përbëhet nga disa pjesë (Figura 3-1). Kafka garanton renditjen e rreptë të renditur në çdo pjesë. Kjo do të thotë se mesazhet e regjistruara në një pjesë në një rend të caktuar do të lexohen në të njëjtin rend. Çdo pjesë është realizuar si një skedar ciklik (rolling) logu që përmban nëngrup (subset) të gjitha mesazheve, të dërguara në temë nga prodhuesit e saj. Tema e krijuar ka për default një pjesë. Ideja e pjesëve është një ide thelbësore e Kafka për shkallëzimin horizontal.

Kuptimi i brokerëve të mesazheve. Studimi i mekanizmave të shkëmbimit të mesazheve përmes ActiveMQ dhe Kafka. KREU 3. Kafka
Figura 3-1. Pjesët e Kafka

Kur një prodhues dërgon një mesazh në temën Kafka, ai vendos se në cilën pjesë do ta dërgojë mesazhin. Ne do ta shqyrtojmë këtë më në detaje më vonë.

Leximi i mesazheve

Klienti, i cili dëshiron të lexojë mesazhet, menaxhon një tregues të emëruar, të quajtur grupi i konsumatorëve (consumer group), i cili tregon në shifrën (offset) e mesazhit në pjesë. Shifra është pozita me numër në rritje, e cila fillon nga 0 në fillim të pjesës. Ky grup konsumatorësh, i referuar në API përmes një identifikuesi të përdoruesit group_id, përputhet me një konsumator logjik ose sistem.

Shumica e sistemeve të cilat përdorin ndërlidhjen e mesazheve lexojnë të dhënat nga adresat përmes disa instancave dhe rrjedhave për përpunimin paralel të mesazheve. Kështu, zakonisht do të ketë shumë instanca konsumatorësh që ndajnë të njëjtën grup konsumatorësh.

Problemi i leximit mund të paraqitet si vijon:

  • Tema ka disa ndarje
  • Mund të përdoret nga shumë grupe konsumatorësh në të njëjtën kohë
  • Një grup konsumatorësh mund të ketë disa instanca të veçanta

Kjo është një problem jo trivial «shumë në shumë». Për ta kuptuar se si Kafka trajton marrëdhëniet midis grupeve të konsumatorëve, instancave të konsumatorëve dhe ndarjeve, le të shqyrtojmë një seri skenesh leximi që bëhen gjithnjë e më të komplikuara.

Konsumatorët dhe grupet e konsumatorëve

Le t'i marim si pikë fillimi një temë me një ndarje (Figura 3-2).

Kuptimi i brokerëve të mesazheve. Studimi i mekanizmave të shkëmbimit të mesazheve përmes ActiveMQ dhe Kafka. KREU 3. Kafka
Figura 3-2. Konsumatori lexon nga ndarja

Kur një instancë e konsumatorit lidhet me këtë temë me grup_id e tij të vet, i caktohet një pjesë për të lexuar dhe një pozicion në atë pjesë. Pozita e këtij pozicioni konfigurohet në klient si një tregues për pozitat më të fundit (mesazhi më i ri) ose më të hershme (mesazhi më i vjetër). Konsumatori pyet (polls) për mesazhe nga tema, gjë që çon në leximin e tyre të njëpasnjëshëm nga regjistri.
Pozita e pozicionit komitohet rregullisht mbrapsht në Kafka dhe ruhet si mesazhe në temën e brendshme _consumer_offsets. Mesazhet e lexuara gjithsesi nuk fshihen, ndryshe nga një broker normal, dhe klienti mund të rrotulluar (rewind) pozicionin për të rishikuar mesazhet që janë parë më parë.

Kur një konsumator logjik i dytë lidhet, duke përdorur një grup_id tjetër, ai menaxhon një tregues të dytë, i cili nuk varet nga i pari (Figura 3-3). Në këtë mënyrë, topic-i Kafka vepron si një radhë, ku ekziston një konsumator dhe, si një topic i zakonshëm i publikuar-Subskriptues (pub-sub), në të cilin janë të regjistruar disa konsumatorë, me përfitimin e shtuar që të gjitha mesazhet ruhen dhe mund të përpunohen disa herë.

Kuptimi i brokerëve të mesazheve. Studimi i mekanizmave të shkëmbimit të mesazheve përmes ActiveMQ dhe Kafka. KREU 3. Kafka
Figura 3-3. Dy konsumatorë në grupe të ndryshme konsumatorësh lexojnë nga një particion

Konsumatorët në grupin e konsumatorëve

Kur një shembull konsumatori lexon të dhënat nga particioni, ai kontrollon plotësisht treguesin dhe përpunon mesazhet, siç u përshkrua në seksionin e mëparshëm.
Nëse disa shembuj konsumatorësh janë lidhur me të njëjtin group_id në një topic me një particion, atëherë shembulli që u lidh së fundmi do t’i kalojë kontrollin e treguesit dhe që nga ai moment do të marrë të gjitha mesazhet (Figura 3-4).

Kuptimi i brokerëve të mesazheve. Studimi i mekanizmave të shkëmbimit të mesazheve përmes ActiveMQ dhe Kafka. KREU 3. Kafka
Figura 3-4. Dy konsumatorë në të njëjtin grup konsumatorësh lexojnë nga një particion

Kyçi i tillë i përpunimit, ku numri i ekzemplarëve të konsumatorëve tejkalon numrin e ndarjeve, mund të konsiderohet si një lloj konsumatori monopol. Kjo mund të jetë e dobishme nëse ju nevojitet një klasifikim «aktiv-pasiv» (ose «të ngrohtë-të ftohtë») të ekzemplarëve tuaj të konsumatorëve, megjithatë, funksionimi paralel i disa konsumatorëve («aktiv-aktiv» ose «të ngrohtë-të ngrohtë») është shumë më tipik sesa konsumatorët në gjendje pritjeje.

Një sjellje e tillë e shpërndarjes së mesazheve, e përshkruar më sipër, mund të shkaktojë habi në krahasim me mënyrën se si sillet një radhë e zakonshme JMS. Në këtë model, mesazhet e dërguara në radhë do të shpërndahen njëlloj midis dy konsumatorëve.

Më së shpeshti, kur ne krijojmë disa ekzemplarë konsumatorësh, ne e bëjmë këtë ose për përpunimin paralel të mesazheve, ose për të rritur shpejtësinë e leximit, ose për të rritur qëndrueshmërinë e procesit të leximit. Duke qenë se vetëm një ekzemplar konsumatori mund të lexojë të dhënat nga një ndarje në të njëjtën kohë, si arrihet kjo në Kafka?

Një nga mënyrat për ta bërë këtë është të përdorni një shembull konsumi që lexon të gjitha mesazhet dhe i dërgon ato në një grup threads. Edhe pse ky qasje rrit kapacitetin e përpunimit, ai rrit kompleksitetin e logjikës së konsumatorëve dhe nuk bën asgjë për të rritur qëndrueshmërinë e sistemit të leximit. Nëse një shembull konsumatori ndizet për shkak të një defekti energjie ose ngjarje të ngjashme, leximi ndalon.

Një qasje kanonike për të zgjidhur këtë problem në Kafka është përdorimi ishumë ndarjeve.

Ndarjet

Ndarjet janë mekanizmi kryesor për paralelizimin e leximit dhe zgjerimin e temës përtej kapacitetit të një shembulli brokeri. Për ta kuptuar më mirë këtë, le të shohim një situatë kur ekziston një temë me dy ndarje dhe një konsumator i regjistruar për këtë temë (Figura 3-5).

Kuptimi i brokerëve të mesazheve. Studimi i mekanizmave të shkëmbimit të mesazheve përmes ActiveMQ dhe Kafka. KREU 3. Kafka
Figura 3-5. Një konsumator lexon nga disa ndarje

Në këtë skenar, konsumatori merr kontrollin mbi treguesit që përkojnë me group_id e tij në të dy ndarjet dhe fillon të lexojë mesazhet nga të dy ndarjet.
Kur në këtë temë shtohet një konsumator shtesë për të njëjtin group_id, Kafka ricakton (reallocate) një nga partitë nga konsumatori i parë në konsumatorin e dytë. Pas kësaj, çdo ekzemplar konsumatori do të lexojë nga një parti të temës.Figura 3-6).

Për të siguruar përpunimin e mesazheve paralelisht në 20 rrjedha, do t'ju nevojiten të paktën 20 parti. Nëse partitë janë më pak, do të keni konsumatorë që nuk kanë me çfarë të punojnë, siç u diskutua më parë për konsumatorët monopol.

Kuptimi i brokerëve të mesazheve. Studimi i mekanizmave të shkëmbimit të mesazheve përmes ActiveMQ dhe Kafka. KREU 3. Kafka
Figura 3-6. Dy konsumatorë në të njëjtën grup konsumatorësh lexojnë nga partitë të ndryshme

Kjo skemë ndjeshëm ul kompleksitetin e punës së brokerit Kafka në krahasim me shpërndarjen e mesazheve, e nevojshme për të mbështetur radhën JMS. Këtu, nuk ka nevojë të shqetësoheni për momentet e mëposhtme:

  • Cili konsumator duhet të marrë mesazhin e ardhshëm, në bazë të shpërndarjes rrethore (round-robin), kapacitetit aktual të tamponit të parazgjedhur apo mesazheve të mëparshme (si për grupet e mesazheve JMS).
  • Cilat mesazhe janë dërguar cilëve konsumatorë dhe a duhet të përsëriten në rast dështimi.

Gjithçka që duhet të bëjë brokeri Kafka është të transmetojë mesazhet në mënyrë të rregullt te konsumatori, kur ky i fundit i kërkon ato.

Megjithatë, kërkesat për paralelizimin e leximit dhe dërgimin përsëri të mesazheve të dështuara nuk zhduken asnjëherë — përgjegjësia për to thjesht kalon nga brokeri te klienti. Kjo do të thotë që ato duhet të merren parasysh në kodin tuaj.

Dërgimi i mesazheve

Përgjegjësia për vendosjen se në cilën parti të dërgohet mesazhi i takon producentit të këtij mesazhi. Për të kuptuar mekanizmin me anë të cilit bëhet kjo, fillimisht duhet të shqyrtojmë se çfarë saktësisht po dërgojmë.

Ndërsa në JMS ne përdorim një strukturë mesazhi me metadata (tituj dhe prona) dhe një trup që përmban ngarkesën e dobishme (payload), në Kafka mesazhi është një çift «çelës-vlerë». Ngarkesa e dobishme e mesazhit dërgohet si vlerë (value). Çelësi, nga ana tjetër, përdoret kryesisht për partitizim dhe duhet të përmbajë një çelës të veçantë për logjikën e biznesit, për të vendosur mesazhet e lidhura në të njëjtën parti.

Në Kapitullin 2, ne diskutuam një skenar të bastesh online, kur ngjarjet e lidhura duhet të përpunohen një pas një nga një konsumator të vetëm:

  1. Llogaria e përdoruesit është konfiguruar.
  2. Paratë kreditohet në llogari.
  3. Bëhet një bast që tërheq para nga llogaria.

Nëse çdo ngjarje është një mesazh që dërgohet në temë, atëherë çelësi natyror do të jetë identifikuesi i llogarisë.
Kur mesazhi dërgohet duke përdorur Kafka Producer API, ai kalon nëpër funksionin e ndarjes, i cili, duke marrë parasysh mesazhin dhe gjendjen aktuale të klasterit Kafka, kthen identifikuesin e ndarjes, në të cilën duhet të dërgohet mesazhi. Ky funksion është realizuar në Java përmes ndërfaqes Partitioner.

Kjo ndërfaqe duket si më poshtë:

interface Partitioner {
    int partition(String topic,
        Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster);
}

Implementimi i Partitioner për përcaktimin e pjesëzës përdor si parazgjedhje algoritmin e heshit të çelësit (general-purpose hashing algorithm over the key) ose metoden e rrethit (round-robin), nëse çelësi nuk është specifikuar. Ky vlerë parazgjedhje funksionon mirë në shumicën e rasteve. Megjithatë, në të ardhmen mund të dëshironi të shkruani strategjinë tuaj për particionim.

Shkruani strategjinë tuaj për particionim

Le të shqyrtojmë një shembull, kur dëshironi të dërgoni metad data së bashku me ngarkesën e mesazhit. Ngarkesa në shembullin tonë është një udhëzim për të bërë një depozitë në llogarinë e lojërave. Udhëzimi është ajo që do të doja ta garantonim se nuk do të modifikohet gjatë transmetimit dhe duam të sigurohemi se vetëm një sistem i besueshëm më i lartë mund ta iniciojë këtë udhëzim. Në këtë rast, sistemet dërguese dhe pranuese bien dakord për të përdorur nënshkrimin për të verifikuar autenticitetin e mesazhit.
Në JMS të zakonshëm, thjesht e përcaktojmë pronën "nënshkrimi i mesazhit" dhe e shtojmë atë në mesazh. Megjithatë, Kafka nuk na ofron një mekanizëm për të transmetuar metadatat — vetëm çelësin dhe vlerën.

Duke vlera është ngarkesa e transferit bankar (bank transfer payload), integriteti i së cilës ne duam ta ruajmë, ne nuk kemi alternativë tjetër përveçse të përcaktojmë strukturën e të dhënave për përdorim në çelës. Duke supozuar se na nevojitet një identifikues llogarie për ndarjen, pasi të gjitha mesazhet që lidhen me llogarinë duhet të trajtohen në rend, ne do të krijojmë strukturën JSON si më poshtë:

{
  "signature": "541661622185851c248b41bf0cea7ad0",
  "accountId": "10007865234"
}

Duke pasur parasysh se vlera e nënshkrimit do të ndryshojë në varësi të ngarkesës, strategjia e parazgjedhur e hashing-ut të ndërfaqes Partitioner nuk do të grumbullojë me besueshmëri mesazhet e lidhura. Prandaj, do të na nevojitet të shkruajmë strategjinë tonë, e cila do të analizojë këtë çelës dhe do të ndajë (partition) vlerën e accountId.

Kafka përfshin shumica kontrolle për zb discovery të dëmtimit të mesazheve në depo dhe ka një grup të plotë funksionesh sigurie. Edhe në këtë rast, ndonjëherë paraqiten kërkesa specifike për industrinë, si ato të përmendura më lart.

Strategjia e ndarjes së përdoruesit duhet të garantoshë që të gjitha mesazhet e lidhura të përfundojnë në një partition të vetme. Edhe pse duket e thjeshtë, kërkesa mund të ndërlikohet për shkak të rëndësisë së renditjes së mesazheve të lidhura dhe se sa fikse është numri i partitioneve në temë.

Numri i partitioneve në temë mund të ndryshojë me kalimin e kohës, pasi ato mund të shtohen nëse trafiku tejkalon pritshmërinë fillestare. Kështu, çelësat e mesazheve mund të lidhen me partitionin në të cilin janë dërguar fillimisht, duke nënkuptuar një pjesë të gjendjes që duhet të shpërndahet ndërmjet instancave të prodhuesit.

Një tjetër faktor që duhen marrë parasysh është barazia e shpërndarjes së mesazheve midis partitioneve. Si rregull, çelësat nuk shpërndahen në mënyrë të barabartë në mesazhe, dhe funksionet e hash-it nuk garantojnë shpërndarje të drejtë të mesazheve për një grup të vogël çelësash.
Është e rëndësishme të theksohet se, siç e vendosni ju mesazhet, ndarësi mund të ketë nevojë të përdoret përsëri.

Le të shqyrtojmë kërkesën për replikimin e të dhënave midis klasterëve Kafka në lokacione të ndryshme gjeografike. Për këtë qëllim, Kafka ofron një mjet komandash të quajtur MirrorMaker, i cili përdoret për të lexuar mesazhe nga një klaster dhe për t'i dërguar ato në një tjetër.

MirrorMaker duhet të kuptojë çelësat e temës së replikueshme, për të mbajtur rendin relativ midis mesazheve gjatë replikimit mes klasterëve, pasi numri i ndarjeve për këtë temë mund të mos përputhet në dy klasterë.

Strategjitë e personalizuara të ndarjes takohen relativisht rrallë, pasi ndarjet e defoltit si hashing ose cikli i rrotullimit funksionojnë me sukses në shumicën e skenarëve. Megjithatë, nëse ju nevojiten garanci të rrepta për renditje ose nëse duhet të nxirrni metadata nga ngarkesat, atëherë ndarja është diçka që duhet të shqyrtoni më në detaje.

Avantazhet e shkallëzueshmërisë dhe performancës së Kafka-s janë rezultat i transferimit të disa detyrave tradicionale të brokerit te klienti. Në këtë rast, merret një vendim për shpërndarjen e mesazheve që potencialisht lidhen në disa konsumatore që punojnë paralelisht.

Brokes JMS gjithashtu duhet të përballen me këto kërkesa. Është interesante se mekanizmi i dërgimit të mesazheve të lidhura te i njëjti konsumator, realizuar nëpërmjet JMS Message Groups (një lloj strategjie balancimi ngjitës), kërkon gjithashtu që dërguesi të etiketohet mesazhet si të lidhura. Në rastin e JMS, brokeri është përgjegjës për dërgimin e kësaj grupi mesazhesh të lidhura te një konsumator nga shumë dhe për transferimin e të drejtave të pronësisë mbi grupin nëse konsumatori rrëzohet.

Marrëveshjet mbi prodhuesin

Paritizionimi nuk është e vetmja gjë që duhet të merren parasysh gjatë dërgimit të mesazheve. Le të shqyrtojmë metodat send() të klasës Producer në Java API:

Future  send(ProducerRecord  record);
Future  send(ProducerRecord  record, Callback callback);

Duhet menzionuar menjëherë se të dy metodat kthejnë një Future, çka tregon se operacioni i dërgimit nuk kryhet menjëherë. Si rezultat, mesazhi (ProducerRecord) shkruhet në tamponin e dërgimit për çdo parti aktive dhe dërgohet te brokeri në një rrjedhë lokale në bibliotekën e klientit Kafka. Megjithëse kjo e bën procesin jashtëzakonisht të shpejtë, kjo do të thotë se një aplikacion i shkruar keq mund të humbasë mesazhe nëse procesi i tij ndalet.

Si gjithmonë, ka një mënyrë për ta bërë operacionin e dërgimit më të besueshëm në dëm të performancës. Madhësia e këtij tamponi mund të vendoset në 0, dhe rrjedha e aplikacionit dërgues do të detyrohet të presë derisa dërgimi i mesazhit te brokeri të përfundojë, në këtë mënyrë:

RecordMetadata metadata = producer.send(record).get();

Njëherë tjetër për leximin e mesazheve

Leximi i mesazheve ka vështirësi të tjera, për të cilat duhet të mendojmë. Ndryshe nga API JMS, i cili mund të niste një dëgjues mesazhesh (message listener) në përgjigje të mbërritjes së një mesazhi, interface Consumer Kafka vetëm kontrollon (polling). Le të shohim më në detaje metodën poll (), e cila përdoret për këtë qëllim:

ConsumerRecords  poll(long timeout);

Vlera e kthyese e metodës është një strukturë kontainer që përmban disa objekte ConsumerRecord nga potencialisht disa parti. ConsumerRecord është vetë një objekt mbajtës për një çift çelës-vlerë me metadatat përkatëse, siç janë partia nga e cila është marrë.

Siç u diskutua në Kapitullin 2, duhet të kujtojmë vazhdimisht se çfarë ndodh me mesazhet pas përpunimit të tyre me sukses ose dështim, për shembull, nëse klienti nuk mund ta përpunojë mesazhin ose nëse ai ndërprehet. Në JMS, kjo u përpunua përmes modalitetit të konfirmimit (acknowledgement mode). Brokeri ose do të fshijë mesazhin e përpunuar me sukses, ose do ta dërgojë përsëri atë që nuk është përpunuar ose që ka dështuar (nëse janë përdorur transaksionet).
Kafka funksionon krejt ndryshe. Mesazhet nuk fshihen në broker pas leximit dhe përgjegjësia për ato që ndodhin në rast dështimi është mbi kodin që lexon vetë.

Siç e përmendëm, grupi i konsumatorëve është i lidhur me zhvendosjen në regjistër. Pozita në regjistër e lidhur me këtë zhvendosje përputhet me mesazhin tjetër që do të lëshohet si përgjigje në poll (). Momenti i kohës, kur kjo zhvendosje rritet, ka rëndësi vendimtare në lexim.

Duke u kthyer te modeli i leximit, përpunimi i mesazhit përbëhet nga tre etapa:

  1. Nxirrni mesazhin për lexim.
  2. Përpunoni mesazhin.
  3. Konfirmoni mesazhin.

Konsumeri Kafka vjen me një opsion konfigurimi enable.auto.commit. Ky është një cilësim i zakonshëm në mënyrë default, si zakonisht është me cilësimet që përmbajnë fjalën 'auto'.

Derisa Kafka 0.10, klienti që përdorte këtë parametër, dërgonte zhvendosjen e mesazhit të fundit të lexuar në thirrjen e ardhshme poll () pas përpunimit. Kjo do të thoshte se çdo mesazh që ishte nxjerrë (fetched) mund të përpunohej përsëri, nëse clienti e kishte përpunuar atë, por kishte humbur papritur përpara thirrjes. poll (). Duke brokeri nuk ruan asnjë gjendje në lidhje me sa herë që mesazhi është lexuar, konsumatori i ardhshëm që nxjerr këtë mesazh nuk do të dijë se ndodhi diçka e keqe. Ky sjellje ishte pseudo-transaksional. Shkëputja komitohej vetëm në rastin e përpunimit të suksesshëm të mesazhit, por nëse klienti ndalonte operimin, brokeri përsëri dërgonte të njëjtin mesazh tek një klient tjetër. Kjo sjellje përputhej me garantimin e dorëzimit të mesazhevetë paktën një herë«.

Në Kafka 0.10, kodi i klientit u ndryshua në mënyrë që komitimi të fillonte periodikisht nga biblioteka e klientit, në përputhje me konfigurimin auto.commit.interval.ms. Ky kyçje është diku midis modës JMS AUTO_ACKNOWLEDGE dhe DUPS_OK_ACKNOWLEDGE. Kur përdoret auto-përfundimi, mesazhet mund të konfirmohen pavarësisht nëse ato janë trajtuar realisht — kjo mund të ndodhë në rastin e një konsumer-i të ngadaltë. Nëse konsumer-i ndalej, mesazhet do të përfundoheshin nga konsumer-i tjetër, duke filluar nga pozita e përfunduar, e cila mund të çojë në humbjen e një mesazhi. Në këtë rast, Kafka nuk humb mesazhe, kodi i leximit thjesht nuk i trajton ato.

Ky mod ka të njëjtat mundësi si në versionin 0.9: mesazhet mund të trajtohen, por në rast dështimi, offset-i mund të mos jetë përfunduar, gjë që potencialisht mund të sjellë dyfishim të dorëzimit. Sa më shumë mesazhe të nxirrni gjatë ekzekutimit poll (), aq më shumë është problemi.

Siç u diskutua në seksionin "Leximi i mesazheve nga radhët" në fq. 21, në sistemin e shkëmbimit të mesazheve nuk ka një koncept të dorëzimit të vetëm të mesazhit, nëse merret parasysh modet e dështimit.

Në Kafka ka dy mënyra për të konfirmuar (komit) offset-in: automatikisht dhe manualisht. Në të dyja rastet, mesazhet mund të përpunohen disa herë, nëse mesazhi është përpunuar, por ka ndodhur një dështim para komitimit. Ju gjithashtu mund të mos e përpunoni fare mesazhin, nëse komitimi është ndodhur në sfond dhe kodi juaj është përfunduar para se të fillonte përpunimin (ndoshta në Kafka 0.9 dhe versionet e mëparshme).

Të menaxhosh procesin e komitimit të offset-it manualisht mund të bëhet në API-në e konsumatoreve të Kafka, duke vendosur parametrin enable.auto.commit në vlerën false dhe duke thirrur qartë një nga metodat e mëposhtme:

void commitSync();
void commitAsync();

Nëse synoni të përpunoni mesazhin "të paktën një herë", duhet të bëni komitimin e offset-it manualisht me commitSync (), duke e ekzekutuar këtë komandë menjëherë pas përpunimit të mesazheve.

Këto metoda nuk lejojnë konfirmimin (acknowledged) të mesazheve deri sa ato të përpunohen, por ato nuk bëjnë asgjë për të eliminuar potencialin për përpunim të dyfishtë, ndërkohë që krijojnë iluzionin e transaksionalitetit. Në Kafka nuk ka transaksione. Klienti nuk ka mundësinë të bëj këtë:

  • Rifit automatikisht një mesazh të dështuar. Konsumatorët duhet të trajtojnë vetë përjashtimet që ndodhin për shkak të ngarkesave problematike dhe ndërprerjeve të backend-it, pasi ata nuk mund të mbështeten në ripërsëritjen e mesazheve nga brokeri.
  • Dërgoni mesazhe në disa tema në kuadër të një operacioni atomar. Siç do të shohim së shpejti, kontrolli mbi tema dhe pjesët e ndryshme mund të jetë në makina të ndryshme në klasterin Kafka, të cilat nuk koordinojnë transaksionet gjatë dërgimit. Në momentin që po shkruaj këtë artikull, është bërë një punë e caktuar për të bërë këtë të mundur me anë të KIP-98.
  • Lidhni leximin e një mesazhi nga një temë me dërgimin e një mesazhi tjetër në një temë tjetër. Edhe një herë, arkitektura e Kafka-s varet nga shumë makina të pavarura që funksionojnë si një autobus dhe nuk bëhen përpjekje për ta fshehur këtë. Për shembull, nuk ka komponente API që të lejojnë lidhjen. Konsumator dhe Prodhues në transaksion. Në JMS, kjo sigurohet nga objekti Session, nga i cili krijohen MessageProducers dhe MessageConsumers.

Nëse nuk mund të mbështetemi te transaksionet, si mund të sigurojmë semantikë që është më afër asaj që ofrojnë sistemet tradicionale të mesazheve?

Nëse ekziston mundësia që zhvendosja e konsumatorit të rritet para se mesazhi të përprocessed, për shembull, gjatë një dështimi të konsumatorit, atëherë konsumatori nuk ka mënyrë për të ditur nëse grupi i tij i konsumatorëve është humbur mesazhe kur i caktohet një pjesë. Prandaj, një nga strategjitë është të rrokulliset (rewind) zhvendosja në poziten para. API i konsumatorit Kafka ofron metodat e mëposhtme për këtë:

void seek(TopicPartition partition, long offset);
void seekToBeginning(Collection  partitions);

Metoda seek () mund të përdoret me metodën
offsetsForTimes (Map timestampsToSearch) për të rrokullisur në një gjendje në një moment të caktuar në të kaluarën.

Në mënyrë të tërthortë, përdorimi i këtij qasje nënkupton se është shumë e mundshme që disa mesazhe që janë përpunuar më parë do të lexohen dhe përpunohen përsëri. Për të shmangur këtë, ne mund të përdorim leximin idempotent, siç përshkruhet në Kapitullin 4, për të ndjekur mesazhet që janë parë më parë dhe për të përjashtuar dyfishimet.

Si një alternativë, kodi i konsumatorit tuaj mund të jetë i thjeshtë, nëse humbja ose dyfishimi i mesazheve është i pranueshëm. Kur shqyrtojmë skenarët e përdorimit, për të cilat zakonisht përdoret Kafka, si përpunimi i ngjarjeve të regjistrave, metrikeve, ndjekja e klikimeve etj., ne kuptojmë se humbja e mesazheve të veçanta nuk ka të ngjarë të ketë një ndikim të rëndësishëm në aplikacionet përreth. Në këto raste, vlerat e parazgjedhura janë plotësisht të pranueshme. Anasjelltas, nëse aplikacioni juaj ka nevojë të përçojë pagesa, duhet të kujdeseni me kujdes për secilin mesazh individual. E gjithë kjo ndërlidhet me kontekstin.

Vëzhgimet personale tregojnë se me rritjen e intensitetit të mesazheve, vlera e çdo mesazhi të veçantë zvogëlohet. Mesazhet e shumta zakonisht bëhen të vlefshme kur shqyrtohen në formë të agreguar.

Disponueshmëria e lartë (High Availability)

Pragmatizmi i Kafka-s në lidhje me disponueshmërinë e lartë dallon në mënyrë thelbësore nga ai i ActiveMQ. Kafka është zhvilluar mbi klasterë që shkallëzohen horizontalisht, ku të gjitha instancat e brokerit pranojnë dhe shpërndajnë mesazhe në të njëjtën kohë.

Klasteri i Kafka-s përbëhet nga disa instanca brokerësh që punojnë në serverë të ndryshëm. Kafka është krijuar për të punuar në pajisje autonome normale, ku çdo nyje ka ruajtje të saj të dedikuar. Përdorimi i ruajtjeve rrjetësore (SAN) nuk rekomandohet, pasi nyjat e shumta të computimit mund të konkurrojnë për intervalet e kohës së ruajtjes dhe të krijojnë konflikte.ë

Kafka është gjithmonë aktive sistemi. Shumë përdorues të mëdhenj të Kafka kurrë nuk e ndalin klasterin e tyre, dhe softueri gjithmonë siguron përditësimin përmes rindizjes sekondare. Kjo arrihet duke garantuar përputhshmërinë me versionin e mëparshëm për mesazhet dhe ndërveprimet ndërmjet brokerëve.

Brokerët janë të lidhur me klasterin e serverëve ZooKeeper, i cili vepron si regjistri i të dhënave të konfigurimit dhe përdoret për të koordinuar rolet e secilit broker. ZooKeeper vetë është një sistem i shpërndarë që siguron disponueshmëri të lartë përmes replikimit të informacionit përmes vendosjes kuorumit.

Në rastin bazik, një temë krijohet në klasterin Kafka me këto pronësi:

  • Numri i particioneve. Siç u diskutua më parë, vlera e saktë e përdorur këtu varet nga niveli i dëshiruar i leximit paralel.
  • Faktori i replikimit përcakton numrin e instancave të brokerëve në klaster që duhet të përmbajnë regjistrat për këtë particion.

Duke përdor ZooKeepers për koordinim, Kafka përpiqet të bëjë një shpërndarje të drejtë të pjesëve të reja midis brokerëve në kluster. Kjo realizohet nga një instancë që vepron si Kontrolluesi.

në kohën e ekzekutimit për çdo pjesë të temës Kontrolluesi i cakton brokerëve rolet lider (lider, master, i pari) dhe ndjekësve (ndjekës, skllevër, të nënshtruar). Brokeri që vepron si lider për këtë pjesë është përgjegjës për pranimin e të gjitha mesazheve që i dërgojnë producentët dhe shpërndarjen e mesazheve te konsumatorët. Kur dërgohen mesazhe në pjesën e temës, ato replicohen në të gjitha nyjat e brokerit që veprojnë si ndjekës për këtë pjesë. Çdo nyje që përmban regjistrat për pjesën e temës quhet replikë. Brokeri mund të veprojë si lider për disa pjesë dhe si ndjekës për të tjera.

Ndjekësi që përmban të gjitha mesazhet e ruajtura te lideri quhet replikë e sinkronizuar (replica e synchronizuar, in-sync replica). Nëse brokeri që vepron si lider për një partition ndalon së funksionuari, cdo broker që është në gjendje aktuale ose e synchronizuar për këtë partition mund të marrë rolin e liderit. Kjo është një dizajn jashtëzakonisht i qëndrueshëm.

Një pjesë e konfigurimit të prodhuesit është parametri acks, i cili përcakton se sa replika duhet të konfirmojnë (acknowledge) marrjen e mesazhit para se fluksi i aplikacionit të vazhdojë dërgimin: 0, 1 ose të gjitha. Nëse cakttohet vlera all, kur të marrë mesazhin lideri do të dërgojë një konfirmim (confirmation) përsëri tek prodhuesi, sapo të marrë konfirmimin (acknowledgements) e regjistrimit nga disa replika (përfshirë veten), të përcaktuara nga konfigurimi i temës min.insync.replicas (me parametrin e paracaktuar 1). Nëse mesazhi nuk mund të replicohet me sukses, atëherë prodhuesi do të thërrasë një përjashtim për aplikacionin (NotEnoughReplicas ose NotEnoughReplicasAfterAppend).

Në një konfigurim tipik krijohet një temë me një faktor replikimi 3 (1 lider, 2 ndjekës për çdo partition) dhe parametri min.insync.replicas vendoset në vlerën 2. Në këtë rast, klustëri do të lejojë që një nga brokerët që menaxhon ndarjen e temës të shkyçet pa ndikuar në aplikacionet klient.

Kjo na rikthen tek kompromisi i njohur midis performancës dhe besueshmërisë. Replikimi ndodh përmes kohës shtesë të pritjes për konfirmime (acknowledgments) nga ndjekësit. Megjithatë, pasi që kryhet paralelisht, replikimi, për të paktën tri nyje, ka të njëjtën performancë si për dy (duke injoruar rritjen e përdorimit të kapacitetit të rrjetit).

Duke përdorur këtë skemë replikimi, Kafka siguron me mjeshtri shmangien e nevojës për të siguruar regjistrimin fizik të çdo mesazhi në disk përmes operacionit sync (). Çdo mesazh i dërguar nga prodhuesi do të regjistrohet në ditarin e particioneve, por, siç u diskutua në Kapitullin 2, regjistrimi në skedarin fillimisht bëhet në buferin e sistemit operativ. Nëse ky mesazh është replikuar në një instancë tjetër të Kafka dhe ndodhet në memorjen e saj, humbja e liderit nuk do të thotë që vetë mesazhi është humbur — ai mund të merret nga një replikë e sinkronizuar.
Heqja e nevojës për të kryer operacionin sync () do të thotë që Kafka mund të pranojë mesazhe me ritmin me të cilin mund t'i regjistrojë ato në memorje. Dhe anasjelltas, sa më gjatë të shmanget shkarkimi (flushing) i memorjes në disk, aq më mirë. Për këtë arsye, nuk janë të rralla rastet kur brokerëve të Kafka u jepet 64 GB memorje ose më shumë. Kjo përdorim i memorjes do të thotë që një instancë e vetme e Kafka mund të punojë lehtësisht me shpejtësi shumë mijëra herë më të shpejtë se një broker mesazhesh tradicional.

Kafka gjithashtu mund të konfigurohet për të aplikuar operacionin sync () në paketat e mesazheve. Duke qenë se gjithçka në Kafka është orientuar drejt punës me paketa, kjo në të vërtetë funksionon mjaft mirë për shumë skenarë përdorimi dhe është një mjet i dobishëm për përdoruesit që kërkojnë garantime shumë të forta. Pjesa më e madhe e performancës së pastër të Kafka është e lidhur me mesazhet që dërgohen te brokeri në formë paketash, dhe me faktin se këto mesazhe lexohen nga brokeri në blloqe të njëpasnjëshme me zero-copy operacionet (operacione në të cilat nuk kryhet detyra e kopjimit të të dhënave nga një zonë memorjeje në një tjetër). E fundit është një fitim i madh për sa i përket performancës dhe burimeve dhe është e mundur vetëm falë përdorimit të strukturës së dhënash të bazuar në një regjistër, që përcakton skemën e partiçionit.

Në klasterin Kafka, është e mundur një performancë shumë më e lartë sesa duke përdorur një broker të vetëm Kafka, pasi partitë e temës mund të shkallëzohen horizontalisht në shumë makina të veçanta.

Përfundimet

Në këtë kapitull ne shqyrtuam se si arkitektura e Kafka-ripërcakton marrëdhëniet midis klientëve dhe brokerëve për të siguruar një sistem të besueshëm të ndërrimit të mesazheve, me një kapacitet shumë herë më të madh se ai i një brokeri mesazhesh të zakonshëm. Diskutuam funksionalitetin që ajo përdor për të arritur këtë qëllim dhe përshkruam më shkurt arkitekturën e aplikacioneve që ofrojnë këtë funksionalitet. Në kapitullin e ardhshëm do shqyrtojmë problemet e zakonshme që aplikacionet e bazuara në ndërrimin e mesazheve duhet të trajtojnë dhe do diskutojmë strategjitë për zgjidhjen e tyre. Ne do ta mbyllim kapitullin duke përshkruar se si të reflektojmë për teknologjitë e ndërrimit të mesazheve në përgjithësi, që ju të mund të vlerësoni përshtatshmërinë e tyre për skenarët tuaj të përdorimit.

Pjesa e mëparshme e përkthyer: Kuptimi i brokerëve të mesazheve. Studimi i mekanikës së ndërrimit të mesazheve përmes ActiveMQ dhe Kafka. Kapitulli 1

Përkthimi është realizuar nga: tele.gg/middle_java

Vazhdon…

Vetëm përdoruesit e regjistruar mund të marrin pjesë në anketë. Hyni, ju lutemi.

A përdoret Kafka në organizatën tuaj?

  • Po

  • Jo

  • E përdornim më parë, tani jo

  • Kemi planifikuar ta përdorim

38 përdorues e votuan. 8 përdorues u përmbajtën.

Burimi: habr.com

Bli një hosting të besueshëm për faqet me mbrojtje DDoS, VPS VDS serverë 🔥 Bli një hosting të besueshëm për faqet me mbrojtje DDoS, VPS VDS serverë | ProHoster