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:
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 . 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 (). 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.

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 ().

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 (). 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.

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 ().

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 ().

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 ().
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.

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:
- AnvÀndarkontot Àr konfigurerat.
- Pengar sÀtts in pÄ kontot.
- 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:
- HÀmta ett meddelande för lÀsning.
- Bearbeta meddelandet.
- 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 , 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 Produkter, 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 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:
ĂversĂ€ttning gjord:
FortsÀttning ...
Endast registrerade anvÀndare kan delta i undersökningen. , 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
