Berichtmakelaars begrijpen. De werking van berichten leren met ActiveMQ en Kafka. Hoofdstuk 3. Kafka

Vervolg van de vertaling van een klein boekje:
Berichtmakelaars begrijpen
auteur: Jakub Korab, uitgever: O'Reilly Media, Inc., publicatiedatum: juni 2017, ISBN: 9781492049296.

Vorig vertaald deel: Berichtmakelaars begrijpen. De werking van berichten leren met ActiveMQ en Kafka. Hoofdstuk 1 Introductie

HOOFDSTUK 3

Kafka

Kafka is door LinkedIn ontwikkeld om een ​​aantal beperkingen van traditionele berichtenmakelaars te omzeilen en om te voorkomen dat er meerdere berichtenmakelaars moeten worden opgezet voor verschillende punt-naar-punt-interacties, wat in dit boek wordt beschreven onder "Opschalen en uitschalen" op pagina 28 Gebruiksvoorbeelden LinkedIn heeft grotendeels vertrouwd op eenrichtingsopname van zeer grote hoeveelheden gegevens, zoals klikken op pagina's en toegangslogboeken, terwijl die gegevens nog steeds door meerdere systemen kunnen worden gebruikt zonder de productiviteit van producenten of andere consumenten te beïnvloeden. De reden dat Kafka bestaat, is in feite om het soort berichtarchitectuur te krijgen dat de Universal Data Pipeline beschrijft.

Gezien dit uiteindelijke doel kwamen er natuurlijk andere eisen. Kafka moet:

  • Wees extreem snel
  • Zorg voor meer bandbreedte bij het werken met berichten
  • Ondersteuning voor Publisher-Subscriber- en Point-to-Point-modellen
  • Vertraag niet met het toevoegen van consumenten. De prestatie van zowel de wachtrij als het onderwerp in ActiveMQ neemt bijvoorbeeld af naarmate het aantal consumenten op de bestemming toeneemt.
  • Horizontaal schaalbaar zijn; als een makelaar die berichten aanhoudt dit alleen kan doen met maximale schijfsnelheid, dan is het logisch om verder te gaan dan een enkele makelaar om de prestaties te verbeteren
  • Beperk de toegang tot het opslaan en ophalen van berichten

Om dit alles te bereiken, heeft Kafka een architectuur aangenomen die de rollen en verantwoordelijkheden van klanten en berichtenmakelaars opnieuw definieerde. Het JMS-model is erg makelaarsgericht, waarbij de makelaar verantwoordelijk is voor het verspreiden van berichten en klanten zich alleen zorgen hoeven te maken over het verzenden en ontvangen van berichten. Kafka daarentegen is klantgericht, waarbij de klant veel kenmerken van een traditionele makelaar overneemt, zoals eerlijke distributie van relevante berichten aan consumenten, in ruil voor een extreem snelle en schaalbare makelaar. Voor mensen die met traditionele berichtensystemen hebben gewerkt, vereist het werken met Kafka een fundamentele verandering van geest.
Deze technische richting heeft geleid tot de creatie van een berichteninfrastructuur die de doorvoer met vele ordes van grootte kan verhogen in vergelijking met een conventionele makelaar. Zoals we zullen zien, brengt deze aanpak nadelen met zich mee, wat betekent dat Kafka niet geschikt is voor bepaalde soorten workloads en geïnstalleerde software.

Uniform bestemmingsmodel

Om aan de hierboven beschreven vereisten te voldoen, heeft Kafka publish-subscribe en point-to-point messaging gecombineerd onder één soort bestemming − onderwerp. Dit is verwarrend voor mensen die met berichtensystemen hebben gewerkt, waarbij het woord "onderwerp" verwijst naar een uitzendmechanisme waarvan (uit het onderwerp) lezen niet duurzaam is. Kafka-onderwerpen moeten worden beschouwd als een hybride bestemmingstype, zoals gedefinieerd in de inleiding van dit boek.

Voor de rest van dit hoofdstuk verwijst de term 'onderwerp', tenzij we expliciet anders aangeven, naar een Kafka-onderwerp.

Om volledig te begrijpen hoe onderwerpen zich gedragen en welke garanties ze bieden, moeten we eerst kijken hoe ze in Kafka zijn geïmplementeerd.
Elk onderwerp in Kafka heeft zijn eigen logboek.
Producenten die berichten naar Kafka sturen, schrijven naar dit logboek en consumenten lezen uit het logboek met behulp van pointers die constant vooruit gaan. Kafka verwijdert periodiek de oudste delen van het logboek, ongeacht of de berichten in die delen zijn gelezen of niet. Een centraal onderdeel van het ontwerp van Kafka is dat het de makelaar niet uitmaakt of berichten worden gelezen of niet - dat is de verantwoordelijkheid van de klant.

De termen "log" en "pointer" komen niet voor in Kafka-documentatie. Deze bekende termen worden hier gebruikt om het begrip te bevorderen.

Dit model is totaal anders dan ActiveMQ, waarbij berichten uit alle wachtrijen in hetzelfde logboek worden opgeslagen en de broker de berichten als verwijderd markeert nadat ze zijn gelezen.
Laten we nu wat dieper graven en het onderwerp inloggen meer in detail bekijken.
Het Kafka-logboek bestaat uit verschillende partities (Figuur 3-1). Kafka garandeert een strikte ordening in elke partitie. Dit betekent dat berichten die in een bepaalde volgorde naar de partitie zijn geschreven, in dezelfde volgorde worden gelezen. Elke partitie is geïmplementeerd als een rollend logbestand dat bevat een subset (subset) van alle berichten die door de producenten naar het onderwerp zijn verzonden. Het gemaakte onderwerp bevat standaard één partitie. Het idee van partities is het centrale idee van Kafka voor horizontaal schalen.

Berichtmakelaars begrijpen. De werking van berichten leren met ActiveMQ en Kafka. Hoofdstuk 3. Kafka
Afbeelding 3-1. Kafka-partities

Wanneer een producent een bericht naar een Kafka-onderwerp verzendt, beslist het naar welke partitie het bericht moet worden verzonden. We zullen dit later in meer detail bekijken.

Berichten lezen

De client die de berichten wil lezen, beheert een benoemde aanwijzer genaamd groep consumenten, waarnaar wordt verwezen compenseren berichten in de partitie. Een offset is een incrementele positie die begint bij 0 aan het begin van een partitie. Deze consumentengroep, waarnaar wordt verwezen in de API via de door de gebruiker gedefinieerde group_id, komt overeen met één logische consument of systeem.

De meeste berichtensystemen lezen gegevens van de bestemming met behulp van meerdere instanties en threads om berichten parallel te verwerken. Er zullen dus gewoonlijk veel consumenteninstanties zijn die dezelfde consumentengroep delen.

Het leesprobleem kan als volgt worden weergegeven:

  • Onderwerp heeft meerdere partities
  • Meerdere groepen consumenten kunnen tegelijkertijd een onderwerp gebruiken
  • Een groep consumenten kan meerdere afzonderlijke instanties hebben

Dit is een niet-triviaal veel-op-veel probleem. Om te begrijpen hoe Kafka omgaat met relaties tussen consumentengroepen, consumenteninstanties en partities, gaan we kijken naar een reeks steeds complexere leesscenario's.

Consumenten en consumentengroepen

Laten we als uitgangspunt een onderwerp nemen met één partitie (Figuur 3-2).

Berichtmakelaars begrijpen. De werking van berichten leren met ActiveMQ en Kafka. Hoofdstuk 3. Kafka
Afbeelding 3-2. Consument leest van partitie

Wanneer een consumenteninstantie met zijn eigen group_id verbinding maakt met dit onderwerp, krijgt het een leespartitie toegewezen en een offset in die partitie. De positie van deze offset is configureerbaar in de client als een pointer naar de meest recente positie (nieuwste bericht) of vroegste positie (oudste bericht). De consument vraagt ​​(polls) berichten van het onderwerp, waardoor ze achtereenvolgens uit het logboek worden gelezen.
De offsetpositie wordt regelmatig teruggegeven aan Kafka en opgeslagen als berichten in een intern onderwerp _consumentencompensaties. Gelezen berichten worden nog steeds niet verwijderd, in tegenstelling tot een reguliere makelaar, en de klant kan de offset terugdraaien om reeds bekeken berichten opnieuw te verwerken.

Wanneer een tweede logische consument verbinding maakt met een andere group_id, beheert deze een tweede pointer die onafhankelijk is van de eerste (Figuur 3-3). Een Kafka-topic gedraagt ​​zich dus als een wachtrij waar één consument is en als een normaal publish-subscribe (pub-sub)-topic waarop meerdere consumenten zich abonneren, met als bijkomend voordeel dat alle berichten worden opgeslagen en meerdere keren kunnen worden verwerkt.

Berichtmakelaars begrijpen. De werking van berichten leren met ActiveMQ en Kafka. Hoofdstuk 3. Kafka
Afbeelding 3-3. Twee consumenten in verschillende consumentengroepen lezen van dezelfde partitie

Consumenten in een consumentengroep

Wanneer een consumenteninstantie gegevens van een partitie leest, heeft deze de volledige controle over de aanwijzer en worden berichten verwerkt zoals beschreven in de vorige sectie.
Als meerdere instanties van consumenten met dezelfde group_id waren verbonden met een onderwerp met één partitie, dan krijgt de instantie die als laatste verbinding heeft gemaakt de controle over de aanwijzer en ontvangt vanaf dat moment alle berichten (Figuur 3-4).

Berichtmakelaars begrijpen. De werking van berichten leren met ActiveMQ en Kafka. Hoofdstuk 3. Kafka
Afbeelding 3-4. Twee verbruikers in dezelfde verbruikersgroep lezen van dezelfde partitie

Deze verwerkingswijze, waarbij het aantal consumentenexemplaren het aantal partities overschrijdt, kan worden gezien als een soort exclusieve consument. Dit kan handig zijn als u "actief-passief" (of "hot-warm") clustering van uw consumentenexemplaren nodig hebt, hoewel het veel gebruikelijker is om meerdere consumenten parallel ("actief-actief" of "hot-hot") te laten draaien dan verbruikers Stand-by.

Dit hierboven beschreven berichtdistributiegedrag kan verrassend zijn in vergelijking met hoe een normale JMS-wachtrij zich gedraagt. In dit model worden berichten die naar de wachtrij worden gestuurd gelijkmatig verdeeld over de twee consumenten.

Wanneer we meerdere instanties van consumenten maken, doen we dit meestal om berichten parallel te verwerken, of om de leessnelheid te verhogen, of om de stabiliteit van het leesproces te vergroten. Hoe wordt dit in Kafka bereikt, aangezien slechts één consumenteninstantie tegelijk gegevens van een partitie kan lezen?

Een manier om dit te doen is door een enkele consumenteninstantie te gebruiken om alle berichten te lezen en door te geven aan de threadpool. Hoewel deze aanpak de verwerkingsdoorvoer verhoogt, verhoogt het de complexiteit van de consumentenlogica en doet het niets aan de robuustheid van het leessysteem. Als één exemplaar van de verbruiker uitvalt door een stroomstoring of iets dergelijks, dan stopt het aftrekken.

De gebruikelijke manier om dit probleem in Kafka op te lossen, is door bОmeer partities.

Verdeling

Partities zijn het belangrijkste mechanisme voor het parallel lezen en schalen van een onderwerp buiten de bandbreedte van een enkele brokerinstantie. Laten we, om dit beter te begrijpen, een situatie bekijken waarin er een onderwerp is met twee partities en één consument zich op dit onderwerp abonneert (Figuur 3-5).

Berichtmakelaars begrijpen. De werking van berichten leren met ActiveMQ en Kafka. Hoofdstuk 3. Kafka
Afbeelding 3-5. Eén consument leest van meerdere partities

In dit scenario krijgt de consument controle over de pointers die overeenkomen met zijn group_id in beide partities en begint hij berichten van beide partities te lezen.
Wanneer een extra consument voor dezelfde group_id wordt toegevoegd aan dit onderwerp, wijst Kafka een van de partities opnieuw toe van de eerste naar de tweede consument. Daarna leest elke instantie van de consument van één partitie van het onderwerp (Figuur 3-6).

Om ervoor te zorgen dat berichten in 20 threads parallel worden verwerkt, hebt u minimaal 20 partities nodig. Als er minder partities zijn, blijf je achter met consumenten die niets hebben om aan te werken, zoals eerder beschreven in de bespreking van exclusieve consumenten.

Berichtmakelaars begrijpen. De werking van berichten leren met ActiveMQ en Kafka. Hoofdstuk 3. Kafka
Afbeelding 3-6. Twee consumenten in dezelfde consumentengroep lezen van verschillende partities

Dit schema vermindert de complexiteit van de Kafka-makelaar aanzienlijk in vergelijking met de berichtendistributie die nodig is om de JMS-wachtrij te onderhouden. Hier hoeft u zich geen zorgen te maken over de volgende punten:

  • Welke consument het volgende bericht moet ontvangen, op basis van round-robin-toewijzing, huidige capaciteit van prefetch-buffers of eerdere berichten (zoals voor JMS-berichtengroepen).
  • Welke berichten naar welke consumenten worden gestuurd en of ze bij storing opnieuw moeten worden afgeleverd.

Het enige wat de Kafka-makelaar hoeft te doen, is berichten achtereenvolgens doorgeven aan de consument wanneer deze hierom vraagt.

De vereisten voor het parallelliseren van het proeflezen en het opnieuw verzenden van mislukte berichten verdwijnen echter niet - de verantwoordelijkheid daarvoor gaat gewoon over van de makelaar naar de klant. Dit betekent dat er in uw code rekening mee moet worden gehouden.

Berichten versturen

Het is de verantwoordelijkheid van de producent van dat bericht om te beslissen naar welke partitie een bericht gestuurd moet worden. Om het mechanisme te begrijpen waarmee dit wordt gedaan, moeten we eerst bedenken wat we precies verzenden.

Waar we in JMS een berichtenstructuur gebruiken met metadata (headers en eigenschappen) en een body die de payload bevat (payload), is het bericht in Kafka paar "sleutel-waarde". De payload van het bericht wordt als een waarde verzonden. De sleutel daarentegen wordt voornamelijk gebruikt voor partitionering en moet bevatten bedrijfslogica-specifieke sleutelom gerelateerde berichten in dezelfde partitie te plaatsen.

In hoofdstuk 2 hebben we het scenario voor online weddenschappen besproken waarbij gerelateerde gebeurtenissen op volgorde moeten worden verwerkt door een enkele consument:

  1. Het gebruikersaccount is geconfigureerd.
  2. Er wordt geld op de rekening bijgeschreven.
  3. Er wordt een weddenschap gesloten die geld van de rekening haalt.

Als elke gebeurtenis een bericht is dat in een onderwerp is geplaatst, is de natuurlijke sleutel de account-ID.
Wanneer een bericht wordt verzonden met behulp van de Kafka Producer API, wordt het doorgegeven aan een partitiefunctie die, gegeven het bericht en de huidige status van het Kafka-cluster, de ID retourneert van de partitie waarnaar het bericht moet worden verzonden. Deze functie is in Java geïmplementeerd via de Partitioner-interface.

Deze interface ziet er als volgt uit:

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

De Partitioner-implementatie gebruikt het standaard algemene hash-algoritme voor de sleutel om de partitie te bepalen, of round-robin als er geen sleutel is opgegeven. Deze standaardwaarde werkt in de meeste gevallen goed. In de toekomst wil je echter je eigen schrijven.

Uw eigen partitioneringsstrategie schrijven

Laten we eens kijken naar een voorbeeld waarbij u metadata samen met de berichtpayload wilt verzenden. De payload in ons voorbeeld is een instructie om een ​​storting te doen op de spelaccount. Een instructie is iets waarvan we zeker willen weten dat het niet wordt gewijzigd bij verzending en we willen er zeker van zijn dat alleen een vertrouwd stroomopwaarts systeem die instructie kan initiëren. In dit geval zijn de verzendende en ontvangende systemen het eens over het gebruik van een handtekening om het bericht te authenticeren.
In normale JMS definiëren we eenvoudig een eigenschap "berichthandtekening" en voegen deze toe aan het bericht. Kafka biedt ons echter geen mechanisme voor het doorgeven van metadata, alleen een sleutel en een waarde.

Aangezien de waarde een payload voor bankoverschrijvingen is waarvan we de integriteit willen behouden, hebben we geen andere keuze dan de gegevensstructuur te definiëren die in de sleutel moet worden gebruikt. Ervan uitgaande dat we een account-ID nodig hebben voor partitionering, aangezien alle berichten met betrekking tot een account in volgorde moeten worden verwerkt, zullen we de volgende JSON-structuur bedenken:

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

Omdat de waarde van de handtekening varieert afhankelijk van de payload, zal de standaard hashing-strategie van de Partitioner-interface gerelateerde berichten niet betrouwbaar groeperen. Daarom moeten we onze eigen strategie schrijven die deze sleutel zal ontleden en de accountId-waarde zal partitioneren.

Kafka bevat controlesommen om corruptie van berichten in de winkel te detecteren en heeft een volledige set beveiligingsfuncties. Toch verschijnen er soms branchespecifieke vereisten, zoals die hierboven.

De partitioneringsstrategie van de gebruiker moet ervoor zorgen dat alle gerelateerde berichten in dezelfde partitie terechtkomen. Hoewel dit eenvoudig lijkt, kan de vereiste worden gecompliceerd door het belang van het ordenen van gerelateerde berichten en hoe vast het aantal partities in een onderwerp is.

Het aantal partities in een onderwerp kan in de loop van de tijd veranderen, omdat ze kunnen worden toegevoegd als het verkeer de aanvankelijke verwachtingen overtreft. Berichtsleutels kunnen dus worden geassocieerd met de partitie waarnaar ze oorspronkelijk zijn verzonden, wat impliceert dat een stuk status moet worden gedeeld tussen producer-instanties.

Een andere factor waarmee rekening moet worden gehouden, is de gelijkmatige verdeling van berichten over partities. Sleutels zijn meestal niet gelijkmatig verdeeld over berichten en hash-functies garanderen geen eerlijke verdeling van berichten voor een kleine set sleutels.
Het is belangrijk op te merken dat hoe u berichten ook splitst, het scheidingsteken zelf mogelijk opnieuw moet worden gebruikt.

Overweeg de vereiste om gegevens te repliceren tussen Kafka-clusters op verschillende geografische locaties. Voor dit doel wordt Kafka geleverd met een opdrachtregelprogramma genaamd MirrorMaker, dat wordt gebruikt om berichten van het ene cluster te lezen en naar het andere over te brengen.

MirrorMaker moet de sleutels van het gerepliceerde onderwerp begrijpen om de relatieve volgorde tussen berichten te behouden bij het repliceren tussen clusters, aangezien het aantal partities voor dat onderwerp mogelijk niet hetzelfde is in twee clusters.

Aangepaste partitioneringsstrategieën zijn relatief zeldzaam, aangezien standaard hashing of round robin in de meeste scenario's goed werken. Als u echter sterke bestelgaranties nodig heeft of metadata uit payloads wilt extraheren, dan is partitionering iets waar u beter naar moet kijken.

De schaalbaarheid en prestatievoordelen van Kafka komen voort uit het verschuiven van een deel van de verantwoordelijkheden van de traditionele makelaar naar de klant. In dit geval wordt besloten om mogelijk gerelateerde berichten te verspreiden onder meerdere parallel werkende consumenten.

JMS-makelaars moeten ook met dergelijke vereisten omgaan. Interessant is dat het mechanisme voor het verzenden van gerelateerde berichten naar dezelfde consument, geïmplementeerd via JMS Message Groups (een variatie op de sticky load balancing (SLB)-strategie), ook vereist dat de afzender berichten als gerelateerd markeert. In het geval van JMS is de makelaar verantwoordelijk voor het verzenden van deze groep gerelateerde berichten naar één van de vele consumenten, en het overdragen van het eigendom van de groep als de consument eraf valt.

Producentenovereenkomsten

Partitionering is niet het enige waarmee u rekening moet houden bij het verzenden van berichten. Laten we eens kijken naar de methoden send() van de klasse Producer in de Java API:

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

Er moet onmiddellijk worden opgemerkt dat beide methoden Future retourneren, wat aangeeft dat de verzendbewerking niet onmiddellijk wordt uitgevoerd. Het resultaat is dat voor elke actieve partitie een bericht (ProducerRecord) naar de verzendbuffer wordt geschreven en naar de broker wordt verzonden als achtergrondthread in de Kafka-clientbibliotheek. Hoewel dit de dingen ongelooflijk snel maakt, betekent dit dat een onervaren applicatie berichten kan verliezen als het proces wordt gestopt.

Zoals altijd is er een manier om de verzendbewerking betrouwbaarder te maken ten koste van de prestaties. De grootte van deze buffer kan worden ingesteld op 0, en de verzendende applicatiethread zal worden gedwongen te wachten tot de berichtoverdracht naar de makelaar is voltooid, als volgt:

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

Meer over het lezen van berichten

Het lezen van berichten heeft extra complexiteit waarover moet worden gespeculeerd. In tegenstelling tot de JMS API, die een berichtluisteraar kan uitvoeren als reactie op een bericht, is de Consument Kafka doet alleen peilingen. Laten we de methode eens nader bekijken peiling()hiervoor gebruikt:

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

De retourwaarde van de methode is een containerstructuur die meerdere objecten bevat consumenten record van mogelijk meerdere partities. consumenten record is zelf een houderobject voor een sleutel-waardepaar met bijbehorende metadata, zoals de partitie waarvan het is afgeleid.

Zoals besproken in hoofdstuk 2, moeten we in gedachten houden wat er met berichten gebeurt nadat ze al dan niet succesvol zijn verwerkt, bijvoorbeeld als de client het bericht niet kan verwerken of als het wordt afgebroken. In JMS werd dit afgehandeld via een bevestigingsmodus. De makelaar verwijdert het succesvol verwerkte bericht of bezorgt het onbewerkte of valse bericht opnieuw (ervan uitgaande dat er transacties zijn gebruikt).
Kafka werkt heel anders. Berichten worden na proeflezen niet verwijderd in de makelaar, en wat er gebeurt bij een fout is de verantwoordelijkheid van de proefleescode zelf.

Zoals we al zeiden, is de consumentengroep gekoppeld aan de offset in het logboek. De logpositie die bij deze offset hoort, komt overeen met het volgende bericht dat als antwoord moet worden verzonden peiling(). Het tijdstip waarop deze offset toeneemt, is bepalend voor het uitlezen.

Terugkerend naar het eerder besproken leesmodel, bestaat de berichtverwerking uit drie fasen:

  1. Een bericht ophalen om te lezen.
  2. Verwerk het bericht.
  3. Bevestig bericht.

De Kafka-consument wordt geleverd met een configuratieoptie inschakelen.auto.commit. Dit is een vaak gebruikte standaardinstelling, zoals gebruikelijk is bij instellingen die het woord "auto" bevatten.

Vóór Kafka 0.10 stuurde een client die deze optie gebruikte de offset van het laatst gelezen bericht bij het volgende gesprek peiling() na verwerking. Dit betekende dat alle berichten die al waren opgehaald, opnieuw konden worden verwerkt als de client ze al had verwerkt, maar onverwachts werd vernietigd voordat er werd gebeld peiling(). Aangezien de makelaar geen enkele status bijhoudt over hoe vaak een bericht is gelezen, zal de volgende consument die dat bericht ophaalt niet weten dat er iets ergs is gebeurd. Dit gedrag was pseudo-transactioneel. De offset werd alleen vastgelegd als het bericht met succes was verwerkt, maar als de klant afbrak, zou de makelaar hetzelfde bericht opnieuw naar een andere klant sturen. Dit gedrag was in overeenstemming met de garantie voor berichtbezorging "ten minste een keer".

In Kafka 0.10 is de clientcode gewijzigd zodat de commit periodiek wordt geactiveerd door de clientbibliotheek, zoals geconfigureerd auto.commit.interval.ms. Dit gedrag bevindt zich ergens tussen de JMS AUTO_ACKNOWLEDGE- en DUPS_OK_ACKNOWLEDGE-modi. Bij gebruik van autocommit konden berichten worden vastgelegd, ongeacht of ze daadwerkelijk werden verwerkt - dit kan gebeuren in het geval van een langzame consument. Als een consument zou afbreken, zouden berichten worden opgehaald door de volgende consument, te beginnen bij de vastgelegde positie, wat zou kunnen resulteren in een gemist bericht. In dit geval is Kafka de berichten niet kwijtgeraakt, de leescode heeft ze gewoon niet verwerkt.

Deze modus heeft dezelfde belofte als in versie 0.9: berichten kunnen worden verwerkt, maar als het mislukt, wordt de offset mogelijk niet vastgelegd, waardoor de bezorging mogelijk wordt verdubbeld. Hoe meer berichten u ophaalt tijdens het uitvoeren peiling(), hoe meer dit probleem.

Zoals besproken in “Berichten uit een wachtrij lezen” op pagina 21, bestaat er niet zoiets als een eenmalige bezorging van een bericht in een berichtensysteem als rekening wordt gehouden met storingsmodi.

In Kafka zijn er twee manieren om een ​​offset (offset) vast te leggen (commit): automatisch en handmatig. In beide gevallen kunnen berichten meerdere keren worden verwerkt als het bericht is verwerkt maar is mislukt vóór de commit. Je kunt er ook voor kiezen om het bericht helemaal niet te verwerken als de commit op de achtergrond plaatsvond en je code was voltooid voordat het kon worden verwerkt (misschien in Kafka 0.9 en eerder).

U kunt het handmatige offset-commit-proces in de Kafka-consumenten-API regelen door de parameter in te stellen inschakelen.auto.commit naar false en expliciet een van de volgende methoden aanroepen:

void commitSync();
void commitAsync();

Als u het bericht "minstens één keer" wilt verwerken, moet u de offset handmatig vastleggen met commitSync()door deze opdracht direct na het verwerken van de berichten uit te voeren.

Met deze methoden kunnen berichten niet worden bevestigd voordat ze worden verwerkt, maar ze doen niets om potentiële verwerkingsvertragingen weg te nemen terwijl ze de schijn wekken dat het om transacties gaat. Er zijn geen transacties in Kafka. De cliënt heeft niet de mogelijkheid om het volgende te doen:

  • Draai automatisch een vervalst bericht terug. Consumenten moeten zelf omgaan met uitzonderingen die voortkomen uit problematische payloads en backend-uitval, omdat ze niet op de makelaar kunnen vertrouwen om berichten opnieuw af te leveren.
  • Stuur berichten naar meerdere onderwerpen in één atomaire bewerking. Zoals we binnenkort zullen zien, kan de controle over verschillende onderwerpen en partities zich op verschillende machines in de Kafka-cluster bevinden die geen transacties coördineren wanneer ze worden verzonden. Op het moment van schrijven is er wat werk verzet om dit mogelijk te maken met de KIP-98.
  • Associeer het lezen van een bericht van het ene onderwerp met het verzenden van een ander bericht naar een ander onderwerp. Nogmaals, de Kafka-architectuur is afhankelijk van veel onafhankelijke machines die als één bus draaien en er wordt geen poging gedaan om dit te verbergen. Er zijn bijvoorbeeld geen API-componenten waarmee u kunt koppelen klant и Producent bij een transactie. In JMS wordt dit geleverd door het object Sessiewaaruit zijn ontstaan BerichtProducers и BerichtConsumenten.

Als we niet op transacties kunnen vertrouwen, hoe kunnen we dan semantiek bieden die dichter bij die van traditionele berichtensystemen ligt?

Als de mogelijkheid bestaat dat de offset van de consument toeneemt voordat het bericht is verwerkt, zoals tijdens een consumentencrash, dan kan de consument niet weten of zijn consumentengroep het bericht heeft gemist toen er een partitie aan werd toegewezen. Een strategie is dus om de offset terug te spoelen naar de vorige positie. De consumenten-API van Kafka biedt hiervoor de volgende methoden:

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

werkwijze zoeken() kan worden gebruikt met methode
offsetsForTimes(Map tijdstempelsToSearch) om terug te spoelen naar een staat op een bepaald punt in het verleden.

Impliciet betekent het gebruik van deze aanpak dat het zeer waarschijnlijk is dat sommige berichten die eerder zijn verwerkt, opnieuw zullen worden gelezen en verwerkt. Om dit te voorkomen, kunnen we idempotent lezen gebruiken, zoals beschreven in hoofdstuk 4, om eerder bekeken berichten bij te houden en duplicaten te verwijderen.

Als alternatief kan uw consumentencode eenvoudig worden gehouden, zolang verlies of duplicatie van berichten acceptabel is. Als we kijken naar gebruikssituaties waarvoor Kafka vaak wordt gebruikt, zoals het afhandelen van logboekgebeurtenissen, metrische gegevens, het bijhouden van klikken, enz., begrijpen we dat het verlies van individuele berichten waarschijnlijk geen significante invloed zal hebben op omliggende applicaties. In dergelijke gevallen zijn de standaardwaarden perfect acceptabel. Aan de andere kant, als uw aanvraag betalingen moet verzenden, moet u zorgvuldig omgaan met elk afzonderlijk bericht. Het komt allemaal neer op context.

Uit persoonlijke observaties blijkt dat naarmate de intensiteit van berichten toeneemt, de waarde van elk afzonderlijk bericht afneemt. Grote berichten zijn meestal waardevol wanneer ze in geaggregeerde vorm worden bekeken.

Hoge beschikbaarheid

Kafka's benadering van hoge beschikbaarheid is heel anders dan die van ActiveMQ. Kafka is ontworpen rond scale-out clusters waar alle brokerinstanties tegelijkertijd berichten ontvangen en distribueren.

Een Kafka-cluster bestaat uit meerdere brokerinstanties die op verschillende servers draaien. Kafka is ontworpen om op gewone stand-alone hardware te draaien, waarbij elk knooppunt zijn eigen speciale opslag heeft. Het gebruik van Network Attached Storage (SAN) wordt niet aanbevolen omdat meerdere rekenknooppunten om tijd kunnen concurreren.Ыe opslagintervallen en creëer conflicten.

Kafka is altijd aan systeem. Veel grote Kafka-gebruikers sluiten hun clusters nooit af en de software wordt altijd bijgewerkt met een opeenvolgende herstart. Dit wordt bereikt door compatibiliteit met de vorige versie te garanderen voor berichten en interacties tussen brokers.

Makelaars aangesloten op een servercluster Dierentuinmedewerker, dat fungeert als een configuratiegegevensregister en wordt gebruikt om de rollen van elke makelaar te coördineren. ZooKeeper zelf is een gedistribueerd systeem dat een hoge beschikbaarheid biedt door middel van het repliceren van informatie door vast te stellen quorum.

In het basisscenario wordt een onderwerp gemaakt in een Kafka-cluster met de volgende eigenschappen:

  • Het aantal partities. Zoals eerder besproken, hangt de exacte waarde die hier wordt gebruikt af van het gewenste niveau van parallelle uitlezing.
  • De replicatiefactor (factor) bepaalt hoeveel brokerinstanties in het cluster logboeken voor deze partitie moeten bevatten.

Met behulp van ZooKeepers voor coördinatie probeert Kafka nieuwe partities eerlijk te verdelen onder de makelaars in het cluster. Dit wordt gedaan door een enkele instantie die optreedt als controller.

Tijdens looptijd voor elke onderwerppartitie controleur wijs rollen toe aan een makelaar leider (leider, meester, presentator) en volgers (volgelingen, slaven, ondergeschikten). De makelaar, die optreedt als leider voor deze partitie, is verantwoordelijk voor het ontvangen van alle berichten die door de producenten naar hem zijn verzonden en het verspreiden van de berichten naar de consumenten. Wanneer berichten naar een onderwerppartitie worden verzonden, worden ze gerepliceerd naar alle brokerknooppunten die fungeren als volgers voor die partitie. Elk knooppunt dat logboeken voor een partitie bevat, wordt aangeroepen replica. Een makelaar kan optreden als leider voor sommige partities en als volger voor andere.

Er wordt een volger gebeld die alle berichten van de leider bevat gesynchroniseerde replica (een replica die zich in een gesynchroniseerde staat bevindt, in-sync replica). Als een makelaar die optreedt als leider voor een partitie uitvalt, kan elke makelaar die up-to-date is of gesynchroniseerd is voor die partitie, de rol van leider overnemen. Het is een ongelooflijk duurzaam ontwerp.

Een deel van de producentenconfiguratie is de parameter aks, waarmee wordt bepaald hoeveel replica's de ontvangst van een bericht moeten bevestigen (bevestigen) voordat de toepassingsthread doorgaat met verzenden: 0, 1 of alles. Indien ingesteld op allen, wanneer een bericht wordt ontvangen, stuurt de leider een bevestiging terug naar de producent zodra deze bevestigingen (bevestigingen) van het record ontvangt van verschillende signalen (inclusief zichzelf) die zijn gedefinieerd door de onderwerpinstelling min.insync.replica's (standaard 1). Als het bericht niet met succes kan worden gerepliceerd, genereert de producent een toepassingsuitzondering (Niet GenoegReplica's of Niet genoegReplicasAfterAppend).

Een typische configuratie creëert een onderwerp met een replicatiefactor van 3 (1 leider, 2 volgers per partitie) en de parameter min.insync.replica's is ingesteld op 2. In dit geval staat het cluster toe dat een van de brokers die de onderwerppartitie beheert, uitvalt zonder dat dit gevolgen heeft voor clienttoepassingen.

Dit brengt ons terug bij de al bekende afweging tussen prestaties en betrouwbaarheid. Replicatie gaat ten koste van extra wachttijd voor bevestigingen (acknowledgements) van volgers. Hoewel, omdat het parallel loopt, replicatie naar ten minste drie knooppunten dezelfde prestaties levert als twee (de toename van het gebruik van netwerkbandbreedte buiten beschouwing gelaten).

Door dit replicatieschema te gebruiken, vermijdt Kafka op slimme wijze de noodzaak om elk bericht fysiek naar schijf te schrijven met de bewerking synchroniseren(). Elk bericht dat door de producent wordt verzonden, wordt naar het partitielogboek geschreven, maar zoals besproken in hoofdstuk 2, wordt het schrijven naar een bestand aanvankelijk gedaan in de buffer van het besturingssysteem. Als dit bericht wordt gerepliceerd naar een andere Kafka-instantie en zich in het geheugen bevindt, betekent het verlies van de leider niet dat het bericht zelf verloren is gegaan - het kan worden overgenomen door een gesynchroniseerde replica.
Weigering om de operatie uit te voeren synchroniseren() betekent dat Kafka berichten net zo snel kan ontvangen als ze in het geheugen kan schrijven. Omgekeerd, hoe langer u kunt voorkomen dat u het geheugen naar de schijf leegmaakt, hoe beter. Om deze reden is het niet ongebruikelijk dat Kafka-makelaars 64 GB of meer geheugen toegewezen krijgen. Dit geheugengebruik betekent dat een enkele Kafka-instantie gemakkelijk duizenden keren sneller kan werken dan een traditionele berichtenmakelaar.

Kafka kan ook worden geconfigureerd om de bewerking toe te passen synchroniseren() naar berichtenpakketten. Omdat alles in Kafka pakketgericht is, werkt het eigenlijk best goed voor veel gebruiksscenario's en is het een handig hulpmiddel voor gebruikers die zeer sterke garanties nodig hebben. Veel van de pure prestaties van Kafka komen van de berichten die als pakketten naar de makelaar worden verzonden en dat deze berichten in opeenvolgende blokken van de makelaar worden gelezen met behulp van nul-kopie bewerkingen (bewerkingen waarbij de taak van het kopiëren van gegevens van het ene geheugengebied naar het andere niet wordt uitgevoerd). Dit laatste is een grote prestatie- en resourcewinst en is alleen mogelijk door het gebruik van een onderliggende loggegevensstructuur die het partitieschema definieert.

In een Kafka-cluster zijn veel betere prestaties mogelijk dan met een enkele Kafka-broker, omdat onderwerppartities kunnen worden uitgeschaald over veel afzonderlijke machines.

Resultaten van

In dit hoofdstuk hebben we gekeken hoe de Kafka-architectuur de relatie tussen klanten en makelaars opnieuw vormgeeft om een ​​ongelooflijk robuuste berichtenpijplijn te bieden, met een doorvoer die vele malen groter is dan die van een conventionele berichtenmakelaar. We hebben de functionaliteit besproken die het gebruikt om dit te bereiken en hebben kort gekeken naar de architectuur van applicaties die deze functionaliteit bieden. In het volgende hoofdstuk bekijken we veelvoorkomende problemen die op berichten gebaseerde toepassingen moeten oplossen en bespreken we strategieën om hiermee om te gaan. We sluiten het hoofdstuk af door te schetsen hoe u over berichtentechnologieën in het algemeen kunt praten, zodat u hun geschiktheid voor uw gebruikssituaties kunt beoordelen.

Vorig vertaald deel: Berichtmakelaars begrijpen. De werking van berichten leren met ActiveMQ en Kafka. Hoofdstuk 1

Vertaling gedaan: tele.gg/middle_java

Wordt vervolgd ...

Alleen geregistreerde gebruikers kunnen deelnemen aan het onderzoek. Inloggen, Alsjeblieft.

Wordt Kafka gebruikt in uw organisatie?

  • Ja

  • Geen

  • Vroeger gebruikt, nu niet meer

  • We zijn van plan om te gebruiken

38 gebruikers hebben gestemd. 8 gebruikers onthielden zich van stemming.

Bron: www.habr.com

Voeg een reactie