FörstÄelse för meddelandebrokers. En studie av meddelandeutbytet genom ActiveMQ och Kafka. Kapitel 3. Kafka

FortsÀttning pÄ översÀttningen av en liten bok:
«Understanding Message Brokers»
författare: Jakub Korab, förlag: O’Reilly Media, Inc., utgivningsdatum: juni 2017, ISBN: 9781492049296.

Tidigare översatt del: FörstÄelse av meddelandebrokers. En studie av meddelandeutbyte genom ActiveMQ och Kafka. Kapitel 1. Inledning

KAPITEL 3

Kafka

Kafka utvecklades av LinkedIn för att kringgÄ vissa begrÀnsningar hos traditionella meddelandebrokers och för att undvika behovet av att konfigurera flera meddelandebrokers för olika "punkt-till-punkt"-interaktioner, vilket beskrivs i denna bok i avsnittet om "Vertikal och horisontell skalning" pÄ sida 28. AnvÀndningsscenarierna pÄ LinkedIn baserades huvudsakligen pÄ ensidig insamling av mycket stora datamÀngder, sÄsom klick pÄ sidor och Ätkomstloggar, samtidigt som dessa data tillÀt anvÀndning av flera system utan att pÄverka prestandan hos producenter eller andra konsumenter. Faktum Àr att anledningen till att Kafka existerar Àr för att uppnÄ den meddelandeutbytesarkitektur som beskrivs i Universal Data Pipeline.

Med detta slutmÄl i Ätanke uppstod naturligtvis andra krav. Kafka mÄste:

  • Var extremt snabb
  • Erbjuda hög kapacitet vid meddelandehantering
  • Stötta 'Utgivare-Prenumerant'- och 'Punkt-Punkt'-modeller
  • Inte saktas ner vid tillĂ€gg av konsumenter. Till exempel, prestandan för bĂ„de köer och Ă€mnen i ActiveMQ försĂ€mras med ökat antal konsumenter pĂ„ mottagarens sida
  • Vara horisontellt skalbar; om en broker som lagrar (persisterar) meddelanden kan göra detta endast i maximal hastighet för disken, Ă€r det meningsfullt att gĂ„ bortom en enda brokerinstans för att öka kapaciteten
  • AvgrĂ€nsa Ă„tkomst till lagring och Ă„tervinning av meddelanden

För att uppnÄ allt detta har Kafka antagit en arkitektur som omdefinierar rollerna och ansvaret för klienter och meddelandebrokers. JMS-modellen Àr mycket inriktad pÄ brokern, dÀr denna ansvarar för distributionen av meddelanden, och klienterna behöver bara oroa sig för att skicka och ta emot meddelanden. Kafka, Ä sin sida, Àr klientcentrerad, dÀr klienten tar pÄ sig mÄnga av de traditionella broker-funktionerna, sÄsom rÀttvis fördelning av relevanta meddelanden bland konsumenter, i utbyte mot att fÄ en extremt snabb och skalbar broker. För mÀnniskor som har arbetat med traditionella meddelandesystem krÀver arbete med Kafka fundamentala förÀndringar i synsÀttet.
Denna ingenjörsmÀssig inriktning har lett till skapandet av en meddelandeinfrastruktur som kan öka genomströmningen med flera storleksordningar jÀmfört med en vanlig broker. Som vi kommer att se medför detta tillvÀgagÄngssÀtt kompromisser som innebÀr att Kafka inte Àr lÀmplig för vissa typer av belastningar och installerad programvara.

Enhetlig mottagarmodell

För att uppfylla kraven som nÀmnts ovan har Kafka kombinerat meddelandetyperna 'publikation-prenumeration' och 'punkt-till-punkt' inom en enda typ av mottagare - Àmnet. Detta kan förvirra personer som har arbetat med meddelandesystem dÀr termen 'Àmne' hÀnvisar till en sÀndlistemechanism, frÄn vilken (frÄn Àmnet) lÀsning inte Àr pÄlitlig (Àr icke-varaktig). Kafka-Àmnen bör betraktas som en hybridtyp av mottagare, i enlighet med den definition som ges i inledningen till denna bok.

I resten av detta kapitel, om vi inte uttryckligen anger annat, kommer termen "Àmne" att hÀnvisa till Kafka-Àmnet.

För att helt förstÄ hur Àmnen fungerar och vilka garantier de erbjuder, mÄste vi först titta pÄ hur de Àr implementerade i Kafka.
Varje Àmne i Kafka har sin egen logg.
Producenter som skickar meddelanden till Kafka skriver till denna logg, medan konsumenter lĂ€ser frĂ„n loggen med hjĂ€lp av pekare som stĂ€ndigt flyttas framĂ„t. Med jĂ€mna mellanrum tar Kafka bort de Ă€ldsta delarna av loggen, oavsett om meddelandena i dessa delar har lĂ€sts eller inte. En central aspekt av Kafka-designen Ă€r att brokern inte bryr sig om meddelandena har lĂ€sts eller inte — det Ă€r klientens ansvar.

Termerna "logg" och "pekare" förekommer inte i dokumentationen för Kafka. Dessa vÀlkÀnda termer anvÀnds hÀr för att underlÀtta förstÄelsen.

Denna modell skiljer sig helt frÄn ActiveMQ, dÀr meddelanden frÄn alla köer lagras i en enda logg, och brokern markerar meddelanden som raderade efter att de har lÀsts.
LÄt oss nu dyka djupare ner och titta nÀrmare pÄ Àmnets logg.
Kafka-loggen bestÄr av flera partitioner (Figur 3-1). Kafka garanterar strikt ordning i varje partition. Det innebÀr att meddelanden som skrivs till en partition i en viss ordning kommer att lÀsas i samma ordning. Varje partition implementeras som en cyklisk (rolling) loggfil som innehÄller ett delmÀngd (subset) av alla meddelanden som skickats till Àmnet av dess producenter. Det skapade Àmnet innehÄller som standard en partition. Idén med partitioner Àr en central idé för Kafka för horisontell skalning.

FörstÄelse för meddelandebrokers. En studie av meddelandeutbytet genom ActiveMQ och Kafka. Kapitel 3. Kafka
Figur 3-1. Kafka-partitioner

NÀr en producent skickar ett meddelande till ett Kafka-Àmne bestÀmmer han vilken partition som meddelandet ska skickas till. Vi kommer att titta nÀrmare pÄ detta senare.

LĂ€sa meddelanden

En klient som vill lÀsa meddelanden hanterar en namngiven pekare som kallas konsumentgrupp (consumer group), som pekar pÄ offset för meddelandet i partitionen. Offset Àr en position med ett vÀxande nummer som börjar vid 0 i början av partitionen. Denna konsumentgrupp, som refereras till i API:et via en anvÀndardefinierad identifierare group_id, motsvarar en logisk konsument eller system.

De flesta system som anvÀnder meddelandeutbyte lÀser data frÄn mottagaren genom flera instanser och flöden för parallell meddelandehantering. PÄ sÄ sÀtt kommer det vanligtvis finnas mÄnga instanser av konsumenter som delar en och samma konsumentgrupp.

Problemet med lÀsning kan beskrivas pÄ följande sÀtt:

  • Ämnet har flera partitioner
  • Flera konsumentgrupper kan anvĂ€nda Ă€mnet samtidigt
  • En konsumentgrupp kan ha flera separata instanser

Detta Àr ett icke-trivialt problem av "mÄnga till mÄnga". För att förstÄ hur Kafka hanterar relationerna mellan konsumentgrupper, konsumentinstanser och partitioner, lÄt oss övervÀga en serie gradvis mer komplexa lÀsningsscenarier.

Konsumenter och konsumentgrupper

LÄt oss ta som utgÄngspunkt ett Àmne med en partition (Figure 3-2).

FörstÄelse för meddelandebrokers. En studie av meddelandeutbytet genom ActiveMQ och Kafka. Kapitel 3. Kafka
Figure 3-2. Konsumenten lÀser frÄn partitionen

NÀr en instans av konsumenten ansluter med sitt eget group_id till detta Àmne tilldelas den en partition för lÀsning och en offset i denna partition. Positionen för denna offset konfigureras i klienten, som en pekare till den senaste positionen (det nyaste meddelandet) eller den tidigaste positionen (det Àldsta meddelandet). Konsumenten begÀr (polls) meddelanden frÄn Àmnet, vilket leder till sekventiell lÀsning frÄn loggen.
Offsetpositionen kommitteras regelbundet tillbaka till Kafka och sparas som meddelanden i den interna Àmnet _consumer_offsets. De lÀsta meddelandena raderas inte, till skillnad frÄn en vanlig broker, och klienten kan spola tillbaka (rewind) offseten för att bearbeta redan visade meddelanden igen.

NÀr en andra logisk konsument ansluter, med en annan group_id, hanterar den en andra pekare som inte Àr beroende av den första (Figure 3-3). PÄ sÄ sÀtt fungerar Kafka-Àmnet som en kö, dÀr det finns en konsument och, som ett vanligt pub-sub-Àmne, dÀr flera konsumenter Àr prenumererade, med den extra fördelen att alla meddelanden sparas och kan bearbetas flera gÄnger.

FörstÄelse för meddelandebrokers. En studie av meddelandeutbytet genom ActiveMQ och Kafka. Kapitel 3. Kafka
Figure 3-3. TvÄ konsumenter i olika konsumentgrupper lÀser frÄn en partition

Konsumenter i konsumentgruppen

NÀr en instans av en konsument lÀser data frÄn en partition kontrollerar den helt pekaren och behandlar meddelandena som beskrivs i föregÄende avsnitt.
Om flera instanser av konsumenter har anslutits med samma group_id till en topic med en partition, kommer den instans som anslöt sig sist att fÄ kontroll över pekaren och frÄn och med nu ta emot alla meddelanden (Figur 3-4).

FörstÄelse för meddelandebrokers. En studie av meddelandeutbytet genom ActiveMQ och Kafka. Kapitel 3. Kafka
Figur 3-4. TvÄ konsumenter i samma konsumentgrupp lÀser frÄn en partition

Denna bearbetningslÀge, dÀr antalet konsumentinstanser överstiger antalet partitioner, kan ses som en typ av monopolistisk konsumtion. Detta kan vara anvÀndbart om du behöver en "aktiv-passiv" (eller "het-varm") klusterbildning av dina konsumentinstanser, Àven om parallell drift av flera konsumenter ("aktiv-aktiv" eller "het-het") Àr mycket mer typiskt Àn inaktiva konsumenter.

SÄdant meddelandefördelande beteende, som beskrivits ovan, kan verka förvÄnande i jÀmförelse med hur en typisk JMS-kö beter sig. I den hÀr modellen kommer meddelanden som skickas till köer att fördelas jÀmnt mellan tvÄ konsumenter.

Mest vanligt nÀr vi skapar flera instanser av konsumenter gör vi det antingen för parallell bearbetning av meddelanden, för att öka lÀshastigheten, eller för att öka lÀsningssystemets motstÄndskraft. Eftersom endast en instans av en konsument kan lÀsa data frÄn en partition samtidigt, hur uppnÄs detta i Kafka?

Ett sĂ€tt att göra detta Ă€r att anvĂ€nda en instans av en konsument för att lĂ€sa alla meddelanden och vidarebefordra dem till en trĂ„dpool. Även om denna metod ökar bearbetningens genomströmning, ökar den logikens komplexitet för konsumenterna och gör inget för att öka systemets lĂ€sresiliens. Om en instans av konsumenten stĂ€ngs av pĂ„ grund av strömavbrott eller en liknande hĂ€ndelse, stoppas lĂ€sningen.

Det kanoniska sÀttet att lösa detta problem i Kafka Àr att anvÀnda fler partitioner.Tillflera partitioner.

Partitionering

Partitioner Àr den huvudsakliga mekanismen för att parallellisera lÀsningen och skala Àmnen utöver kapaciteten hos en enda brokerinstans. För att bÀttre förstÄ detta, lÄt oss övervÀga ett scenario dÀr det finns ett Àmne med tvÄ partitioner och en konsument prenumererar pÄ detta Àmne (Figur 3-5).

FörstÄelse för meddelandebrokers. En studie av meddelandeutbytet genom ActiveMQ och Kafka. Kapitel 3. Kafka
Figur 3-5. En konsument lÀser frÄn flera partitioner

I detta scenario fÄr konsumenten kontroll över pekarna som motsvarar hans group_id i bÄda partitionerna och börjar lÀsa meddelanden frÄn bÄda partitionerna.
NÀr en extra konsument lÀggs till för samma group_id, omfördelar Kafka (reallocates) en av partitionerna frÄn den första till den andra konsumenten. DÀrefter kommer varje konsumentinstans att lÀsa frÄn en partition av Àmnet (Figur 3-6).

För att möjliggöra bearbetning av meddelanden parallellt i 20 trÄdar behöver du minst 20 partitioner. Om det finns fÀrre partitioner kommer du att ha konsumenter som inte har nÄgot att arbeta med, vilket tidigare beskrevs i diskussionen om monopolkonsumenter.

FörstÄelse för meddelandebrokers. En studie av meddelandeutbytet genom ActiveMQ och Kafka. Kapitel 3. Kafka
Figur 3-6. TvÄ konsumenter i samma konsumentgrupp lÀser frÄn olika partitioner

Detta schema minskar avsevÀrt komplexiteten i hur Kafka-brokern arbetar jÀmfört med den meddelandefördelning som krÀvs för att stödja JMS-köer. HÀr behöver man inte bekymra sig om följande punkter:

  • Vilken konsument som ska fĂ„ nĂ€sta meddelande baserat pĂ„ rund-robin-fördelning, nuvarande kapacitet hos buffertarna för förhandsupphĂ€mtning eller tidigare meddelanden (som för JMS meddelandegrupper).
  • Vilka meddelanden som har skickats till vilka konsumenter och om de ska levereras igen i hĂ€ndelse av ett fel.

Allt som broker Kafka behöver göra Àr att konsekvent överföra meddelanden till konsumenten nÀr den senare begÀr dem.

Dock försvinner inte kraven pĂ„ parallell lĂ€sning och omleverans av misslyckade meddelanden — ansvaret för dessa övergĂ„r bara frĂ„n brokern till klienten. Detta innebĂ€r att de behöver beaktas i din kod.

Skicka meddelanden

AnsvarsfrÄgan för beslutet om vilken partition ett meddelande ska skickas till faller pÄ producenten av det meddelandet. För att förstÄ den mekanism som detta görs med, behöver vi först titta pÄ vad vi faktiskt skickar.

Medan vi i JMS anvÀnder en meddelandestruktur med metadata (huvuden och egenskaper) och en kropp som innehÄller nyttolasten (payload), Àr meddelandet i Kafka ett par "nyckel-vÀrde". Nyttolasten av meddelandet skickas som vÀrde (value). Nyckeln anvÀnds Ä sin sida frÀmst för partitionering och bör innehÄlla en nyckel specifik för affÀrslogiken, för att placera relaterade meddelanden i samma partition.

I kapitel 2 diskuterade vi scenariot med online-betting nÀr relaterade hÀndelser mÄste bearbetas i ordning av en konsument:

  1. AnvÀndarkontot Àr instÀllt.
  2. Pengarna sÀtts in pÄ kontot.
  3. En insats görs, vilket drar pengar frÄn kontot.

Om varje hÀndelse representerar ett meddelande som skickas till en topic, skulle den naturliga nyckeln i detta fall vara kontots identifierare.
NÀr ett meddelande skickas med Kafka Producer API, överlÀmnas det till partitioneringsfunktionen, som, med hÀnsyn till meddelandet och det aktuella tillstÄndet i Kafka-klustret, returnerar identifieraren för den partition dÀr meddelandet ska skickas. Denna funktion implementeras i Java genom grÀnssnittet Partitioner.

Detta grÀnssnitt ser ut som följer:

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

Implementeringen av Partitioner för att bestÀmma partitionen anvÀnder standardalgoritmen för nyckelhackning (general-purpose hashing algorithm over the key) eller rond-robin, om nyckeln inte anges. Detta standardvÀrde fungerar bra i de flesta fall. Men i framtiden kanske du vill skriva din egen.

Skriva en egen partitioneringsstrategi

LÄt oss övervÀga ett exempel dÀr du vill skicka metadata tillsammans med nyttolasten av meddelandet. Nyttolasten i vÄrt exempel Àr en instruktion för att sÀtta in pengar pÄ spelkontot. Instruktionen Àr nÄgot som vi vill garantera att inte modifieras vid överföring och vi vill vara sÀkra pÄ att endast ett betrott överordnat system kan initiera denna instruktion. I detta fall överenskommer sÀndande och mottagande system om att anvÀnda en signatur för att verifiera meddelandets Àkthet.
I ett vanligt JMS definierar vi helt enkelt egenskapen "meddelandesignatur" och lĂ€gger till den i meddelandet. DĂ€remot tillhandahĂ„ller Kafka ingen mekanism för att skicka metadata — endast nyckel och vĂ€rde.

Eftersom vÀrdet Àr nyttolasten för en banköverföring (bank transfer payload), vars integritet vi vill bevara, har vi inget annat val Àn att definiera datakonstruktionen som ska anvÀndas i nyckeln. Förutsatt att vi behöver ett konto-ID för partionering, eftersom alla meddelanden som rör kontot mÄste behandlas i ordning, kommer vi att konstruera följande JSON-struktur:

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

Eftersom vÀrdet av signaturen kommer att variera beroende pÄ nyttolasten, kommer standardhashningsstrategin för Partitioner-grÀnssnittet inte att pÄ ett tillförlitligt sÀtt gruppera relaterade meddelanden. DÀrför behöver vi skriva en egen strategi som analyserar denna nyckel och partitionerar vÀrdet av accountId.

Kafka inkluderar kontrollsummor för att upptÀcka skador pÄ meddelanden i lagret och har en full uppsÀttning sÀkerhetsfunktioner. Trots detta dyker det ibland upp specifika branschkrav, som exemplifierat ovan.

Den anpassade partitioneringsstrategin mĂ„ste garantera att alla relaterade meddelanden hamnar i samma partition. Även om detta verkar enkelt kan kravet kompliceras av vikten av att ordna relaterade meddelanden och hur fast antal partitioner som finns i Ă€mnet.

Antalet partitioner i ett Àmne kan förÀndras över tid, eftersom de kan lÀggas till om trafiken överskrider de ursprungliga förvÀntningarna. DÀrför kan meddelandenycklar vara kopplade till den partition som de ursprungligen skickades till, vilket implicerar en del av tillstÄndet som mÄste fördelas mellan producenter-instansarna.

En annan faktor att ta hÀnsyn till Àr even distributionen av meddelanden mellan partitioner. Generellt fördelas nycklar inte jÀmnt bland meddelandena, och hash-funktioner garanterar inte en rÀttvis fördelning av meddelanden för ett litet antal nycklar.
Det Àr viktigt att notera att oavsett hur du vÀljer att dela meddelanden, kan avgrÀnsaren behöva anvÀndas om igen.

LÄt oss övervÀga kravet pÄ datareplication mellan Kafka-kluster pÄ olika geografiska platser. För detta ÀndamÄl levereras Kafka med ett kommandoradsverktyg som heter MirrorMaker, vilket anvÀnds för att lÀsa meddelanden frÄn ett kluster och överföra dem till ett annat.

MirrorMaker mÄste förstÄ nycklarna för det replikerade Àmnet för att bibehÄlla ordningen mellan meddelanden vid replikering mellan kluster, eftersom antalet partitioner för detta Àmne kan variera mellan de tvÄ klustren.

Anpassade partitioneringsstrategier förekommer relativt sÀllan, eftersom de standardmÀssiga hash- eller cykelfördelningarna fungerar framgÄngsrikt i de flesta scenarier. Men om du behöver strikta garantier för ordning eller om du mÄste extrahera metadata frÄn nyttolaster, Àr partitionering nÄgot du bör titta nÀrmare pÄ.

Fördelarna med Kafka:s skalbarhet och prestanda beror pÄ att vissa uppgifter för en traditionell broker flyttas till klienten. I detta fall görs beslutet att fördela potentiellt relaterade meddelanden till flera konsumenter som arbetar parallellt.

JMS-brokers mÄste ocksÄ hantera sÄdana krav. Intressant nog krÀver mekanismen för att skicka relaterade meddelanden till samma konsument, som realiseras genom JMS Message Groups (en typ av strategi för klibbig belastningsbalansering), att avsÀndaren markerar meddelanden som relaterade. I JMS:s fall ansvarar brokern för att skicka den gruppen av relaterade meddelanden till en konsument av flera och överföra ÀganderÀtten till gruppen om konsumenten avbryts.

Överenskommelser för producenter

Partionering Àr inte det enda att tÀnka pÄ nÀr man skickar meddelanden. LÄt oss titta pÄ metoderna send() i Producer-klassen i Java API:

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

Det bör noteras att bĂ„da metoderna returnerar Future, vilket indikerar att sĂ€ndningsoperationen inte utförs omedelbart. Resultatet blir att meddelandet (ProducerRecord) skrivs till sĂ€ndningsbufferten för varje aktiv partition och överförs till mĂ€klaren av en bakgrundstrĂ„d i Kafka-klientbiblioteket. Även om detta gör arbetet otroligt snabbt, betyder det att ett oprofessionellt skrivet program kan förlora meddelanden om dess process stoppas.

Som alltid finns det ett sÀtt att göra sÀndningsoperationen mer pÄlitlig genom att kompromissa med prestandan. Storleken pÄ denna buffert kan stÀllas in till 0, vilket tvingar sÀndande applikationsström att vÀnta tills överföringen av meddelandet till mÀklaren Àr slutförd, pÄ följande sÀtt:

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

Mer om att lÀsa meddelanden

Att lÀsa meddelanden har ytterligare utmaningar som Àr vÀrda att övervÀga. Till skillnad frÄn JMS API, som kan starta en meddelandelistor (message listener) som svar pÄ inkommande meddelanden, sÄ anvÀnder grÀnssnittet Consumer Kafka anvÀnder endast polling. LÄt oss titta nÀrmare pÄ metoden poll (), som anvÀnds för detta ÀndamÄl:

ConsumerRecords  poll(long timeout);

ReturvÀrdet av metoden Àr en behÄllarstruktur som innehÄller flera objekt ConsumerRecord frÄn potentiellt flera partitioner. ConsumerRecord Àr sjÀlv en objektbehÄllare för ett nyckel-vÀrde-par med motsvarande metadata, sÄsom partitionen det har hÀmtats frÄn.

Som diskuterades i kapitel 2 mÄste vi stÀndigt hÄlla i Ätanke vad som hÀnder med meddelandena efter att de har har hanterats framgÄngsrikt eller misslyckats, till exempel om klienten inte kan bearbeta meddelandet eller om den kraschar. I JMS hanterades detta genom bekrÀftelselÀge (acknowledgement mode). MÀklaren tar antingen bort det framgÄngsrikt hanterade meddelandet eller omdistribuerar det ohanterade eller misslyckade (under förutsÀttning att transaktioner har anvÀnts).
Kafka fungerar helt annorlunda. Meddelanden tas inte bort frÄn mÀklaren efter att de har lÀsts och ansvaret för vad som hÀnder vid ett fel ligger pÄ den lÀsande koden.

Som vi redan har nÀmnt Àr en konsumentgrupp kopplad till en förskjutning i loggen. Positionen i loggen kopplad till denna förskjutning motsvarar nÀsta meddelande som kommer att ges som svar. poll ()Tidpunkten nÀr denna förskjutning ökar Àr avgörande för lÀsning.

ÅtervĂ€nder vi till lĂ€sningsmodellen som diskuterats tidigare, bestĂ„r bearbetningen av meddelandet av tre steg:

  1. Extrahera meddelandet för lÀsning.
  2. Bearbeta meddelandet.
  3. BekrÀfta meddelandet.

Kafka-konsumenten levereras med en konfigurationsalternativ enable.auto.commit. Detta Àr en ofta anvÀnd standardinstÀllning, som vanligtvis Àr fallet med instÀllningar som innehÄller ordet 'auto'.

Fram till Kafka 0.10 skickade klienten som anvÀnde denna parameter förskjutningen av det senaste lÀsta meddelandet vid nÀsta anrop poll () efter bearbetning. Det betydde att eventuella meddelanden som redan hade extraherats (fetched) kunde behandlas pÄ nytt om klienten redan hade bearbetat dem men ovÀntat stÀngdes av innan anropet. poll (). Eftersom mÀklaren inte sparar nÄgot tillstÄnd betrÀffande hur mÄnga gÄnger ett meddelande har lÀsts, kommer nÀsta konsument som extraherar detta meddelande inte veta att nÄgot gick fel. Detta beteende var pseudotransaktionellt. Förskjutningen bekrÀftades endast efter framgÄngsrik bearbetning av meddelandet, men om klienten avbröts skickade mÀklaren samma meddelande till en annan klient. Detta beteende stÀmde överens med garantin för meddelandeförsÀndelse 'minst en gÄng«.

I Kafka 0.10 Àndrades klientkoden sÄ att bekrÀftelsen nu startades periodiskt av klientbiblioteket, i enlighet med instÀllningen auto.commit.interval.ms. Detta beteende ligger nÄgonstans mellan lÀgena JMS AUTO_ACKNOWLEDGE och DUPS_OK_ACKNOWLEDGE. Vid anvÀndning av autocommit kunde meddelanden bekrÀftas oavsett om de faktiskt hade bearbetats - detta kunde intrÀffa vid en lÄngsam konsument. Om konsumenten stÀngdes av, extraherades meddelandena av nÀsta konsument, som började frÄn den bekrÀftade positionen, vilket kunde leda till att meddelanden gick förlorade. I detta fall förlorade Kafka inte meddelanden, koden som lÀste dem bearbetade helt enkelt inte dem.

Detta lÀge har samma perspektiv som i version 0.9: meddelanden kan bearbetas, men vid ett fel kan förskjutningen kanske inte ha bekrÀftats, vilket potentiellt kan leda till dubblerad leverans. Ju fler meddelanden du extraherar samtidigt poll (), desto större Àr detta problem.

Som diskuterats i avsnittet "LÀsa meddelanden frÄn kön" pÄ sida 21, finns det inget begrepp som engÄngsleverans av ett meddelande i meddelandesystemet, med tanke pÄ felhanteringslÀgen.

I Kafka finns det tvÄ sÀtt att registrera (committa) en offset: automatiskt och manuellt. I bÄda fallen kan meddelanden behandlas flera gÄnger om ett meddelande har bearbetats, men ett fel intrÀffade innan det blev committat. Du kan Àven helt lÄta bli att behandla ett meddelande om commit Àgde rum i bakgrunden och din kod har avslutats innan den pÄbörjade behandlingen (möjligen i Kafka 0.9 och tidigare versioner).

Du kan styra processen för manuell commit av offset i Kafka-konsumerarens API genom att stÀlla in parameter enable.auto.commit till false och explícit anropa en av följande metoder:

void commitSync();
void commitAsync();

Om du strÀvar efter att behandla ett meddelande "Ätmintone en gÄng" mÄste du committa offset manuellt med commitSync (), genom att köra detta kommando omedelbart efter att ha behandlat meddelandena.

Dessa metoder tillÄter inte bekrÀftelse (acknowledged) av meddelanden innan de har behandlats, men de gör inget för att förhindra potentiell dubbelbehandling, samtidigt som de skapar en illusion av transaktionalitet. Det finns inga transaktioner i Kafka. Klienten har inte möjlighet att göra följande:

  • Automatiskt rulla tillbaka (roll back) ett misslyckat meddelande. Konsumenter mĂ„ste sjĂ€lva hantera undantag som uppstĂ„r pĂ„ grund av problematiska payloads och backend-avbrott, eftersom de inte kan lita pĂ„ att broker levererar meddelanden pĂ„ nytt.
  • Skicka meddelanden till flera Ă€mnen som en enda atomĂ€r operation. Som vi snart kommer att se, kan kontrollen över olika Ă€mnen och partitioner befinna sig pĂ„ olika maskiner i Kafka-klustret, som inte koordinerar transaktioner vid avsĂ€ndning. Vid tidpunkten för denna artikels skrivande har det gjorts viss forskning för att möjliggöra detta med hjĂ€lp av KIP-98.
  • Koppla lĂ€sning av ett meddelande frĂ„n ett Ă€mne med att skicka ett annat meddelande till ett annat Ă€mne. Återigen, Kafka-arkitekturen Ă€r beroende av mĂ„nga oberoende maskiner som fungerar som en enda buss och gör inga försök att dölja detta. Till exempel finns det inga API-komponenter som tillĂ„ter att koppla ihop Konsument och Producent i transaktionen. I JMS sĂ€kerstĂ€lls detta av objektet Session, frĂ„n vilket de skapas MessageProducers och MessageConsumers.

Om vi inte kan lita pÄ transaktioner, hur kan vi sÀkerstÀlla en semantik som Àr nÀrmare den som traditionella meddelandesystem erbjuder?

Om det finns en möjlighet att konsumentens offset kan öka före meddelandet har behandlats, till exempel vid ett konsumentfel, sÄ finns det inget sÀtt för konsumenten att veta om hans konsumentgrupp har missat meddelanden nÀr den tilldelas en partition. En av strategierna Àr dÀrför att spola tillbaka offset till en tidigare position. Kafka-konsumentens API tillhandahÄller följande metoder för detta:

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

Metod seek () kan anvÀndas med metoden
offsetsForTimes (Map timestampsToSearch) för att spola tillbaka till ett tillstÄnd vid en specifik tidpunkt i det förflutna.

Implicit betyder anvÀndningen av denna metod att det ganska troligt Àr att vissa meddelanden som har behandlats tidigare kommer att lÀsas och behandlas igen. För att undvika detta kan vi anvÀnda idempotent lÀsning, som beskrivs i Kapitlet 4, för att spÄra tidigare visade meddelanden och utesluta dubbletter.

Som ett alternativ kan koden för din konsument vara enkel om förlust eller dubblett av meddelanden Ă€r acceptabel. NĂ€r vi övervĂ€ger anvĂ€ndningsscenarier dĂ€r Kafka vanligtvis anvĂ€nds, som logg-, metrik- och klickspĂ„rning, förstĂ„r vi att förlust av enskilda meddelanden sĂ€llan har en betydande pĂ„verkan pĂ„ omgivande applikationer. I sĂ„dana fall Ă€r standardvĂ€rden helt acceptabla. Å andra sidan, om din applikation behöver överföra betalningar mĂ„ste du noga se till varje enskilt meddelande. Allt beror pĂ„ sammanhanget.

Personliga observationer visar att med ökande meddelandeintensitet minskar vÀrdet av varje enskilt meddelande. Stora mÀngder meddelanden blir vanligtvis vÀrdefulla om de betraktas i aggregerad form.

Hög tillgÀnglighet (High Availability)

Kafkas tillvÀgagÄngssÀtt för hög tillgÀnglighet skiljer sig vÀsentligt frÄn ActiveMQ:s. Kafka Àr designad för att fungera pÄ horisontellt skalbara kluster dÀr alla broker-exemplar tar emot och distribuerar meddelanden samtidigt.

Ett Kafka-kluster bestĂ„r av flera broker-exemplar som körs pĂ„ olika servrar. Kafka Ă€r utformad för att fungera pĂ„ vanlig autonom hĂ„rdvara dĂ€r varje nod har sin egen dedikerade lagring. AnvĂ€ndning av nĂ€tverkslagring (SAN) rekommenderas inte, eftersom flera berĂ€kningsnoder kan konkurrera om lagringens tidsintervall och skapa konflikter.Ölagra intervall och skapa konflikter.

Kafka Àr en alltid aktiv system. MÄnga stora anvÀndare av Kafka stÀnger aldrig ner sina kluster och mjukvaran sÀkerstÀller alltid uppdatering genom sekventiell omstart. Detta uppnÄs genom att garantera bakÄtkompatibilitet för meddelanden och interaktioner mellan broker-exemplaren.

Brokers Àr anslutna till ett kluster av servrar ZooKeeper, som fungerar som ett register för konfigurationsinformation och anvÀnds för att koordinera rollerna för varje broker. ZooKeeper Àr sjÀlv en distribuerad system som sÀkerstÀller hög tillgÀnglighet genom att replikera information genom att upprÀtta kvorum..

I grundfall skapas en topic i Kafka-klustret med följande egenskaper:

  • Antal partitioner. Som diskuterats tidigare beror det exakta vĂ€rdet som anvĂ€nds hĂ€r pĂ„ önskad nivĂ„ av parallell lĂ€sning.
  • Replikationsfaktorn definierar hur mĂ„nga broker-exemplar i klustret som ska innehĂ„lla loggar för denna partition.

Genom att anvÀnda ZooKeeper för koordinering försöker Kafka rÀttvist fördela nya partitioner mellan broker-exemplaren i klustret. Detta görs av ett exemplar som fungerar som kontroller.

Vid körning för varje partition av en topic Kontroller tilldelar en broker roller ledare (leader, master, huvud) och följare. (följare, slavar, underordnade). En broker som fungerar som ledare för denna partition ansvarar för att ta emot alla meddelanden som skickas till honom av producenter och sprida meddelanden till konsumenterna. NÀr meddelanden skickas till en partitionsÀmne replikeras de pÄ alla broker-noder som fungerar som följare för denna partition. Varje nod som har loggar för partitionen kallas replik. En broker kan fungera som ledare för vissa partitioner och som följare för andra.

Följaren som innehÄller alla meddelanden som lagras hos ledaren kallas synkroniserad replik (in-sync replica). Om en broker som fungerar som ledare för en partition stÀngs av, kan vilken broker som helst som Àr i uppdaterat eller synkroniserat tillstÄnd för denna partition ta över rollen som ledare. Detta Àr en otroligt robust design.

En del av producentens konfiguration Àr parametern acks, som bestÀmmer hur mÄnga repliker som mÄste bekrÀfta (acknowledge) mottagandet av meddelandet innan applikationsströmmen fortsÀtter med att skicka: 0, 1 eller alla. Om vÀrdet allanges, kommer ledaren att skicka en bekrÀftelse (confirmation) tillbaka till producenten sÄ snart den fÄr bekrÀftelser (acknowledgements) för skrivningen frÄn fler repliker (inklusive sig sjÀlv), som anges av instÀllningen för Àmnet min.insync.replicas (standard Àr 1). Om meddelandet inte kan replikeras framgÄngsrikt kommer producenten att utlösa ett undantag för applikationen (NotEnoughReplicas eller NotEnoughReplicasAfterAppend).

I en typisk konfiguration skapas ett Àmne med en replikeringsfaktor pÄ 3 (1 ledare, 2 följare för varje partition) och parametern min.insync.replicas stÀller in vÀrdet 2. I detta fall kommer klustret att tillÄta att en av brokerna som hanterar Àmnespartitionen kan stÀngas av utan att pÄverka klientapplikationerna.

Det hÀr för oss tillbaka till den bekanta kompromissen mellan prestanda och tillförlitlighet. Replikering sker genom ytterligare vÀntetid för bekrÀftelser (acknowledgments) frÄn efterföljare. Men eftersom det görs parallellt, har replikering, Ätminstone över tre noder, samma prestanda som med tvÄ (ignorerar ökat nÀtverksbandbredd).

Genom att anvÀnda detta replikering schema undviker Kafka smidigt behovet av att sÀkerstÀlla fysisk skrivning av varje meddelande till disk via operationen sync (). Varje meddelande som skickas av producenten kommer att skrivas till partitionens logg, men som diskuterat i Kapitel 2, utförs skrivningen till fil initialt i operativsystemets buffer. Om detta meddelande replikerats till en annan instans av Kafka och finns i dess minne, betyder förlust av ledaren inte att sjÀlva meddelandet har gÄtt förlorat - det kan tas över av en synkroniserad replik.
AvstÄndet frÄn att behöva utföra operationen sync () betyder att Kafka kan ta emot meddelanden i den takt den kan skriva dem i minnet. Och tvÀrtom, ju lÀngre man kan undvika att tömma (flushing) minnet till disk, desto bÀttre. Av denna anledning Àr det inte ovanligt att Kafka-brokers fÄr 64 GB minne eller mer. Denna minnesanvÀndning innebÀr att en instans av Kafka kan arbeta i hastigheter som Àr tusentals gÄnger snabbare Àn en traditionell meddelandebroker.

Kafka kan ocksÄ konfigureras för att tillÀmpa operationen sync () pÄ meddelandepaket. Eftersom allt i Kafka Àr inriktat pÄ att arbeta med paket fungerar detta faktiskt ganska bra för mÄnga anvÀndningsfall och Àr ett nyttigt verktyg för anvÀndare som krÀver mycket starka garantier. Största delen av Kafkas rena prestanda relaterar till meddelanden som skickas till brokern i form av paket och att dessa meddelanden lÀses frÄn brokern i sekventiella block med hjÀlp av zero-copy operationer (med operationer dÀr uppgiften att kopiera data frÄn ett minne till ett annat inte utförs). Detta Àr en stor vinst nÀr det gÀller prestanda och resurser och Àr möjligt tack vare anvÀndningen av den underliggande loggdatastrukturen, som definierar partitionsschemat.

I en Kafka-kluster Àr det möjligt att uppnÄ mycket högre prestanda Àn med en enda Kafka-broker, eftersom partitionerna i en topic kan skalas horisontellt över flera separata maskiner.

Sammanfattning

I detta kapitel har vi undersökt hur Kafka-arkitekturen omformulerar relationerna mellan klienter och brokers för att sÀkerstÀlla en oerhört robust meddelandekanal, med en bandbredd som Àr mÄnga gÄnger högre Àn en vanlig meddelandebroker. Vi har diskuterat de funktioner som den anvÀnder för att uppnÄ detta mÄl, och kortfattat betraktat arkitekturen för de applikationer som tillhandahÄller dessa funktioner. I nÀsta kapitel kommer vi att diskutera vanliga problem som meddelandebaserade applikationer mÄste hantera, och vi kommer att diskutera strategier för att lösa dessa. Vi avslutar kapitlet med att skissa pÄ hur man resonerar kring meddelandeteknologier i allmÀnhet, sÄ att du kan bedöma deras lÀmplighet för dina anvÀndningsscenarier.

Tidigare översatt del: FörstÄelse av meddelandebrokers. Studera mekaniken för meddelandeutbyte med ActiveMQ och Kafka. Kapitel 1

ÖversĂ€ttning utförd av: tele.gg/middle_java

FortsĂ€ttning följer


Endast registrerade anvÀndare kan delta i omröstningen. Logga in, tack.

AnvÀnds Kafka i din organisation?

  • Ja

  • Ingen

  • AnvĂ€ndes tidigare, gör det inte nu

  • Planerar att anvĂ€nda

38 anvÀndare röstade. 8 anvÀndare avstod.

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster