Förstå meddelandeförmedlare. Lär dig mekaniken i meddelandehantering med ActiveMQ och Kafka. Kapitel 3. Kafka

Fortsättning på översättningen av en liten bok:
Förstå Message Brokers
författare: Jakub Korab, utgivare: O'Reilly Media, Inc., publiceringsdatum: juni 2017, ISBN: 9781492049296.

Föregående översatt del: Förstå meddelandeförmedlare. Lär dig mekaniken i meddelandehantering med ActiveMQ och Kafka. Kapitel 1 Inledning

KAPITEL 3

kafka

Kafka utvecklades av LinkedIn för att komma runt några av begränsningarna hos traditionella meddelandeförmedlare och undvika att behöva konfigurera flera meddelandeförmedlare för olika punkt-till-punkt-interaktioner, vilket beskrivs i den här boken under "Skala upp och ut" på sidan 28 Användningsfall LinkedIn har till stor del förlitat sig på envägsintag av mycket stora mängder data, såsom sidklick och åtkomstloggar, samtidigt som den har tillåtit att denna data kan användas av flera system utan att påverka produktiviteten hos producenter eller andra konsumenter. Anledningen till att Kafka existerar är faktiskt för att få den typ av meddelandearkitektur som Universal Data Pipeline beskriver.

Givet detta slutmål uppstod naturligtvis andra krav. Kafka borde:

  • Var extremt snabb
  • Ge mer bandbredd när du arbetar med meddelanden
  • Stöd för Publisher-Subscriber och Point-to-Point-modeller
  • Sakta inte ner med att lägga till konsumenter. Till exempel försämras prestandan för både kön och ämnet i ActiveMQ när antalet konsumenter på destinationen växer.
  • Var horisontellt skalbar; om en mäklare som kvarstår meddelanden bara kan göra det vid maximal diskhastighet, är det vettigt att gå längre än en enskild mäklarinstans för att öka prestandan
  • Begränsa åtkomsten till att lagra och återhämta meddelanden

För att uppnå allt detta antog Kafka en arkitektur som omdefinierade roller och ansvar för kunder och meddelandeförmedlare. JMS-modellen är väldigt mäklarorienterad, där mäklaren ansvarar för att distribuera meddelanden och kunderna bara behöver oroa sig för att skicka och ta emot meddelanden. Kafka, å andra sidan, är kundcentrerad, där kunden tar till sig många av funktionerna hos en traditionell mäklare, såsom rättvis distribution av relevanta meddelanden till konsumenter, i utbyte mot en extremt snabb och skalbar mäklare. För personer som har arbetat med traditionella meddelandesystem kräver arbetet med Kafka en grundläggande sinnesförändring.
Denna tekniska riktning har lett till skapandet av en meddelandeinfrastruktur som kan öka genomströmningen med många storleksordningar jämfört med en konventionell mäklare. Som vi kommer att se kommer detta tillvägagångssätt med avvägningar, vilket innebär att Kafka inte är lämplig för vissa typer av arbetsbelastningar och installerad programvara.

Unified Destination Model

För att uppfylla de krav som beskrivs ovan har Kafka kombinerat publicera-prenumerera och punkt-till-punkt meddelanden under en sorts destination − ämne. Detta är förvirrande för personer som har arbetat med meddelandesystem, där ordet "ämne" hänvisar till en sändningsmekanism från vilken (från ämnet) läsning är ohållbar. Kafka-ämnen bör betraktas som en hybriddestinationstyp, enligt definitionen i inledningen till denna bok.

För resten av detta kapitel, såvida vi inte uttryckligen anger annat, kommer termen "ämne" att hänvisa till ett Kafka-ämne.

För att till fullo förstå hur ämnen beter sig och vilka garantier de ger måste vi först titta på hur de implementeras i Kafka.
Varje ämne i Kafka har sin egen logg.
Producenter som skickar meddelanden till Kafka skriver till den här loggen, och konsumenter läser från loggen med hjälp av pekare som ständigt går framåt. Periodvis tar Kafka bort de äldsta delarna av loggen, oavsett om meddelandena i de delarna har lästs eller inte. En central del av Kafkas design är att mäklaren inte bryr sig om meddelanden läses eller inte – det är kundens ansvar.

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

Denna modell är helt olik ActiveMQ, där meddelanden från alla köer lagras i samma logg, och mäklaren markerar meddelandena som raderade efter att de har lästs.
Låt oss nu gräva lite djupare och titta på ämnesloggen mer i detalj.
Kafka-loggen består av flera partitioner (Figur 3-1). Kafka garanterar strikt ordning i varje partition. Detta innebär att meddelanden som skrivs till partitionen i en viss ordning kommer att läsas i samma ordning. Varje partition implementeras som en rullande loggfil som innehåller delmängd (underuppsättning) av alla meddelanden som skickas till ämnet av dess producenter. Det skapade ämnet innehåller som standard en partition. Idén med partitioner är den centrala idén med Kafka för horisontell skalning.

Förstå meddelandeförmedlare. Lär dig mekaniken i meddelandehantering med ActiveMQ och Kafka. Kapitel 3. Kafka
Bild 3-1. Kafka partitioner

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

Läser meddelanden

Klienten som vill läsa meddelandena hanterar en anropad pekare konsumentgrupp, vilket pekar på offset meddelanden i partitionen. En offset är en inkrementell position som börjar vid 0 i början av en partition. Denna konsumentgrupp, refererad till i API:t via det användardefinierade group_id, motsvarar en logisk konsument eller system.

De flesta meddelandesystem läser data från destinationen med hjälp av flera instanser och trådar för att behandla meddelanden parallellt. Det kommer alltså vanligtvis att finnas många konsumentinstanser som delar samma konsumentgrupp.

Problemet med läsning kan representeras på följande sätt:

  • Ämnet har flera partitioner
  • Flera grupper av konsumenter kan använda ett ämne samtidigt
  • En grupp konsumenter kan ha flera separata instanser

Detta är ett icke-trivialt många-till-många-problem. För att förstå hur Kafka hanterar relationer mellan konsumentgrupper, konsumentinstanser och partitioner, låt oss titta på en serie av allt mer komplexa lässcenarier.

Konsumenter och konsumentgrupper

Låt oss ta som utgångspunkt ett ämne med en partition (Figur 3-2).

Förstå meddelandeförmedlare. Lär dig mekaniken i meddelandehantering med ActiveMQ och Kafka. Kapitel 3. Kafka
Bild 3-2. Konsumenten läser från partitionen

När en konsumentinstans ansluter med sitt eget group_id till detta ämne tilldelas den en läspartition och en offset i den partitionen. Positionen för denna offset konfigureras i klienten som en pekare till den senaste positionen (nyaste meddelandet) eller tidigaste positionen (äldsta meddelandet). Konsumenten begär (omröstningar) meddelanden från ämnet, vilket gör att de läses sekventiellt från loggen.
Offsetpositionen återförs regelbundet till Kafka och lagras som meddelanden i ett internt ämne _konsumentkompensationer. Lästa meddelanden raderas fortfarande inte, till skillnad från en vanlig mäklare, och klienten kan spola tillbaka offseten för att bearbeta redan visade meddelanden.

När en andra logisk konsument ansluter med ett annat group_id, hanterar den en andra pekare som är oberoende av den första (Figur 3-3). Således fungerar ett Kafka-ämne som en kö där det finns en konsument och som ett vanligt publicera-prenumerera (pub-sub) ämne som flera konsumenter prenumererar på, med den extra fördelen att alla meddelanden lagras och kan behandlas flera gånger.

Förstå meddelandeförmedlare. Lär dig mekaniken i meddelandehantering med ActiveMQ och Kafka. Kapitel 3. Kafka
Bild 3-3. Två konsumenter i olika konsumentgrupper läser från samma partition

Konsumenter i en konsumentgrupp

När en konsumentinstans läser data från en partition har den full kontroll över pekaren och bearbetar meddelanden enligt beskrivningen i föregående avsnitt.
Om flera instanser av konsumenter var kopplade med samma group_id till ett ämne med en partition, så kommer den instans som anslutna senast att ges kontroll över pekaren och från det ögonblicket kommer den att ta emot alla meddelanden (Figur 3-4).

Förstå meddelandeförmedlare. Lär dig mekaniken i meddelandehantering med ActiveMQ och Kafka. Kapitel 3. Kafka
Bild 3-4. Två konsumenter i samma konsumentgrupp läser från samma partition

Detta behandlingssätt, där antalet konsumentinstanser överstiger antalet partitioner, kan ses som en sorts exklusiv konsument. Detta kan vara användbart om du behöver "aktiv-passiv" (eller "varm-varm") klustring av dina konsumentinstanser, även om att köra flera konsumenter parallellt ("aktiv-aktiv" eller "varm-het") är mycket mer typiskt än konsumenter i standby.

Detta meddelandedistributionsbeteende som beskrivs ovan kan vara överraskande jämfört med hur en normal JMS-kö beter sig. I denna modell kommer meddelanden som skickas till kön att vara jämnt fördelade mellan de två konsumenterna.

Oftast, när vi skapar flera instanser av konsumenter, gör vi detta antingen för att behandla meddelanden parallellt, eller för att öka läshastigheten eller för att öka stabiliteten i läsprocessen. Eftersom endast en konsumentinstans kan läsa data från en partition åt gången, hur uppnås detta i Kafka?

Ett sätt att göra detta är att använda en enda konsumentinstans för att läsa alla meddelanden och skicka dem till trådpoolen. Även om detta tillvägagångssätt ökar bearbetningsgenomströmningen, ökar det komplexiteten i konsumentlogiken och gör ingenting för att öka robustheten hos lässystemet. Om en kopia av konsumenten går sönder på grund av ett strömavbrott eller liknande händelse, upphör subtraktionen.

Det kanoniska sättet att lösa detta problem i Kafka är att använda bОfler partitioner.

Partitionering

Partitioner är huvudmekanismen för att parallellisera läsning och skala ett ämne utöver bandbredden för en enskild mäklarinstans. För att bättre förstå detta, låt oss överväga en situation där det finns ett ämne med två partitioner och en konsument prenumererar på detta ämne (Figur 3-5).

Förstå meddelandeförmedlare. Lär dig mekaniken i meddelandehantering med ActiveMQ och Kafka. Kapitel 3. Kafka
Bild 3-5. En konsument läser från flera partitioner

I detta scenario får konsumenten kontroll över pekarna som motsvarar dess group_id i båda partitionerna och börjar läsa meddelanden från båda partitionerna.
När ytterligare en konsument för samma group_id läggs till i detta ämne, omfördelar Kafka en av partitionerna från den första till den andra konsumenten. Efter det kommer varje instans av konsumenten att läsa från en partition av ämnet (Figur 3-6).

För att säkerställa att meddelanden behandlas parallellt i 20 trådar behöver du minst 20 partitioner. Om det finns färre partitioner kommer du att sitta kvar med konsumenter som inte har något att jobba på, som beskrivits tidigare i diskussionen om exklusiva konsumenter.

Förstå meddelandeförmedlare. Lär dig mekaniken i meddelandehantering med ActiveMQ och Kafka. Kapitel 3. Kafka
Bild 3-6. Två konsumenter i samma konsumentgrupp läser från olika partitioner

Detta schema reducerar avsevärt komplexiteten hos Kafka-mäklaren jämfört med meddelandedistributionen som krävs för att upprätthålla JMS-kön. Här behöver du inte oroa dig för följande punkter:

  • Vilken konsument ska ta emot nästa meddelande, baserat på round-robin-allokering, nuvarande kapacitet för förhämtningsbuffertar eller tidigare meddelanden (som för JMS-meddelandegrupper).
  • Vilka meddelanden som skickas till vilka konsumenter och om de ska återlevereras vid fel.

Allt Kafka-mäklaren behöver göra är att skicka meddelanden sekventiellt till konsumenten när denne begär dem.

Kraven för att parallellisera korrekturläsningen och återsända misslyckade meddelanden försvinner dock inte – ansvaret för dem går helt enkelt från mäklaren till kunden. Det betyder att de måste beaktas i din kod.

Skicka meddelanden

Det är producenten av meddelandets ansvar att bestämma vilken partition ett meddelande ska skickas till. För att förstå mekanismen genom vilken detta görs måste vi först överväga exakt vad vi faktiskt skickar.

Medan vi i JMS använder en meddelandestruktur med metadata (rubriker och egenskaper) och en kropp som innehåller nyttolasten (nyttolasten), är meddelandet i Kafka par "nyckel-värde". Meddelandets nyttolast skickas som ett värde. Nyckeln, å andra sidan, används främst för partitionering och måste innehålla affärslogikspecifik nyckelför att lägga relaterade meddelanden i samma partition.

I kapitel 2 diskuterade vi scenariot för vadslagning online där relaterade händelser måste behandlas i ordning av en enda konsument:

  1. Användarkontot är konfigurerat.
  2. Pengar sätts in på kontot.
  3. En satsning görs som tar ut pengar från kontot.

Om varje händelse är ett meddelande till ett ämne, så skulle den naturliga nyckeln vara konto-ID.
När ett meddelande skickas med Kafka Producer API skickas det till en partitionsfunktion som, givet meddelandet och Kafka-klustrets nuvarande tillstånd, returnerar ID:t för den partition som meddelandet ska skickas till. Den här funktionen implementeras i Java via Partitioner-gränssnittet.

Det här gränssnittet ser ut så här:

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

Partitioner-implementeringen använder den förinställda hashalgoritmen för allmänt ändamål över nyckeln för att bestämma partitionen, eller round-robin om ingen nyckel är angiven. Detta standardvärde fungerar bra i de flesta fall. Men i framtiden kommer du att vilja skriva din egen.

Att skriva din egen partitioneringsstrategi

Låt oss titta på ett exempel där du vill skicka metadata tillsammans med meddelandets nyttolast. Nyttolasten i vårt exempel är en instruktion för att göra en insättning till spelkontot. En instruktion är något som vi vill garanteras inte ändras vid överföring och vill vara säkra på att endast ett pålitligt uppströmssystem kan initiera den instruktionen. I detta fall kommer de sändande och mottagande systemen överens om användningen av en signatur för att autentisera meddelandet.
I normala JMS definierar vi helt enkelt en "meddelandesignatur"-egenskap och lägger till den i meddelandet. Kafka förser oss dock inte med en mekanism för att skicka metadata, bara en nyckel och ett värde.

Eftersom värdet är en banköverföringsnyttolast vars integritet vi vill bevara, har vi inget annat val än att definiera datastrukturen som ska användas i nyckeln. Om vi ​​antar att vi behöver ett konto-ID för partitionering, eftersom alla meddelanden relaterade till ett konto måste behandlas i ordning, kommer vi att komma fram till följande JSON-struktur:

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

Eftersom värdet på signaturen kommer att variera beroende på nyttolasten, kommer standardhashastrategin för Partitioner-gränssnittet inte att gruppera relaterade meddelanden på ett tillförlitligt sätt. Därför kommer vi att behöva skriva vår egen strategi som kommer att analysera denna nyckel och partitionera accountId-värdet.

Kafka inkluderar kontrollsummor för att upptäcka korruption av meddelanden i butiken och har en komplett uppsättning säkerhetsfunktioner. Trots det dyker det ibland upp branschspecifika krav, som det ovan.

Användarens partitioneringsstrategi måste säkerställa att alla relaterade meddelanden hamnar i samma partition. Även om detta verkar enkelt, kan kravet kompliceras av vikten av att beställa relaterade meddelanden och hur fixat antalet partitioner i ett ämne är.

Antalet partitioner i ett ämne kan ändras över tiden, eftersom de kan läggas till om trafiken går över de ursprungliga förväntningarna. Således kan meddelandenycklar associeras med den partition de ursprungligen skickades till, vilket innebär att en del av tillståndet ska delas mellan producentinstanser.

En annan faktor att tänka på är den jämna fördelningen av meddelanden över partitionerna. Vanligtvis är nycklar inte jämnt fördelade över meddelanden, och hashfunktioner garanterar inte en rättvis fördelning av meddelanden för en liten uppsättning nycklar.
Det är viktigt att notera att hur du än väljer att dela upp meddelanden, kan separatorn behöva återanvändas.

Tänk på kravet att replikera data mellan Kafka-kluster på olika geografiska platser. För detta ändamål kommer Kafka med ett kommandoradsverktyg som heter MirrorMaker, som 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 upprätthålla relativ ordning mellan meddelanden vid replikering mellan kluster, eftersom antalet partitioner för det ämnet kanske inte är detsamma i två kluster.

Anpassade partitioneringsstrategier är relativt sällsynta, eftersom standardhashning eller round robin fungerar bra i de flesta scenarier. Men om du kräver starka beställningsgarantier eller behöver extrahera metadata från nyttolaster, så är partitionering något du bör titta närmare på.

Skalbarheten och prestandafördelarna med Kafka kommer från att flytta över en del av den traditionella mäklarens ansvar till kunden. I det här fallet fattas beslut om att distribuera potentiellt relaterade meddelanden mellan flera konsumenter som arbetar parallellt.

JMS-mäklare måste också hantera sådana krav. Intressant nog kräver mekanismen för att skicka relaterade meddelanden till samma konsument, implementerad genom JMS Message Groups (en variant på strategin för sticky load balancing (SLB)), också att avsändaren markerar meddelanden som relaterade. När det gäller JMS är mäklaren ansvarig för att skicka denna grupp av relaterade meddelanden till en konsument av många, och överföra äganderätten till gruppen om konsumenten faller av.

Producentavtal

Partitionering är inte det enda man bör tänka på när man skickar meddelanden. Låt oss ta en titt på send()-metoderna för klassen Producer i Java API:

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

Det bör omedelbart noteras att båda metoderna returnerar Future, vilket indikerar att sändningsoperationen inte utförs omedelbart. Resultatet är att ett meddelande (ProducerRecord) skrivs till sändbufferten för varje aktiv partition och skickas till mäklaren som en bakgrundstråd i Kafkas klientbibliotek. Även om detta gör saker otroligt snabbt, betyder det att en oerfaren applikation kan förlora meddelanden om processen stoppas.

Som alltid finns det ett sätt att göra sändningsoperationen mer tillförlitlig på bekostnad av prestanda. Storleken på denna buffert kan ställas in på 0, och den sändande applikationstråden kommer att tvingas vänta tills meddelandeöverföringen till mäklaren är klar, enligt följande:

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

Mer om att läsa meddelanden

Att läsa meddelanden har ytterligare komplexitet som måste spekuleras om. Till skillnad från JMS API, som kan köra en meddelandeavlyssnare som svar på ett meddelande konsumenten Kafka bara omröstningar. Låt oss titta närmare på metoden opinionsundersökning()används för detta ändamål:

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

Metodens returvärde är en containerstruktur som innehåller flera objekt konsumentregister från potentiellt flera partitioner. konsumentregister är i sig ett hållarobjekt för ett nyckel-värdepar med tillhörande metadata, såsom partitionen från vilken det härrör.

Som diskuterats i kapitel 2 måste vi ha i åtanke vad som händer med meddelanden efter att de har behandlats framgångsrikt eller misslyckat, till exempel om klienten inte kan behandla meddelandet eller om det avbryts. I JMS hanterades detta genom ett kvittensläge. Mäklaren kommer antingen att radera det framgångsrikt bearbetade meddelandet eller leverera det råa eller falska meddelandet igen (förutsatt att transaktioner användes).
Kafka fungerar väldigt annorlunda. Meddelanden raderas inte i mäklaren efter korrekturläsning, och det som händer vid fel är korrekturkodens ansvar.

Konsumentgruppen är som sagt associerad med offset i loggen. Loggpositionen associerad med denna förskjutning motsvarar nästa meddelande som ska utfärdas som svar på opinionsundersökning(). Den tidpunkt då denna offset ökar är avgörande för läsningen.

För att återgå till läsmodellen som diskuterats tidigare, består meddelandebehandling av tre steg:

  1. Hämta ett meddelande för läsning.
  2. Bearbeta meddelandet.
  3. Bekräfta meddelandet.

Kafka-konsumenten kommer med ett konfigurationsalternativ enable.auto.commit. Detta är en ofta använd standardinställning, vilket är vanligt med inställningar som innehåller ordet "auto".

Före Kafka 0.10 skulle en klient som använder detta alternativ skicka offset för det senaste meddelandet som lästes vid nästa samtal opinionsundersökning() efter bearbetning. Detta innebar att alla meddelanden som redan hade hämtats kunde bearbetas om klienten redan hade bearbetat dem men oväntat förstördes innan anropet opinionsundersökning(). Eftersom mäklaren inte har någon uppgift om hur många gånger ett meddelande har lästs, kommer nästa konsument som hämtar meddelandet inte att veta att något dåligt har hänt. Detta beteende var pseudotransaktionellt. Offset begicks endast om meddelandet behandlades framgångsrikt, men om klienten avbröts skulle mäklaren skicka samma meddelande igen till en annan klient. Detta beteende överensstämde med meddelandeleveransgarantin "åtminstone en gång".

I Kafka 0.10 har klientkoden ändrats så att commit utlöses med jämna mellanrum av klientbiblioteket, som konfigurerat auto.commit.interval.ms. Detta beteende är någonstans mellan lägena JMS AUTO_ACKNOWLEDGE och DUPS_OK_ACKNOWLEDGE. När du använder autocommit kan meddelanden committeras oavsett om de faktiskt bearbetades - detta kan hända i fallet med en långsam konsument. Om en konsument avbröt, kommer meddelanden att hämtas av nästa konsument, med början vid den engagerade positionen, vilket kan resultera i ett missat meddelande. I det här fallet förlorade Kafka inte meddelandena, läskoden behandlade dem bara inte.

Det här läget har samma löfte som i version 0.9: meddelanden kan bearbetas, men om det misslyckas kan förskjutningen inte begås, vilket kan leda till att leveransen fördubblas. Ju fler meddelanden du hämtar när du kör opinionsundersökning(), ju mer detta problem.

Som diskuterats i "Läsa meddelanden från en kö" på sidan 21, finns det inget sådant som en engångsleverans av ett meddelande i ett meddelandesystem när fellägen beaktas.

I Kafka finns det två sätt att begå (begå) en offset (offset): automatiskt och manuellt. I båda fallen kan meddelanden behandlas flera gånger om meddelandet bearbetades men misslyckades före commit. Du kan också välja att inte bearbeta meddelandet alls om commit skedde i bakgrunden och din kod slutfördes innan den kunde bearbetas (kanske i Kafka 0.9 och tidigare).

Du kan styra den manuella offset-commit-processen i Kafka konsument-API genom att ställa in parametern enable.auto.commit till falskt och uttryckligen anropa någon av följande metoder:

void commitSync();
void commitAsync();

Om du vill behandla meddelandet "minst en gång" måste du utföra offset manuellt med commitSync()genom att utföra detta kommando omedelbart efter bearbetning av meddelandena.

Dessa metoder tillåter inte att meddelanden kvitteras innan de behandlas, men de gör ingenting för att eliminera potentiella behandlingsförseningar samtidigt som de ser ut att vara transaktionella. Det finns inga transaktioner i Kafka. Kunden har inte möjlighet att göra följande:

  • Återställ automatiskt ett falskt meddelande. Konsumenterna måste själva hantera undantag som härrör från problematiska nyttolaster och backend-avbrott, eftersom de inte kan lita på att mäklaren återlevererar meddelanden.
  • Skicka meddelanden till flera ämnen i en atomoperation. Som vi kommer att se inom kort kan kontroll över olika ämnen och partitioner finnas på olika maskiner i Kafka-klustret som inte koordinerar transaktioner när de skickas. När detta skrivs har en del arbete gjorts för att göra detta möjligt med KIP-98.
  • Associera att läsa ett meddelande från ett ämne med att skicka ett annat meddelande till ett annat ämne. Återigen beror Kafkas arkitektur på många oberoende maskiner som körs som en buss och inget försök görs att dölja detta. Till exempel finns det inga API-komponenter som gör att du kan länka konsument и Producent i en transaktion. I JMS tillhandahålls detta av objektet Session Testsom skapas av MessageProducers и MessageConsumers.

Om vi ​​inte kan förlita oss på transaktioner, hur kan vi tillhandahålla semantik närmare dem som tillhandahålls av traditionella meddelandesystem?

Om det finns en möjlighet att konsumentens offset kan öka innan meddelandet har behandlats, till exempel vid en konsumentkrasch, har konsumenten ingen möjlighet att veta om dess konsumentgrupp missat meddelandet när den tilldelas en partition. Så en strategi är att spola tillbaka offset till föregående position. Kafka konsument-API tillhandahåller följande metoder för detta:

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

metod söka() kan användas med metod
offsetsForTimes(Map timestampsToSearch) att spola tillbaka till ett tillstånd vid någon specifik tidpunkt i det förflutna.

Implicit innebär att använda detta tillvägagångssätt att det är mycket troligt att vissa meddelanden som tidigare bearbetats kommer att läsas och bearbetas igen. För att undvika detta kan vi använda idempotent läsning, som beskrivs i kapitel 4, för att hålla reda på tidigare visade meddelanden och eliminera dubbletter.

Alternativt kan din konsumentkod hållas enkel, så länge meddelandeförlust eller duplicering är acceptabel. När vi tittar på användningsfall som Kafka ofta används för, såsom hantering av logghändelser, mätvärden, klickspårning, etc., inser vi att förlusten av enskilda meddelanden sannolikt inte kommer att ha en betydande inverkan på omgivande applikationer. I sådana fall är standardvärdena helt acceptabla. Å andra sidan, om din ansökan behöver skicka betalningar måste du noggrant ta hand om varje enskilt meddelande. Allt beror på sammanhanget.

Personliga observationer visar att när intensiteten på meddelanden ökar, minskar värdet av varje enskilt meddelande. Stora meddelanden tenderar att vara värdefulla när de visas i en aggregerad form.

Hög tillgänglighet

Kafkas inställning till hög tillgänglighet skiljer sig mycket från ActiveMQ:s synsätt. Kafka är designad kring utskalningskluster där alla mäklarinstanser tar emot och distribuerar meddelanden samtidigt.

Ett Kafka-kluster består av flera mäklarinstanser som körs på olika servrar. Kafka designades för att köras på vanlig fristående hårdvara, där varje nod har sin egen dedikerade lagring. Användningen av nätverksansluten lagring (SAN) rekommenderas inte eftersom flera beräkningsnoder kan konkurrera om tiden.Ыe lagringsintervall och skapa konflikter.

Kafka är alltid på systemet. Många stora Kafka-användare stänger aldrig av sina kluster och programvaran uppdateras alltid med en sekventiell omstart. Detta uppnås genom att garantera kompatibilitet med den tidigare versionen för meddelanden och interaktioner mellan mäklare.

Mäklare anslutna till ett serverkluster ZooKeeper, som fungerar som ett konfigurationsdataregister och används för att samordna rollerna för varje mäklare. ZooKeeper i sig är ett distribuerat system som ger hög tillgänglighet genom replikering av information genom att etablera kvorum.

I basfallet skapas ett ämne i ett Kafka-kluster med följande egenskaper:

  • Antalet partitioner. Som diskuterats tidigare beror det exakta värdet som används här på den önskade nivån av parallellläsning.
  • Replikeringsfaktorn (faktorn) avgör hur många mäklarinstanser i klustret som ska innehålla loggar för denna partition.

Genom att använda ZooKeepers för koordinering försöker Kafka att fördela nya partitioner rättvist bland mäklarna i klustret. Detta görs av en enda instans som fungerar som en Controller.

Vid körning för varje ämnespartition Kontroller tilldela roller till en mäklare ledare (ledare, mästare, programledare) och anhängare (anhängare, slavar, underordnade). Mäklaren, som fungerar som ledare för denna partition, är ansvarig för att ta emot alla meddelanden som skickas till den av producenterna och distribuera meddelandena till konsumenterna. När meddelanden skickas till en ämnespartition, replikeras de till alla mäklarnoder som fungerar som följare för den partitionen. Varje nod som innehåller loggar för en partition anropas kopia. En mäklare kan fungera som en ledare för vissa partitioner och som en efterföljare för andra.

En följare som innehåller alla meddelanden som innehas av ledaren anropas synkroniserad replika (en replik som är i ett synkroniserat tillstånd, insync replika). Om en mäklare som fungerar som ledare för en partition går ner, kan vilken mäklare som helst som är uppdaterad eller synkroniserad för den partitionen ta över ledarrollen. Det är en otroligt hållbar design.

En del av producentkonfigurationen är parametern acks, som bestämmer hur många repliker som måste bekräfta (bekräfta) mottagandet av ett meddelande innan programtråden fortsätter att skicka: 0, 1 eller alla. Om inställt på alla, sedan när ett meddelande tas emot kommer ledaren att skicka en bekräftelse tillbaka till producenten så snart den tar emot bekräftelser (bekräftelser) av posten från flera ledtrådar (inklusive sig själv) definierade av ämnesinställningen min.insync.repliker (standard 1). Om meddelandet inte kan replikeras framgångsrikt, kommer producenten att skicka ett applikationsundantag (NotEnoughReplicas eller NotEnoughReplicasAfterAppend).

En typisk konfiguration skapar ett ämne med en replikeringsfaktor på 3 (1 ledare, 2 följare per partition) och parametern min.insync.repliker är inställd på 2. I det här fallet kommer klustret att tillåta en av mäklarna som hanterar ämnespartitionen att gå ner utan att påverka klientapplikationer.

Detta för oss tillbaka till den redan välkända avvägningen mellan prestanda och tillförlitlighet. Replikering sker på bekostnad av ytterligare väntetid för bekräftelser (bekräftelser) från följare. Även om, eftersom det körs parallellt, har replikering till minst tre noder samma prestanda som två (om man ignorerar ökningen av nätverksbandbreddsanvändning).

Genom att använda detta replikeringsschema undviker Kafka på ett skickligt sätt behovet av att fysiskt skriva varje meddelande till disk med operationen synkronisera(). Varje meddelande som skickas av producenten kommer att skrivas till partitionsloggen, men som diskuterats i kapitel 2, skrivs till en fil initialt i operativsystemets buffert. Om detta meddelande replikeras till en annan Kafka-instans och finns i dess minne, betyder förlusten av ledaren inte att själva meddelandet gick förlorat - det kan tas över av en synkroniserad replik.
Vägrar att utföra operationen synkronisera() betyder att Kafka kan ta emot meddelanden lika snabbt som den kan skriva dem till minnet. Omvänt, ju längre du kan undvika att spola minne till disk, desto bättre. Av denna anledning är det inte ovanligt att Kafka-mäklare tilldelas 64 GB eller mer minne. Denna minnesanvändning innebär att en enskild Kafka-instans enkelt kan köras med hastigheter många tusen gånger snabbare än en traditionell meddelandeförmedlare.

Kafka kan också konfigureras för att tillämpa operationen synkronisera() till meddelandepaket. Eftersom allt i Kafka är paketorienterat fungerar det faktiskt ganska bra för många användningsfall och är ett användbart verktyg för användare som kräver mycket starka garantier. Mycket av Kafkas rena prestanda kommer från de meddelanden som skickas till mäklaren som paket och att dessa meddelanden läses från mäklaren i sekventiella block med hjälp av noll-kopia operationer (operationer under vilka uppgiften att kopiera data från ett minnesområde till ett annat inte utförs). Det senare är en stor prestanda- och resursvinst och är endast möjligt genom att använda en underliggande loggdatastruktur som definierar partitionsschemat.

Mycket bättre prestanda är möjlig i ett Kafka-kluster än med en enda Kafka-mäklare, eftersom ämnespartitioner kan skalas ut över många separata maskiner.

Resultat av

I det här kapitlet tittade vi på hur Kafka-arkitekturen omskapar relationen mellan kunder och mäklare för att tillhandahålla en otroligt robust meddelandepipeline, med genomströmning många gånger större än en konventionell meddelandemäklare. Vi har diskuterat den funktionalitet den använder för att uppnå detta och kort tittat på arkitekturen för de applikationer som tillhandahåller denna funktionalitet. I nästa kapitel kommer vi att titta på vanliga problem som meddelandebaserade applikationer måste lösa och diskutera strategier för att hantera dem. Vi avslutar kapitlet med att beskriva hur man pratar om meddelandeteknik i allmänhet så att du kan utvärdera deras lämplighet för dina användningsfall.

Föregående översatt del: Förstå meddelandemäklare. Lär dig mekaniken i meddelandehantering med ActiveMQ och Kafka. Kapitel 1

Översättning gjord: tele.gg/middle_java

Fortsättning ...

Endast registrerade användare kan delta i undersökningen. Logga in, Snälla du.

Används Kafka i din organisation?

  • Ja

  • Ingen

  • Tidigare använd, nu inte

  • Vi planerar att använda

38 användare röstade. 8 användare avstod från att rösta.

Källa: will.com

Lägg en kommentar