Forstå meldingsmeglere. Lær mekanikken til meldingstjenester med ActiveMQ og Kafka. Kapittel 3. Kafka

Fortsettelse av oversettelsen av en liten bok:
Forstå meldingsmeglere
forfatter: Jakub Korab, utgiver: O'Reilly Media, Inc., publiseringsdato: juni 2017, ISBN: 9781492049296.

Forrige oversatte del: Forstå meldingsmeglere. Lær mekanikken til meldingstjenester med ActiveMQ og Kafka. kapittel 1 Introduksjon

KAPITTEL 3

Kafka

Kafka ble utviklet av LinkedIn for å omgå noen av begrensningene til tradisjonelle meldingsmeglere og unngå å måtte sette opp flere meldingsmeglere for ulike punkt-til-punkt-interaksjoner, som er beskrevet i denne boken under "Oppskalering og ut" på side 28 Brukstilfeller LinkedIn har i stor grad vært avhengig av enveis inntak av svært store datamengder, for eksempel sideklikk og tilgangslogger, samtidig som det fortsatt lar disse dataene brukes av flere systemer uten å påvirke produktiviteten til produsenter eller andre forbrukere. Faktisk er grunnen til at Kafka eksisterer for å få den typen meldingsarkitektur som Universal Data Pipeline beskriver.

Gitt dette endelige målet, dukket det naturlig opp andre krav. Kafka bør:

  • Vær ekstremt rask
  • Gi mer båndbredde når du arbeider med meldinger
  • Støtt utgiver-abonnent- og punkt-til-punkt-modeller
  • Ikke sakt ned med å legge til forbrukere. For eksempel reduseres ytelsen til både køen og emnet i ActiveMQ ettersom antallet forbrukere på destinasjonen vokser.
  • Vær horisontalt skalerbar; hvis en megler som vedvarer meldinger bare kan gjøre det med maksimal diskhastighet, er det fornuftig å gå utover en enkelt meglerforekomst for å øke ytelsen
  • Begrens tilgangen til å lagre og gjenopprette meldinger

For å oppnå alt dette, tok Kafka i bruk en arkitektur som omdefinerte rollene og ansvaret til klienter og meldingsmeglere. JMS-modellen er veldig meglerorientert, hvor megleren er ansvarlig for å distribuere meldinger og klienter kun trenger å bekymre seg for å sende og motta meldinger. Kafka er derimot klientsentrert, der klienten tar på seg mange av funksjonene til en tradisjonell megler, som rettferdig distribusjon av relevante meldinger til forbrukere, i bytte mot en ekstremt rask og skalerbar megler. For folk som har jobbet med tradisjonelle meldingssystemer, krever det å jobbe med Kafka en grunnleggende tankeendring.
Denne ingeniørretningen har ført til opprettelsen av en meldingsinfrastruktur som er i stand til å øke gjennomstrømningen med mange størrelsesordener sammenlignet med en konvensjonell megler. Som vi vil se, kommer denne tilnærmingen med avveininger, noe som betyr at Kafka ikke er egnet for visse typer arbeidsbelastninger og installert programvare.

Unified Destination Model

For å oppfylle kravene beskrevet ovenfor, har Kafka kombinert publiser-abonner og punkt-til-punkt-meldinger under én type destinasjon − emne. Dette er forvirrende for folk som har jobbet med meldingssystemer, der ordet "emne" refererer til en kringkastingsmekanisme som (fra emnet) lesing er uholdbar fra. Kafka-emner bør betraktes som en hybrid destinasjonstype, som definert i introduksjonen til denne boken.

For resten av dette kapittelet, med mindre vi uttrykkelig sier noe annet, vil begrepet "emne" referere til et Kafka-emne.

For å forstå hvordan emner oppfører seg og hvilke garantier de gir, må vi først se på hvordan de er implementert i Kafka.
Hvert emne i Kafka har sin egen logg.
Produsenter som sender meldinger til Kafka skriver til denne loggen, og forbrukere leser fra loggen ved hjelp av pekere som hele tiden beveger seg fremover. Med jevne mellomrom sletter Kafka de eldste delene av loggen, enten meldingene i disse delene er lest eller ikke. En sentral del av Kafkas design er at megleren ikke bryr seg om meldinger blir lest eller ikke – det er kundens ansvar.

Begrepene "logg" og "peker" vises ikke i Kafka dokumentasjon. Disse velkjente begrepene brukes her for å hjelpe til med forståelsen.

Denne modellen er helt forskjellig fra ActiveMQ, hvor meldinger fra alle køer lagres i samme logg, og megleren merker meldingene som slettet etter at de er lest.
La oss nå grave litt dypere og se på emneloggen mer detaljert.
Kafka-loggen består av flere partisjoner (Figur 3-1). Kafka garanterer streng bestilling i hver partisjon. Dette betyr at meldinger skrevet til partisjonen i en bestemt rekkefølge vil bli lest i samme rekkefølge. Hver partisjon er implementert som en rullende loggfil som inneholder en delmengde (undersett) av alle meldinger sendt til emnet av produsentene. Det opprettede emnet inneholder som standard én partisjon. Ideen om partisjoner er den sentrale ideen til Kafka for horisontal skalering.

Forstå meldingsmeglere. Lær mekanikken til meldingstjenester med ActiveMQ og Kafka. Kapittel 3. Kafka
Figur 3-1. Kafka-skillevegger

Når en produsent sender en melding til et Kafka-emne, bestemmer den hvilken partisjon meldingen skal sendes til. Vi skal se nærmere på dette senere.

Leser meldinger

Klienten som ønsker å lese meldingene administrerer en navngitt peker kalt forbrukergruppe, som peker på offset meldinger i partisjonen. En offset er en inkrementell posisjon som starter ved 0 ved starten av en partisjon. Denne forbrukergruppen, referert til i API via den brukerdefinerte group_id, tilsvarer én logisk forbruker eller system.

De fleste meldingssystemer leser data fra destinasjonen ved å bruke flere forekomster og tråder for å behandle meldinger parallelt. Dermed vil det vanligvis være mange forbrukerinstanser som deler samme forbrukergruppe.

Problemet med lesing kan representeres som følger:

  • Emnet har flere partisjoner
  • Flere grupper av forbrukere kan bruke et emne samtidig
  • En gruppe forbrukere kan ha flere separate forekomster

Dette er et ikke-trivielt mange-til-mange-problem. For å forstå hvordan Kafka håndterer forhold mellom forbrukergrupper, forbrukerforekomster og partisjoner, la oss se på en serie med stadig mer komplekse lesescenarier.

Forbrukere og forbrukergrupper

La oss ta utgangspunkt i et emne med én partisjon (Figur 3-2).

Forstå meldingsmeglere. Lær mekanikken til meldingstjenester med ActiveMQ og Kafka. Kapittel 3. Kafka
Figur 3-2. Forbruker leser fra partisjon

Når en forbrukerforekomst kobler til med sin egen group_id til dette emnet, blir den tildelt en lesepartisjon og en offset i den partisjonen. Posisjonen til denne forskyvningen kan konfigureres i klienten som en peker til den nyeste posisjonen (nyeste melding) eller tidligste posisjon (eldste melding). Forbrukeren ber om (avstemninger) meldinger fra emnet, noe som fører til at de leses sekvensielt fra loggen.
Offsetposisjonen blir regelmessig forpliktet tilbake til Kafka og lagret som meldinger i et internt emne _forbrukerkompensasjoner. Leste meldinger slettes fortsatt ikke, i motsetning til en vanlig megler, og klienten kan spole tilbake forskyvningen for å behandle allerede viste meldinger på nytt.

Når en andre logisk forbruker kobler til ved hjelp av en annen group_id, administrerer den en andre peker som er uavhengig av den første (Figur 3-3). Dermed fungerer et Kafka-emne som en kø der det er én forbruker og som et vanlig publiser-abonner (pub-sub) emne som flere forbrukere abonnerer på, med den ekstra fordelen at alle meldinger lagres og kan behandles flere ganger.

Forstå meldingsmeglere. Lær mekanikken til meldingstjenester med ActiveMQ og Kafka. Kapittel 3. Kafka
Figur 3-3. To forbrukere i forskjellige forbrukergrupper leser fra samme partisjon

Forbrukere i en gruppe forbrukere

Når en forbrukerforekomst leser data fra en partisjon, har den full kontroll over pekeren og behandler meldinger som beskrevet i forrige avsnitt.
Hvis flere forekomster av forbrukere ble koblet med samme group_id til et emne med én partisjon, vil forekomsten som sist koblet til få kontroll over pekeren, og fra det øyeblikket vil den motta alle meldinger (Figur 3-4).

Forstå meldingsmeglere. Lær mekanikken til meldingstjenester med ActiveMQ og Kafka. Kapittel 3. Kafka
Figur 3-4. To forbrukere i samme forbrukergruppe leser fra samme partisjon

Denne behandlingsmåten, der antallet forbrukerforekomster overstiger antall partisjoner, kan betraktes som en slags eksklusiv forbruker. Dette kan være nyttig hvis du trenger "aktiv-passiv" (eller "varm-varm") gruppering av forbrukerforekomstene dine, selv om det er mye mer typisk å kjøre flere forbrukere parallelt ("aktiv-aktiv" eller "varm-varm"). forbrukere i standby.

Denne meldingsdistribusjonsadferden beskrevet ovenfor kan være overraskende sammenlignet med hvordan en normal JMS-kø oppfører seg. I denne modellen vil meldinger som sendes til køen være jevnt fordelt mellom de to forbrukerne.

Oftest, når vi oppretter flere forekomster av forbrukere, gjør vi dette enten for å behandle meldinger parallelt, eller for å øke lesehastigheten, eller for å øke stabiliteten i leseprosessen. Siden bare én forbrukerinstans kan lese data fra en partisjon om gangen, hvordan oppnås dette i Kafka?

En måte å gjøre dette på er å bruke en enkelt forbrukerinstans for å lese alle meldingene og sende dem til trådpoolen. Selv om denne tilnærmingen øker prosesseringsgjennomstrømningen, øker den kompleksiteten til forbrukerlogikken og gjør ingenting for å øke robustheten til lesesystemet. Hvis ett eksemplar av forbrukeren går ned på grunn av strømbrudd eller lignende hendelse, stopper subtraksjonen.

Den kanoniske måten å løse dette problemet på i Kafka er å bruke bОflere partisjoner.

Oppdeling

Partisjoner er hovedmekanismen for å parallellisere lesing og skalering av et emne utover båndbredden til en enkelt meglerforekomst. For bedre å forstå dette, la oss vurdere en situasjon der det er et emne med to partisjoner og en forbruker abonnerer på dette emnet (Figur 3-5).

Forstå meldingsmeglere. Lær mekanikken til meldingstjenester med ActiveMQ og Kafka. Kapittel 3. Kafka
Figur 3-5. Én forbruker leser fra flere partisjoner

I dette scenariet får forbrukeren kontroll over pekerne som tilsvarer dens group_id i begge partisjonene og begynner å lese meldinger fra begge partisjonene.
Når en ekstra forbruker for samme group_id legges til i dette emnet, omdisponerer Kafka en av partisjonene fra den første til den andre forbrukeren. Etter det vil hver forekomst av forbrukeren lese fra en partisjon av emnet (Figur 3-6).

For å sikre at meldinger behandles parallelt i 20 tråder, trenger du minst 20 partisjoner. Hvis det er færre partisjoner, vil du sitte igjen med forbrukere som ikke har noe å jobbe med, som beskrevet tidligere i diskusjonen om eksklusive forbrukere.

Forstå meldingsmeglere. Lær mekanikken til meldingstjenester med ActiveMQ og Kafka. Kapittel 3. Kafka
Figur 3-6. To forbrukere i samme forbrukergruppe leser fra forskjellige partisjoner

Denne ordningen reduserer kompleksiteten til Kafka-megleren i stor grad sammenlignet med meldingsdistribusjonen som kreves for å opprettholde JMS-køen. Her trenger du ikke bekymre deg for følgende punkter:

  • Hvilken forbruker skal motta neste melding, basert på round-robin-allokering, gjeldende kapasitet til forhåndshentingsbuffere eller tidligere meldinger (som for JMS-meldingsgrupper).
  • Hvilke meldinger sendes til hvilke forbrukere og om de skal leveres på nytt ved feil.

Alt Kafka-megleren trenger å gjøre er å sende meldinger sekvensielt til forbrukeren når sistnevnte ber om dem.

Kravene til parallellisering av korrekturlesingen og resending av mislykkede meldinger forsvinner imidlertid ikke – ansvaret for dem går rett og slett over fra megler til oppdragsgiver. Dette betyr at de må tas med i koden din.

Sender meldinger

Det er produsentens ansvar å bestemme hvilken partisjon en melding skal sendes til. For å forstå mekanismen som dette gjøres med, må vi først vurdere hva vi faktisk sender.

Mens vi i JMS bruker en meldingsstruktur med metadata (overskrifter og egenskaper) og en kropp som inneholder nyttelasten (nyttelasten), er meldingen i Kafka par "nøkkelverdi". Meldingsnyttelasten sendes som en verdi. Nøkkelen, derimot, brukes hovedsakelig til partisjonering og må inneholde forretningslogikkspesifikk nøkkelfor å legge relaterte meldinger i samme partisjon.

I kapittel 2 diskuterte vi nettspillscenariet der relaterte hendelser må behandles i rekkefølge av en enkelt forbruker:

  1. Brukerkontoen er konfigurert.
  2. Penger krediteres kontoen.
  3. Det gjøres et spill som trekker penger fra kontoen.

Hvis hver hendelse er en melding postet til et emne, vil den naturlige nøkkelen være konto-ID.
Når en melding sendes ved hjelp av Kafka Producer API, sendes den til en partisjonsfunksjon som, gitt meldingen og den nåværende tilstanden til Kafka-klyngen, returnerer ID-en til partisjonen som meldingen skal sendes til. Denne funksjonen er implementert i Java gjennom Partitioner-grensesnittet.

Dette grensesnittet ser slik ut:

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

Partitioner-implementeringen bruker standard hashing-algoritme for generell bruk over nøkkelen for å bestemme partisjonen, eller round-robin hvis ingen nøkkel er spesifisert. Denne standardverdien fungerer bra i de fleste tilfeller. Men i fremtiden vil du ønske å skrive din egen.

Skrive din egen partisjoneringsstrategi

La oss se på et eksempel der du vil sende metadata sammen med meldingsnyttelasten. Nyttelasten i vårt eksempel er en instruksjon om å gjøre et innskudd til spillkontoen. En instruksjon er noe vi vil garantert ikke endres ved overføring og ønsker å være sikre på at bare et klarert oppstrømssystem kan starte den instruksen. I dette tilfellet er sende- og mottakssystemene enige om bruk av en signatur for å autentisere meldingen.
I vanlig JMS definerer vi ganske enkelt en "meldingssignatur"-egenskap og legger den til i meldingen. Kafka gir oss imidlertid ikke en mekanisme for å sende metadata, bare en nøkkel og en verdi.

Siden verdien er en bankoverføringsnyttelast hvis integritet vi ønsker å bevare, har vi ikke noe annet valg enn å definere datastrukturen som skal brukes i nøkkelen. Forutsatt at vi trenger en konto-ID for partisjonering, siden alle meldinger knyttet til en konto må behandles i rekkefølge, vil vi komme opp med følgende JSON-struktur:

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

Fordi verdien av signaturen vil variere avhengig av nyttelasten, vil ikke standard hashing-strategien til Partitioner-grensesnittet gruppere relaterte meldinger pålitelig. Derfor må vi skrive vår egen strategi som vil analysere denne nøkkelen og dele opp accountId-verdien.

Kafka inkluderer kontrollsummer for å oppdage korrupsjon av meldinger i butikken og har et komplett sett med sikkerhetsfunksjoner. Likevel dukker det noen ganger opp bransjespesifikke krav, som det ovenfor.

Brukerens partisjoneringsstrategi må sikre at alle relaterte meldinger havner i samme partisjon. Selv om dette virker enkelt, kan kravet bli komplisert av viktigheten av å bestille relaterte innlegg og hvor fast antall partisjoner i et emne er.

Antall partisjoner i et emne kan endres over tid, da de kan legges til hvis trafikken går utover de opprinnelige forventningene. Dermed kan meldingsnøkler assosieres med partisjonen de opprinnelig ble sendt til, noe som antyder at en del av tilstanden skal deles mellom produsentforekomster.

En annen faktor å vurdere er den jevne fordelingen av meldinger på tvers av partisjoner. Vanligvis er nøkler ikke fordelt jevnt på tvers av meldinger, og hash-funksjoner garanterer ikke en rettferdig fordeling av meldinger for et lite sett med nøkler.
Det er viktig å merke seg at uansett hvordan du velger å dele meldinger, kan det hende at selve separatoren må gjenbrukes.

Vurder kravet om å replikere data mellom Kafka-klynger på forskjellige geografiske steder. Til dette formålet kommer Kafka med et kommandolinjeverktøy kalt MirrorMaker, som brukes til å lese meldinger fra en klynge og overføre dem til en annen.

MirrorMaker må forstå nøklene til det replikerte emnet for å opprettholde relativ rekkefølge mellom meldinger ved replikering mellom klynger, siden antallet partisjoner for det emnet kanskje ikke er det samme i to klynger.

Egendefinerte partisjoneringsstrategier er relativt sjeldne, da standard hashing eller round robin fungerer bra i de fleste scenarier. Men hvis du trenger sterke bestillingsgarantier eller trenger å trekke ut metadata fra nyttelaster, så er partisjonering noe du bør se nærmere på.

Skalerbarheten og ytelsesfordelene til Kafka kommer fra å flytte noen av ansvaret til en tradisjonell megler til kunden. I dette tilfellet tas det en beslutning om å distribuere potensielt relaterte meldinger på tvers av flere forbrukere som jobber parallelt.

JMS-meglere må også forholde seg til slike krav. Interessant nok krever mekanismen for å sende relaterte meldinger til samme forbruker, implementert gjennom JMS Message Groups (en variant av SLB-strategien), også at avsenderen merker meldinger som relaterte. Når det gjelder JMS, er megleren ansvarlig for å sende denne gruppen av relaterte meldinger til én forbruker av mange, og overføre eierskapet til gruppen dersom forbrukeren faller fra.

Produsentavtaler

Partisjonering er ikke det eneste du bør vurdere når du sender meldinger. La oss ta en titt på send()-metodene til Producer-klassen i Java API:

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

Det bør umiddelbart bemerkes at begge metodene returnerer Future, som indikerer at sendeoperasjonen ikke utføres umiddelbart. Resultatet er at en melding (ProducerRecord) skrives til sendebufferen for hver aktive partisjon og sendes til megleren som en bakgrunnstråd i Kafka-klientbiblioteket. Selv om dette gjør ting utrolig raskt, betyr det at en uerfaren applikasjon kan miste meldinger hvis prosessen stoppes.

Som alltid er det en måte å gjøre sendeoperasjonen mer pålitelig på bekostning av ytelsen. Størrelsen på denne bufferen kan settes til 0, og applikasjonstråden som sender vil bli tvunget til å vente til meldingsoverføringen til megleren er fullført, som følger:

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

Mer om å lese meldinger

Å lese meldinger har flere kompleksiteter som det må spekuleres i. I motsetning til JMS API, som kan kjøre en meldingslytter som svar på en melding, Forbruker Kafka bare meningsmålinger. La oss se nærmere på metoden avstemming()brukt til dette formålet:

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

Returverdien til metoden er en beholderstruktur som inneholder flere objekter forbrukerrekord fra potensielt flere partisjoner. forbrukerrekord er i seg selv et holderobjekt for et nøkkelverdi-par med tilhørende metadata, for eksempel partisjonen det er avledet fra.

Som diskutert i kapittel 2, må vi huske på hva som skjer med meldinger etter at de har blitt vellykket eller mislykket behandlet, for eksempel hvis klienten ikke klarer å behandle meldingen eller hvis den avbryter. I JMS ble dette håndtert gjennom en bekreftelsesmodus. Megleren vil enten slette den vellykket behandlede meldingen, eller levere den rå eller falske meldingen på nytt (forutsatt at transaksjoner ble brukt).
Kafka fungerer veldig annerledes. Meldinger slettes ikke i megler etter korrekturlesing, og det som skjer ved feil er selve korrekturkodens ansvar.

Som vi har sagt er forbrukergruppen knyttet til offset i loggen. Loggposisjonen knyttet til denne forskyvningen tilsvarer den neste meldingen som skal sendes som svar på avstemming(). Tidspunktet når denne forskyvningen øker er avgjørende for lesingen.

For å gå tilbake til lesemodellen diskutert tidligere, består meldingsbehandling av tre stadier:

  1. Hent en melding for lesing.
  2. Behandle meldingen.
  3. Bekreft melding.

Kafka-forbrukeren kommer med et konfigurasjonsalternativ enable.auto.commit. Dette er en ofte brukt standardinnstilling, som er vanlig med innstillinger som inneholder ordet "auto".

Før Kafka 0.10 ville en klient som brukte dette alternativet sende forskyvningen av den siste meldingen som ble lest ved neste samtale avstemming() etter behandling. Dette betydde at alle meldinger som allerede var hentet kunne behandles på nytt hvis klienten allerede hadde behandlet dem, men ble uventet ødelagt før han ringte avstemming(). Fordi megleren ikke oppbevarer noen stat om hvor mange ganger en melding har blitt lest, vil den neste forbrukeren som henter den meldingen ikke vite at noe dårlig har skjedd. Denne oppførselen var pseudotransaksjonell. Forskyvningen ble bare utført hvis meldingen ble behandlet vellykket, men hvis klienten avbrøt, ville megleren sende den samme meldingen igjen til en annen klient. Denne oppførselen var i samsvar med garantien for meldingslevering "i hvert fall en gang".

I Kafka 0.10 er klientkoden endret slik at commit utløses med jevne mellomrom av klientbiblioteket, som konfigurert auto.commit.interval.ms. Denne oppførselen er et sted mellom JMS AUTO_ACKNOWLEDGE- og DUPS_OK_ACKNOWLEDGE-modusene. Ved bruk av autocommit kan meldinger bli forpliktet uavhengig av om de faktisk ble behandlet - dette kan skje i tilfelle av en treg forbruker. Hvis en forbruker avbrøt, vil meldinger bli hentet av neste forbruker, med start på den forpliktede posisjonen, noe som kan resultere i en tapt melding. I dette tilfellet mistet ikke Kafka meldingene, lesekoden behandlet dem bare ikke.

Denne modusen har det samme løftet som i versjon 0.9: meldinger kan behandles, men hvis den mislykkes, kan det hende at forskyvningen ikke blir forpliktet, noe som kan føre til at leveringen dobles. Jo flere meldinger du henter når du kjører avstemming(), jo mer dette problemet.

Som diskutert i "Lese meldinger fra en kø" på side 21, er det ikke noe slikt som en engangslevering av en melding i et meldingssystem når feilmoduser tas i betraktning.

I Kafka er det to måter å begå (begå) en offset (offset): automatisk og manuelt. I begge tilfeller kan meldinger behandles flere ganger hvis meldingen ble behandlet, men mislyktes før commit. Du kan også velge å ikke behandle meldingen i det hele tatt hvis forpliktelsen skjedde i bakgrunnen og koden din ble fullført før den kunne behandles (kanskje i Kafka 0.9 og tidligere).

Du kan kontrollere den manuelle forskyvningsprosessen i Kafka forbruker-API ved å angi parameteren enable.auto.commit til falsk og eksplisitt kalle en av følgende metoder:

void commitSync();
void commitAsync();

Hvis du ønsker å behandle meldingen "minst en gang", må du foreta offset manuelt med commitSync()ved å utføre denne kommandoen umiddelbart etter behandling av meldingene.

Disse metodene tillater ikke at meldinger blir bekreftet før de behandles, men de gjør ingenting for å eliminere potensielle behandlingsforsinkelser samtidig som de ser ut til å være transaksjonelle. Det er ingen transaksjoner i Kafka. Kunden har ikke muligheten til å gjøre følgende:

  • Rull automatisk tilbake en falsk melding. Forbrukerne må selv håndtere unntak som oppstår fra problematiske nyttelaster og driftsstans, siden de ikke kan stole på at megleren leverer meldinger på nytt.
  • Send meldinger til flere emner i én atomoperasjon. Som vi snart vil se, kan kontroll over forskjellige emner og partisjoner ligge på forskjellige maskiner i Kafka-klyngen som ikke koordinerer transaksjoner når de sendes. I skrivende stund er det gjort en del arbeid for å gjøre dette mulig med KIP-98.
  • Forbind å lese en melding fra ett emne med å sende en annen melding til et annet emne. Igjen er arkitekturen til Kafka avhengig av mange uavhengige maskiner som kjører som én buss, og det gjøres ikke noe forsøk på å skjule dette. For eksempel er det ingen API-komponenter som lar deg koble til forbruker и produsent i en transaksjon. I JMS er dette levert av objektet Sessionsom er skapt av Meldingsprodusenter и Meldingsforbrukere.

Hvis vi ikke kan stole på transaksjoner, hvordan kan vi gi semantikk nærmere dem som tilbys av tradisjonelle meldingssystemer?

Hvis det er en mulighet for at forbrukerens forskyvning kan øke før meldingen er behandlet, for eksempel under en forbrukerkrasj, har forbrukeren ingen mulighet til å vite om forbrukergruppen har gått glipp av meldingen da den ble tildelt en partisjon. Så en strategi er å spole tilbake forskyvningen til forrige posisjon. Kafka forbruker-API gir følgende metoder for dette:

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

metode søke() kan brukes med metode
offsetsForTimes(Map timestampsToSearch) å spole tilbake til en tilstand på et bestemt tidspunkt i fortiden.

Implisitt betyr bruk av denne tilnærmingen at det er svært sannsynlig at noen meldinger som tidligere ble behandlet vil bli lest og behandlet på nytt. For å unngå dette kan vi bruke idempotent lesing, som beskrevet i kapittel 4, for å holde styr på tidligere viste meldinger og eliminere duplikater.

Alternativt kan forbrukerkoden din holdes enkel, så lenge meldingstap eller duplisering er akseptabelt. Når vi vurderer brukstilfeller som Kafka ofte brukes til, for eksempel håndtering av logghendelser, beregninger, klikksporing osv., forstår vi at tap av individuelle meldinger neppe vil ha en betydelig innvirkning på omkringliggende applikasjoner. I slike tilfeller er standardverdiene helt akseptable. På den annen side, hvis søknaden din trenger å sende betalinger, må du nøye ta vare på hver enkelt melding. Alt kommer ned til kontekst.

Personlige observasjoner viser at når intensiteten av meldinger øker, reduseres verdien av hver enkelt melding. Store meldinger har en tendens til å være verdifulle når de vises i en aggregert form.

Høy tilgjengelighet

Kafkas tilnærming til høy tilgjengelighet er veldig forskjellig fra ActiveMQs tilnærming. Kafka er designet rundt utskaleringsklynger der alle meglerforekomster mottar og distribuerer meldinger samtidig.

En Kafka-klynge består av flere meglerforekomster som kjører på forskjellige servere. Kafka ble designet for å kjøre på vanlig frittstående maskinvare, der hver node har sin egen dedikerte lagring. Bruk av nettverkstilkoblet lagring (SAN) anbefales ikke fordi flere beregningsnoder kan konkurrere om tid.Ыe lagre intervaller og skape konflikter.

Kafka er alltid på system. Mange store Kafka-brukere slår aldri av klyngene sine, og programvaren oppdateres alltid med en sekvensiell omstart. Dette oppnås ved å garantere kompatibilitet med forrige versjon for meldinger og interaksjoner mellom meglere.

Meglere koblet til en serverklynge Dyrepasser, som fungerer som et konfigurasjonsdataregister og brukes til å koordinere rollene til hver megler. ZooKeeper i seg selv er et distribuert system som gir høy tilgjengelighet gjennom replikering av informasjon ved å etablere quorum.

I utgangspunktet opprettes et emne i en Kafka-klynge med følgende egenskaper:

  • Antall partisjoner. Som diskutert tidligere, avhenger den nøyaktige verdien som brukes her av ønsket nivå av parallelllesing.
  • Replikeringsfaktoren (faktoren) bestemmer hvor mange meglerforekomster i klyngen som skal inneholde logger for denne partisjonen.

Ved å bruke ZooKeepers for koordinering, forsøker Kafka å fordele nye partisjoner rettferdig mellom meglerne i klyngen. Dette gjøres av én instans, som fungerer som en kontroller.

Ved kjøretid for hver emnepartisjon Kontroller tildele roller til en megler leder (leder, mester, programleder) og følgere (tilhengere, slaver, underordnede). Megleren, som fungerer som leder for denne partisjonen, er ansvarlig for å motta alle meldingene som sendes til den av produsentene og distribuere meldingene til forbrukerne. Når meldinger sendes til en emnepartisjon, blir de replikert til alle meglernoder som fungerer som følgere for den partisjonen. Hver node som inneholder logger for en partisjon kalles replika. En megler kan fungere som leder for noen partisjoner og som følger for andre.

En følger som inneholder alle meldingene som lederen har, kalles opp synkronisert kopi (en replika som er i synkronisert tilstand, synkronisert replika). Hvis en megler som fungerer som leder for en partisjon går ned, kan enhver megler som er oppdatert eller synkronisert for den partisjonen ta over lederrollen. Det er et utrolig bærekraftig design.

En del av produsentkonfigurasjonen er parameteren akks, som bestemmer hvor mange replikaer som må bekrefte (godkjenne) mottak av en melding før applikasjonstråden fortsetter å sende: 0, 1 eller alle. Hvis satt til alle, så når en melding mottas, vil lederen sende en bekreftelse tilbake til produsenten så snart den mottar bekreftelser (bekreftelser) av posten fra flere signaler (inkludert seg selv) definert av emneinnstillingen min.insync.replicas (standard 1). Hvis meldingen ikke kan replikeres, vil produsenten sende et programunntak (Ikke nok replikaer eller NotEnoughReplicasAfterAppend).

En typisk konfigurasjon oppretter et emne med en replikeringsfaktor på 3 (1 leder, 2 følgere per partisjon) og parameteren min.insync.replicas er satt til 2. I dette tilfellet vil klyngen tillate en av meglerne som administrerer emnepartisjonen å gå ned uten å påvirke klientapplikasjoner.

Dette bringer oss tilbake til den allerede kjente avveiningen mellom ytelse og pålitelighet. Replikering skjer på bekostning av ekstra ventetid på bekreftelser (bekreftelser) fra følgere. Selv om, fordi den kjører parallelt, har replikering til minst tre noder samme ytelse som to (ignorerer økningen i nettverksbåndbreddebruk).

Ved å bruke dette replikeringsskjemaet, unngår Kafka på en smart måte behovet for å fysisk skrive hver melding til disk med operasjonen sync(). Hver melding som sendes av produsenten vil bli skrevet til partisjonsloggen, men som diskutert i kapittel 2, skrives til en fil i utgangspunktet i operativsystemets buffer. Hvis denne meldingen er replikert til en annen Kafka-instans og er i dens minne, betyr ikke tapet av lederen at selve meldingen gikk tapt – den kan overtas av en synkronisert kopi.
Nektelse av å utføre operasjonen sync() betyr at Kafka kan motta meldinger så raskt som den kan skrive dem til minnet. Omvendt, jo lenger du kan unngå å skylle minne til disk, jo bedre. Av denne grunn er det ikke uvanlig at Kafka-meglere får tildelt 64 GB eller mer minne. Denne minnebruken betyr at en enkelt Kafka-forekomst lett kan kjøre med hastigheter mange tusen ganger raskere enn en tradisjonell meldingsmegler.

Kafka kan også konfigureres til å bruke operasjonen sync() å sende meldingspakker. Siden alt i Kafka er pakkeorientert, fungerer det faktisk ganske bra for mange brukstilfeller og er et nyttig verktøy for brukere som krever veldig sterke garantier. Mye av den rene ytelsen til Kafka kommer fra meldingene som sendes til megleren som pakker og at disse meldingene leses fra megleren i sekvensielle blokker ved hjelp av null-kopi operasjoner (operasjoner der oppgaven med å kopiere data fra ett minneområde til et annet ikke utføres). Sistnevnte er en stor ytelses- og ressursgevinst og er kun mulig gjennom bruk av en underliggende loggdatastruktur som definerer partisjonsskjemaet.

Mye bedre ytelse er mulig i en Kafka-klynge enn med en enkelt Kafka-megler, fordi emnepartisjoner kan skaleres ut på tvers av mange separate maskiner.

Resultater av

I dette kapittelet så vi på hvordan Kafka-arkitekturen reimaginer forholdet mellom klienter og meglere for å gi en utrolig robust meldings-pipeline, med gjennomstrømming mange ganger større enn for en konvensjonell meldingsmegler. Vi har diskutert funksjonaliteten den bruker for å oppnå dette og kort sett på arkitekturen til applikasjonene som gir denne funksjonaliteten. I neste kapittel skal vi se på vanlige problemer meldingsbaserte applikasjoner må løse og diskutere strategier for å håndtere dem. Vi avslutter kapittelet med å skissere hvordan du kan snakke om meldingsteknologier generelt, slik at du kan vurdere deres egnethet for dine brukstilfeller.

Forrige oversatte del: Forstå meldingsmeglere. Lær mekanikken til meldingstjenester med ActiveMQ og Kafka. Kapittel 1

Oversettelse utført: tele.gg/midt_java

To be continued ...

Kun registrerte brukere kan delta i undersøkelsen. Logg inn, vær så snill.

Brukes Kafka i din organisasjon?

  • Ja

  • Ikke

  • Tidligere brukt, nå ikke

  • Vi planlegger å bruke

38 brukere stemte. 8 brukere avsto.

Kilde: www.habr.com

Legg til en kommentar