Forstå meddelelsesmæglere. Lær mekanikken ved meddelelser med ActiveMQ og Kafka. Kapitel 3. Kafka

Fortsættelse af oversættelsen af ​​en lille bog:
Forstå Message Brokers
forfatter: Jakub Korab, udgiver: O'Reilly Media, Inc., udgivelsesdato: juni 2017, ISBN: 9781492049296.

Forrige oversatte del: Forstå meddelelsesmæglere. Lær mekanikken ved meddelelser med ActiveMQ og Kafka. Kapitel 1 Indledning

KAPITEL 3

Kafka

Kafka er udviklet af LinkedIn for at omgå nogle af begrænsningerne ved traditionelle meddelelsesmæglere og undgå at skulle oprette flere meddelelsesmæglere til forskellige punkt-til-punkt-interaktioner, hvilket er beskrevet i denne bog under "Opskalering og ud" på side 28 Brugertilfælde LinkedIn har i vid udstrækning været afhængig af envejsindtagelse af meget store mængder data, såsom sideklik og adgangslogfiler, mens de stadig har tilladt, at disse data kan bruges af flere systemer uden at påvirke produktiviteten hos producenter eller andre forbrugere. Faktisk er grunden til, at Kafka eksisterer, for at få den slags meddelelsesarkitektur, som Universal Data Pipeline beskriver.

Med dette endelige mål opstod naturligvis andre krav. Kafka bør:

  • Vær ekstremt hurtig
  • Giv mere båndbredde, når du arbejder med beskeder
  • Understøtter Publisher-Subscriber og Point-to-Point-modeller
  • Sæt ikke farten ned med at tilføje forbrugere. For eksempel forringes ydelsen af ​​både køen og emnet i ActiveMQ, efterhånden som antallet af forbrugere på destinationen vokser.
  • Vær vandret skalerbar; hvis en mægler, der vedvarer beskeder, kun kan gøre det ved maksimal diskhastighed, giver det mening at gå ud over en enkelt mæglerinstans for at øge ydeevnen
  • Begræns adgangen til at gemme og genhente beskeder

For at opnå alt dette vedtog Kafka en arkitektur, der omdefinerede roller og ansvar for kunder og meddelelsesmæglere. JMS-modellen er meget mæglerorienteret, hvor mægleren er ansvarlig for at distribuere beskeder og kunderne kun skal bekymre sig om at sende og modtage beskeder. Kafka er på den anden side klientcentreret, hvor kunden påtager sig mange af funktionerne i en traditionel mægler, såsom fair distribution af relevante beskeder til forbrugerne, i bytte for en ekstremt hurtig og skalerbar mægler. For folk, der har arbejdet med traditionelle meddelelsessystemer, kræver arbejdet med Kafka en grundlæggende tankeændring.
Denne tekniske retning har ført til skabelsen af ​​en meddelelsesinfrastruktur, der er i stand til at øge gennemløbet med mange størrelsesordener sammenlignet med en konventionel mægler. Som vi vil se, kommer denne tilgang med afvejninger, hvilket betyder, at Kafka ikke er egnet til visse typer arbejdsbelastninger og installeret software.

Unified Destination Model

For at opfylde de ovenfor beskrevne krav har Kafka kombineret udgiver-abonner og punkt-til-punkt-meddelelser under én slags destination − emne. Dette er forvirrende for folk, der har arbejdet med beskedsystemer, hvor ordet "emne" refererer til en udsendelsesmekanisme, hvorfra (fra emnet) læsning er uholdbar. Kafka-emner bør betragtes som en hybrid destinationstype, som defineret i introduktionen til denne bog.

For resten af ​​dette kapitel, medmindre vi udtrykkeligt angiver andet, vil udtrykket "emne" referere til et Kafka-emne.

For fuldt ud at forstå, hvordan emner opfører sig, og hvilke garantier de giver, skal vi først se på, hvordan de er implementeret i Kafka.
Hvert emne i Kafka har sin egen log.
Producenter, der sender beskeder til Kafka, skriver til denne log, og forbrugere læser fra loggen ved hjælp af pointere, der konstant bevæger sig fremad. Periodisk sletter Kafka de ældste dele af loggen, uanset om meddelelserne i disse dele er blevet læst eller ej. En central del af Kafkas design er, at mægleren er ligeglad med, om beskeder bliver læst eller ej – det er kundens ansvar.

Udtrykkene "log" og "pointer" optræder ikke i Kafka dokumentation. Disse velkendte udtryk bruges her for at hjælpe med forståelsen.

Denne model er helt anderledes end ActiveMQ, hvor beskeder fra alle køer gemmes i samme log, og mægleren markerer beskederne som slettede, efter de er blevet læst.
Lad os nu grave lidt dybere og se mere detaljeret på emneloggen.
Kafka-loggen består af flere partitioner (Figur 3-1). Kafka garanterer streng bestilling i hver partition. Det betyder, at beskeder skrevet til partitionen i en bestemt rækkefølge vil blive læst i samme rækkefølge. Hver partition er implementeret som en rullende logfil, der indeholder delmængde (undersæt) af alle meddelelser sendt til emnet af dets producenter. Det oprettede emne indeholder som standard én partition. Ideen med skillevægge er den centrale idé i Kafka til vandret skalering.

Forstå meddelelsesmæglere. Lær mekanikken ved meddelelser med ActiveMQ og Kafka. Kapitel 3. Kafka
Figur 3-1. Kafka skillevægge

Når en producent sender en besked til et Kafka-emne, bestemmer den, hvilken partition beskeden skal sendes til. Det vil vi se nærmere på senere.

Læser beskeder

Klienten, der ønsker at læse meddelelserne, administrerer en navngivet pointer kaldet forbrugergruppe, som peger på offset beskeder i partitionen. En offset er en trinvis position, der starter ved 0 ved starten af ​​en partition. Denne forbrugergruppe, der refereres til i API'et via det brugerdefinerede group_id, svarer til én logisk forbruger eller system.

De fleste meddelelsessystemer læser data fra destinationen ved hjælp af flere instanser og tråde til at behandle meddelelser parallelt. Der vil således normalt være mange forbrugerinstanser, der deler samme forbrugergruppe.

Problemet med at læse kan repræsenteres som følger:

  • Emnet har flere partitioner
  • Flere grupper af forbrugere kan bruge et emne på samme tid
  • En gruppe af forbrugere kan have flere separate tilfælde

Dette er et ikke-trivielt mange-til-mange-problem. For at forstå, hvordan Kafka håndterer forhold mellem forbrugergrupper, forbrugerforekomster og partitioner, lad os se på en række gradvist mere komplekse læsescenarier.

Forbrugere og forbrugergrupper

Lad os tage udgangspunkt i et emne med én partition (Figur 3-2).

Forstå meddelelsesmæglere. Lær mekanikken ved meddelelser med ActiveMQ og Kafka. Kapitel 3. Kafka
Figur 3-2. Forbruger læser fra partition

Når en forbrugerinstans forbinder med sit eget group_id til dette emne, tildeles den en læsepartition og en offset i denne partition. Placeringen af ​​denne offset kan konfigureres i klienten som en pegepind til den seneste position (nyeste besked) eller tidligste position (ældste besked). Forbrugeren anmoder om (afstemninger) beskeder fra emnet, hvilket får dem til at blive sekventielt læst fra loggen.
Offsetpositionen bliver regelmæssigt forpligtet tilbage til Kafka og gemt som beskeder i et internt emne _forbrugerforskydninger. Læste beskeder slettes stadig ikke, i modsætning til en almindelig mægler, og klienten kan spole forskydningen tilbage for at genbehandle allerede sete beskeder.

Når en anden logisk forbruger forbinder ved hjælp af et andet group_id, administrerer den en anden pointer, der er uafhængig af den første (Figur 3-3). Et Kafka-emne fungerer således som en kø, hvor der er én forbruger og som et normalt publish-subscribe (pub-sub) emne, som flere forbrugere abonnerer på, med den ekstra fordel, at alle beskeder gemmes og kan behandles flere gange.

Forstå meddelelsesmæglere. Lær mekanikken ved meddelelser med ActiveMQ og Kafka. Kapitel 3. Kafka
Figur 3-3. To forbrugere i forskellige forbrugergrupper læser fra samme partition

Forbrugere i en forbrugergruppe

Når en forbrugerinstans læser data fra en partition, har den fuld kontrol over markøren og behandler meddelelser som beskrevet i det foregående afsnit.
Hvis flere forekomster af forbrugere var forbundet med det samme group_id til et emne med én partition, så vil den forekomst, der sidst tilsluttede, få kontrol over markøren, og fra det øjeblik vil den modtage alle meddelelser (Figur 3-4).

Forstå meddelelsesmæglere. Lær mekanikken ved meddelelser med ActiveMQ og Kafka. Kapitel 3. Kafka
Figur 3-4. To forbrugere i samme forbrugergruppe læser fra samme partition

Denne behandlingsmåde, hvor antallet af forbrugerforekomster overstiger antallet af partitioner, kan opfattes som en slags eksklusiv forbruger. Dette kan være nyttigt, hvis du har brug for "aktiv-passiv" (eller "varm-varm") gruppering af dine forbrugerforekomster, selvom det er meget mere typisk at køre flere forbrugere parallelt ("aktiv-aktiv" eller "varm-varm"). forbrugere i standby.

Denne meddelelsesfordelingsadfærd beskrevet ovenfor kan være overraskende sammenlignet med, hvordan en normal JMS-kø opfører sig. I denne model vil beskeder sendt til køen være jævnt fordelt mellem de to forbrugere.

Oftest, når vi opretter flere forekomster af forbrugere, gør vi dette enten for at behandle beskeder parallelt eller for at øge læsehastigheden eller for at øge stabiliteten af ​​læseprocessen. Da kun én forbrugerinstans kan læse data fra en partition ad gangen, hvordan opnås dette så i Kafka?

En måde at gøre dette på er at bruge en enkelt forbrugerinstans til at læse alle meddelelserne og sende dem til trådpuljen. Selvom denne tilgang øger behandlingsgennemstrømningen, øger den kompleksiteten af ​​forbrugerlogikken og gør intet for at øge robustheden af ​​læsesystemet. Hvis en kopi af forbrugeren går ned på grund af et strømsvigt eller lignende hændelse, så stopper subtraktionen.

Den kanoniske måde at løse dette problem på i Kafka er at bruge bОflere partitioner.

Opdeling

Partitioner er hovedmekanismen til at parallelisere læsning og skalering af et emne ud over båndbredden af ​​en enkelt mæglerinstans. For bedre at forstå dette, lad os overveje en situation, hvor der er et emne med to partitioner, og en forbruger abonnerer på dette emne (Figur 3-5).

Forstå meddelelsesmæglere. Lær mekanikken ved meddelelser med ActiveMQ og Kafka. Kapitel 3. Kafka
Figur 3-5. Én forbruger læser fra flere partitioner

I dette scenarie får forbrugeren kontrol over de pointere, der svarer til dens group_id i begge partitioner og begynder at læse beskeder fra begge partitioner.
Når en ekstra forbruger for det samme group_id tilføjes til dette emne, omallokerer Kafka en af ​​partitionerne fra den første til den anden forbruger. Derefter vil hver forekomst af forbrugeren læse fra en partition af emnet (Figur 3-6).

For at sikre, at meddelelser behandles parallelt i 20 tråde, skal du have mindst 20 partitioner. Hvis der er færre skillevægge, vil du stå tilbage med forbrugere, der ikke har noget at arbejde på, som beskrevet tidligere i diskussionen om eksklusive forbrugere.

Forstå meddelelsesmæglere. Lær mekanikken ved meddelelser med ActiveMQ og Kafka. Kapitel 3. Kafka
Figur 3-6. To forbrugere i samme forbrugergruppe læser fra forskellige partitioner

Denne ordning reducerer i høj grad kompleksiteten af ​​Kafka-mægleren sammenlignet med den beskeddistribution, der kræves for at opretholde JMS-køen. Her behøver du ikke bekymre dig om følgende punkter:

  • Hvilken forbruger skal modtage den næste besked, baseret på round-robin-allokering, nuværende kapacitet af forhåndshentningsbuffere eller tidligere beskeder (som for JMS-meddelelsesgrupper).
  • Hvilke beskeder sendes til hvilke forbrugere og om de skal omleveres i tilfælde af fejl.

Det eneste, Kafka-mægleren skal gøre, er at sende beskeder sekventielt til forbrugeren, når denne anmoder om dem.

Kravene til parallelisering af korrekturlæsningen og genafsendelse af fejlbehæftede beskeder forsvinder dog ikke – ansvaret for dem overgår blot fra mægleren til kunden. Det betyder, at de skal tages i betragtning i din kode.

Sender beskeder

Det er producenten af ​​denne beskeds ansvar at bestemme, hvilken partition der skal sendes en besked til. For at forstå mekanismen, hvorved dette gøres, skal vi først overveje, hvad vi egentlig sender.

Mens vi i JMS bruger en meddelelsesstruktur med metadata (headere og egenskaber) og en krop, der indeholder nyttelasten (nyttelast), er meddelelsen i Kafka parret "nøgleværdi". Beskedens nyttelast sendes som en værdi. Nøglen bruges derimod hovedsageligt til partitionering og skal indeholde forretningslogik specifik nøgleat placere relaterede meddelelser i den samme partition.

I kapitel 2 diskuterede vi online-væddemålsscenariet, hvor relaterede begivenheder skal behandles i rækkefølge af en enkelt forbruger:

  1. Brugerkontoen er konfigureret.
  2. Penge indsættes på kontoen.
  3. Der foretages et væddemål, der trækker penge fra kontoen.

Hvis hver begivenhed er en meddelelse, der er sendt til et emne, vil den naturlige nøgle være konto-id'et.
Når en besked sendes ved hjælp af Kafka Producer API, sendes den til en partitionsfunktion, som givet beskeden og Kafka-klyngens aktuelle tilstand returnerer ID'et for den partition, som beskeden skal sendes til. Denne funktion er implementeret i Java gennem Partitioner-grænsefladen.

Denne grænseflade ser sådan ud:

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

Partitioner-implementeringen bruger standard hashing-algoritmen til generelle formål over nøglen til at bestemme partitionen, eller round-robin, hvis der ikke er angivet en nøgle. Denne standardværdi fungerer godt i de fleste tilfælde. Men i fremtiden vil du gerne skrive din egen.

At skrive din egen opdelingsstrategi

Lad os se på et eksempel, hvor du vil sende metadata sammen med meddelelsens nyttelast. Nyttelasten i vores eksempel er en instruktion om at foretage en indbetaling til spilkontoen. En instruktion er noget, som vi gerne vil garanteres ikke bliver ændret ved transmission, og vi ønsker at være sikre på, at kun et betroet opstrømssystem kan igangsætte den instruktion. I dette tilfælde er de afsendende og modtagende systemer enige om brugen af ​​en signatur til at autentificere meddelelsen.
I normal JMS definerer vi simpelthen en "meddelelsessignatur" egenskab og tilføjer den til beskeden. Kafka giver os dog ikke en mekanisme til at videregive metadata, kun en nøgle og en værdi.

Da værdien er en bankoverførselsnyttelast, hvis integritet vi ønsker at bevare, har vi intet andet valg end at definere den datastruktur, der skal bruges i nøglen. Forudsat at vi har brug for et konto-id til partitionering, da alle meddelelser relateret til en konto skal behandles i rækkefølge, vil vi komme med følgende JSON-struktur:

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

Fordi værdien af ​​signaturen vil variere afhængigt af nyttelasten, vil standardhash-strategien for Partitioner-grænsefladen ikke pålideligt gruppere relaterede meddelelser. Derfor bliver vi nødt til at skrive vores egen strategi, der vil analysere denne nøgle og opdele accountId-værdien.

Kafka inkluderer kontrolsummer for at opdage korruption af meddelelser i butikken og har et komplet sæt sikkerhedsfunktioner. Alligevel opstår der nogle gange branchespecifikke krav, som det ovenstående.

Brugerens partitioneringsstrategi skal sikre, at alle relaterede meddelelser ender i den samme partition. Selvom dette virker simpelt, kan kravet være kompliceret af vigtigheden af ​​at bestille relaterede meddelelser, og hvor fast antallet af partitioner i et emne er.

Antallet af partitioner i et emne kan ændre sig over tid, da de kan tilføjes, hvis trafikken går ud over de oprindelige forventninger. Således kan beskednøgler associeres med den partition, de oprindeligt blev sendt til, hvilket indebærer, at et stykke tilstand skal deles mellem producentinstanser.

En anden faktor at overveje er den jævne fordeling af meddelelser på tværs af partitioner. Typisk er nøgler ikke fordelt jævnt på tværs af beskeder, og hash-funktioner garanterer ikke en retfærdig fordeling af beskeder for et lille sæt nøgler.
Det er vigtigt at bemærke, at uanset hvordan du vælger at opdele meddelelser, skal selve separatoren muligvis genbruges.

Overvej kravet om at replikere data mellem Kafka-klynger på forskellige geografiske steder. Til dette formål kommer Kafka med et kommandolinjeværktøj kaldet MirrorMaker, som bruges til at læse beskeder fra en klynge og overføre dem til en anden.

MirrorMaker skal forstå nøglerne til det replikerede emne for at opretholde relativ rækkefølge mellem meddelelser, når der replikeres mellem klynger, da antallet af partitioner for det pågældende emne muligvis ikke er det samme i to klynger.

Brugerdefinerede partitioneringsstrategier er relativt sjældne, da standard hashing eller round robin fungerer godt i de fleste scenarier. Men hvis du har brug for stærke bestillingsgarantier eller har brug for at udtrække metadata fra nyttelast, så er partitionering noget, du bør se nærmere på.

Skalerbarheden og fordelene ved Kafka kommer fra at flytte nogle af den traditionelle mæglers ansvar til kunden. I dette tilfælde træffes der en beslutning om at distribuere potentielt relaterede beskeder blandt flere forbrugere, der arbejder parallelt.

JMS-mæglere skal også håndtere sådanne krav. Interessant nok kræver mekanismen til at sende relaterede meddelelser til den samme forbruger, implementeret gennem JMS Message Groups (en variation af strategien for sticky load balancing (SLB)), også afsenderen til at markere meddelelser som relaterede. I tilfælde af JMS er mægleren ansvarlig for at sende denne gruppe af relaterede meddelelser til én forbruger ud af mange og overføre ejerskabet af gruppen, hvis forbrugeren falder fra.

Producentaftaler

Partitionering er ikke den eneste ting, du skal overveje, når du sender beskeder. Lad os tage et kig på send()-metoderne for Producer-klassen i Java API:

Future < RecordMetadata > send(ProducerRecord < K, V > record);
Future < RecordMetadata > send(ProducerRecord < K, V > record, Callback callback);

Det skal straks bemærkes, at begge metoder returnerer Future, hvilket indikerer, at afsendelsesoperationen ikke udføres med det samme. Resultatet er, at en besked (ProducerRecord) skrives til sendebufferen for hver aktiv partition og sendes til mægleren som en baggrundstråd i Kafka-klientbiblioteket. Selvom dette gør tingene utroligt hurtige, betyder det, at en uerfaren applikation kan miste beskeder, hvis dens proces stoppes.

Som altid er der en måde at gøre afsendelsesoperationen mere pålidelig på bekostning af ydeevnen. Størrelsen af ​​denne buffer kan indstilles til 0, og den afsendende applikationstråd vil blive tvunget til at vente, indtil meddelelsesoverførslen til mægleren er fuldført, som følger:

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

Mere om at læse beskeder

At læse beskeder har yderligere kompleksiteter, der skal spekuleres i. I modsætning til JMS API, som kan køre en meddelelseslytter som svar på en meddelelse Forbruger Kafka kun afstemninger. Lad os se nærmere på metoden afstemning()brugt til dette formål:

ConsumerRecords < K, V > poll(long timeout);

Metodens returværdi er en containerstruktur, der indeholder flere objekter forbrugerrekord fra potentielt flere skillevægge. forbrugerrekord er i sig selv et indehaverobjekt for et nøgleværdi-par med tilhørende metadata, såsom partitionen, hvorfra det er afledt.

Som diskuteret i kapitel 2, skal vi huske på, hvad der sker med beskeder, efter at de er blevet behandlet med succes eller uden succes, for eksempel hvis klienten ikke er i stand til at behandle beskeden, eller hvis den afbrydes. I JMS blev dette håndteret gennem en bekræftelsestilstand. Mægleren vil enten slette den vellykket behandlede meddelelse eller levere den rå eller falske meddelelse igen (forudsat at transaktioner blev brugt).
Kafka arbejder meget anderledes. Beskeder slettes ikke i mægleren efter korrekturlæsning, og hvad der sker ved fejl er korrekturkodens ansvar.

Som vi har sagt, er forbrugergruppen tilknyttet forskydningen i loggen. Logpositionen, der er knyttet til denne offset, svarer til den næste meddelelse, der skal udsendes som svar på afstemning(). Det tidspunkt, hvor denne offset stiger, er afgørende for aflæsningen.

For at vende tilbage til den tidligere omtalte læsemodel består meddelelsesbehandling af tre trin:

  1. Hent en besked til læsning.
  2. Behandle beskeden.
  3. Bekræft besked.

Kafka-forbrugeren kommer med en konfigurationsmulighed enable.auto.commit. Dette er en ofte brugt standardindstilling, som det er almindeligt med indstillinger, der indeholder ordet "auto".

Før Kafka 0.10 ville en klient, der bruger denne mulighed, sende forskydningen af ​​den sidst læste besked ved det næste opkald afstemning() efter forarbejdning. Dette betød, at alle meddelelser, der allerede var blevet hentet, kunne genbehandles, hvis klienten allerede havde behandlet dem, men uventet blev ødelagt før opkald afstemning(). Da mægleren ikke fører nogen oplysning om, hvor mange gange en besked er blevet læst, vil den næste forbruger, der henter den besked, ikke vide, at der er sket noget slemt. Denne adfærd var pseudo-transaktionel. Forskydningen blev kun begået, hvis meddelelsen blev behandlet med succes, men hvis klienten afbrød, ville mægleren sende den samme meddelelse igen til en anden klient. Denne adfærd var i overensstemmelse med meddelelsesleveringsgarantien "mindst en gang".

I Kafka 0.10 er klientkoden blevet ændret, så commit udløses periodisk af klientbiblioteket som konfigureret auto.commit.interval.ms. Denne adfærd er et sted mellem tilstandene JMS AUTO_ACKNOWLEDGE og DUPS_OK_ACKNOWLEDGE. Ved brug af autocommit kunne meddelelser begås, uanset om de rent faktisk blev behandlet - dette kan ske i tilfælde af en langsom forbruger. Hvis en forbruger afbrød, ville beskeder blive hentet af den næste forbruger, startende ved den forpligtede position, hvilket kunne resultere i en glemt besked. I dette tilfælde mistede Kafka ikke beskederne, læsekoden behandlede dem bare ikke.

Denne tilstand har det samme løfte som i version 0.9: beskeder kan behandles, men hvis det mislykkes, bliver forskydningen muligvis ikke begået, hvilket potentielt kan forårsage, at leveringen fordobles. Jo flere beskeder du henter, når du udfører afstemning(), jo mere dette problem.

Som diskuteret i "Læsning af meddelelser fra en kø" på side 21, er der ikke sådan noget som en engangslevering af en meddelelse i et meddelelsessystem, når der tages hensyn til fejltilstande.

I Kafka er der to måder at begå (begå) en offset (offset): automatisk og manuelt. I begge tilfælde kan meddelelser behandles flere gange, hvis meddelelsen blev behandlet, men mislykkedes før commit. Du kan også vælge ikke at behandle beskeden overhovedet, hvis commit skete i baggrunden, og din kode blev fuldført, før den kunne behandles (måske i Kafka 0.9 og tidligere).

Du kan styre den manuelle offset-commit-proces i Kafka forbruger API ved at indstille parameteren enable.auto.commit til falsk og eksplicit at kalde en af ​​følgende metoder:

void commitSync();
void commitAsync();

Hvis du ønsker at behandle beskeden "mindst én gang", skal du begå forskydningen manuelt med commitSync()ved at udføre denne kommando umiddelbart efter behandling af meddelelserne.

Disse metoder tillader ikke, at meddelelser bliver bekræftet, før de behandles, men de gør intet for at eliminere potentielle behandlingsforsinkelser, samtidig med at de ser ud til at være transaktionelle. Der er ingen transaktioner i Kafka. Kunden har ikke mulighed for at gøre følgende:

  • Rul automatisk en falsk besked tilbage. Forbrugerne skal selv håndtere undtagelser, der opstår som følge af problematiske nyttelaster og backend-udfald, da de ikke kan stole på, at mægleren genleverer beskeder.
  • Send beskeder til flere emner i én atomoperation. Som vi snart vil se, kan kontrol over forskellige emner og partitioner ligge på forskellige maskiner i Kafka-klyngen, som ikke koordinerer transaktioner, når de sendes. I skrivende stund er der gjort noget arbejde for at gøre dette muligt med KIP-98.
  • Forbind læsning af en besked fra et emne med at sende en anden besked til et andet emne. Igen afhænger Kafka-arkitekturen af ​​mange uafhængige maskiner, der kører som én bus, og der gøres ikke noget forsøg på at skjule dette. For eksempel er der ingen API-komponenter, der giver dig mulighed for at linke forbruger и Producent i en transaktion. I JMS leveres dette af objektet Sessionsom er skabt af MessageProducere и Besked forbrugere.

Hvis vi ikke kan stole på transaktioner, hvordan kan vi så levere semantik tættere på dem, der leveres af traditionelle meddelelsessystemer?

Hvis der er mulighed for, at forbrugerens modregning kan stige, før beskeden er blevet behandlet, f.eks. under et forbrugernedbrud, så har forbrugeren ingen mulighed for at vide, om dens forbrugergruppe gik glip af beskeden, da den blev tildelt en partition. Så en strategi er at spole forskydningen tilbage til den forrige position. Kafka forbruger API giver følgende metoder til dette:

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

fremgangsmåde søge() kan bruges med metode
offsetsForTimes(Kort timestampsToSearch) at spole tilbage til en tilstand på et bestemt tidspunkt i fortiden.

Implicit betyder brug af denne tilgang, at det er meget sandsynligt, at nogle meddelelser, der tidligere blev behandlet, vil blive læst og behandlet igen. For at undgå dette kan vi bruge idempotent læsning, som beskrevet i kapitel 4, til at holde styr på tidligere sete beskeder og eliminere dubletter.

Alternativt kan din forbrugerkode holdes simpel, så længe beskedtab eller duplikering er acceptabelt. Når vi ser på use cases, som Kafka er almindeligt brugt til, såsom håndtering af loghændelser, metrics, kliksporing osv., indser vi, at tabet af individuelle meddelelser sandsynligvis ikke vil have en væsentlig indvirkning på omkringliggende applikationer. I sådanne tilfælde er standardværdierne ganske acceptable. På den anden side, hvis din ansøgning skal sende betalinger, skal du omhyggeligt tage dig af hver enkelt besked. Det hele kommer ned til kontekst.

Personlige observationer viser, at når intensiteten af ​​beskeder stiger, falder værdien af ​​hver enkelt besked. Store beskeder har en tendens til at være værdifulde, når de ses i en aggregeret form.

Høj tilgængelighed

Kafkas tilgang til høj tilgængelighed er meget forskellig fra ActiveMQs tilgang. Kafka er designet omkring scale-out-klynger, hvor alle broker-instanser modtager og distribuerer beskeder på samme tid.

En Kafka-klynge består af flere mæglerforekomster, der kører på forskellige servere. Kafka er designet til at køre på almindelig selvstændig hardware, hvor hver node har sit eget dedikerede lager. Brugen af ​​SAN (Network Attached Storage) anbefales ikke, fordi flere computerknudepunkter kan konkurrere om tid.Ыe opbevaringsintervaller og skabe konflikter.

Kafka er altid på system. Mange store Kafka-brugere lukker aldrig deres klynger ned, og softwaren opdateres altid med en sekventiel genstart. Dette opnås ved at garantere kompatibilitet med den tidligere version for meddelelser og interaktioner mellem mæglere.

Mæglere forbundet til en serverklynge Dyrepasser, der fungerer som et konfigurationsdataregister og bruges til at koordinere hver mæglers roller. ZooKeeper selv er et distribueret system, der giver høj tilgængelighed gennem replikering af information ved at etablere kvorum.

I basistilfældet oprettes et emne i en Kafka-klynge med følgende egenskaber:

  • Antallet af partitioner. Som diskuteret tidligere afhænger den nøjagtige værdi, der anvendes her, af det ønskede niveau af parallellæsning.
  • Replikeringsfaktoren (faktoren) bestemmer, hvor mange mæglerforekomster i klyngen, der skal indeholde logfiler for denne partition.

Ved at bruge ZooKeepers til koordinering forsøger Kafka at fordele nye partitioner retfærdigt blandt mæglerne i klyngen. Dette gøres af en enkelt instans, der fungerer som en controller.

Ved køretid for hver emnepartition Controller tildele roller til en mægler leder (leder, mester, oplægsholder) og følgere (tilhængere, slaver, underordnede). Mægleren, der fungerer som leder for denne partition, er ansvarlig for at modtage alle de beskeder, der sendes til den af ​​producenterne, og distribuere beskederne til forbrugerne. Når beskeder sendes til en emnepartition, replikeres de til alle mæglernoder, der fungerer som følgere for den partition. Hver node, der indeholder logfiler for en partition, kaldes replika. En mægler kan fungere som leder for nogle partitioner og som følger for andre.

En følger, der indeholder alle beskeder, som lederen har, kaldes synkroniseret replika (en replika, der er i en synkroniseret tilstand, synkroniseret replika). Hvis en mægler, der fungerer som leder for en partition, går ned, kan enhver mægler, der er opdateret eller synkroniseret til den pågældende partition, overtage lederrollen. Det er et utroligt bæredygtigt design.

En del af producentens konfiguration er parameteren ack'er, som bestemmer, hvor mange replikaer, der skal anerkende (anerkende) modtagelse af en meddelelse, før applikationstråden fortsætter med at sende: 0, 1 eller alle. Hvis indstillet til alle, så når en besked modtages, sender lederen en bekræftelse tilbage til producenten, så snart den modtager bekræftelser (anerkendelser) af posten fra flere signaler (inklusive sig selv) defineret af emneindstillingen min.insync.replikaer (standard 1). Hvis meddelelsen ikke kan replikeres med succes, vil producenten kaste en applikationsundtagelse (Ikke nok replikaer eller NotEnoughReplicasAfterAppend).

En typisk konfiguration opretter et emne med en replikeringsfaktor på 3 (1 leder, 2 følgere pr. partition) og parameteren min.insync.replikaer er sat til 2. I dette tilfælde vil klyngen tillade en af ​​de mæglere, der administrerer emnepartitionen, at gå ned uden at påvirke klientapplikationer.

Dette bringer os tilbage til den allerede velkendte afvejning mellem ydeevne og pålidelighed. Replikering sker på bekostning af yderligere ventetid på bekræftelser (bekræftelser) fra følgere. Selvom replikering til mindst tre noder, fordi den kører parallelt, har samme ydeevne som to (der ignoreres stigningen i netværksbåndbreddeforbrug).

Ved at bruge dette replikeringsskema undgår Kafka på en smart måde behovet for fysisk at skrive hver besked til disk med operationen synkronisere(). Hver meddelelse, der sendes af producenten, vil blive skrevet til partitionsloggen, men som beskrevet i kapitel 2, skrives til en fil i første omgang i operativsystemets buffer. Hvis denne besked er replikeret til en anden Kafka-instans og er i dens hukommelse, betyder tabet af lederen ikke, at selve beskeden er gået tabt – den kan overtages af en synkroniseret replika.
Afvisning af at udføre operationen synkronisere() betyder, at Kafka kan modtage beskeder lige så hurtigt, som den kan skrive dem til hukommelsen. Omvendt, jo længere tid du kan undgå at skylle hukommelse til disk, jo bedre. Af denne grund er det ikke ualmindeligt, at Kafka-mæglere får tildelt 64 GB eller mere hukommelse. Denne hukommelsesbrug betyder, at en enkelt Kafka-instans nemt kan køre med hastigheder mange tusinde gange hurtigere end en traditionel meddelelsesmægler.

Kafka kan også konfigureres til at anvende handlingen synkronisere() at sende beskeder til pakker. Da alt i Kafka er pakkeorienteret, fungerer det faktisk ret godt til mange use cases og er et nyttigt værktøj for brugere, der kræver meget stærke garantier. Meget af Kafkas rene ydeevne kommer fra de meddelelser, der sendes til mægleren som pakker, og at disse meddelelser læses fra mægleren i sekventielle blokke vha. nul-kopi operationer (operationer, hvor opgaven med at kopiere data fra et hukommelsesområde til et andet ikke udføres). Sidstnævnte er en stor ydeevne og ressourcegevinst og er kun mulig gennem brug af en underliggende logdatastruktur, der definerer partitionsskemaet.

Meget bedre ydeevne er mulig i en Kafka-klynge end med en enkelt Kafka-mægler, fordi emnepartitioner kan skaleres ud på tværs af mange separate maskiner.

Resultaterne af

I dette kapitel så vi på, hvordan Kafka-arkitekturen genskaber forholdet mellem klienter og mæglere for at give en utrolig robust meddelelsespipeline, med gennemstrømning mange gange større end en konventionel meddelelsesmægler. Vi har diskuteret den funktionalitet, den bruger til at opnå dette og kort set på arkitekturen af ​​applikationer, der leverer denne funktionalitet. I det næste kapitel vil vi se på almindelige problemer, meddelelsesbaserede applikationer skal løse, og diskutere strategier til at håndtere dem. Vi afslutter kapitlet med at skitsere, hvordan man taler om meddelelsesteknologier generelt, så du kan vurdere deres egnethed til dine brugssager.

Forrige oversatte del: Forstå meddelelsesmæglere. Lær mekanikken ved meddelelser med ActiveMQ og Kafka. Kapitel 1

Oversættelse udført: tele.gg/midt_java

Fortsættes ...

Kun registrerede brugere kan deltage i undersøgelsen. Log ind, Vær venlig.

Bruges Kafka i din organisation?

  • Ja

  • Nej

  • Tidligere brugt, nu ikke

  • Vi planlægger at bruge

38 brugere stemte. 8 brugere undlod at stemme.

Kilde: www.habr.com

Tilføj en kommentar