Articles

Frågbara Kafka-ämnen med Kafka-strömmar

i dagens databehandlingsarkitekturer används Apache Kafka ofta i ingress-scenen. Vanligtvis används detta steg för att berika och filtrera inkommande meddelandedata; det är dock inte möjligt att göra interaktiva frågor förrän ett senare steg i databehandlingspipelinen. Detta beror på att även om varje meddelande i ett Kafka-ämne kvarstår som standard, är ingen mekanism tillgänglig än så länge som möjliggör snabba sökningar av ett specifikt meddelande i ett ämne.

att kunna söka efter nya data i detta tidiga skede i rörledningen skulle dock undvika förseningar av traditionella bearbetningsrörledningar som vanligtvis inkluderar långvariga batchförbehandlingssteg och skulle ge slutanvändarna nästan omedelbar tillgång till inkommande data.

För att bygga databehandlingsapplikationer med Kafka används Kafka Streams-biblioteket, som underhålls som en del av Kafka-projektet, ofta för att definiera datatransformationer och analyser. Ett viktigt inslag i Kafka-strömmar är statliga butiker, som erbjuder en abstraktion av en snabb lokal nyckelvärdesbutik som kan läsas och skrivas till vid bearbetning av meddelanden med Kafka-strömmar. Dessa nyckelvärdesbutiker kan kontinuerligt fyllas med nya meddelanden från ett Kafka-ämne genom att definiera en lämplig strömprocessor, så att det nu är möjligt att snabbt hämta meddelanden från det underliggande ämnet.

Med utgångspunkt i denna Kafka Streams-funktionalitet skapar vi ett enhetligt REST API som ger en enda frågande slutpunkt för ett givet Kafka-ämne.

Sammanfattningsvis kan kombinationen av Kafka-strömprocessorer med statliga butiker och en HTTP-server effektivt göra alla Kafka-ämnen till en snabb skrivskyddad nyckelvärdesbutik.

Kafka Streams är byggt som ett bibliotek som kan bäddas in i en fristående Java-eller Scala-applikation. Det gör det möjligt för utvecklare att definiera strömprocessorer som utför datatransformationer eller aggregeringar på Kafka-meddelanden, vilket säkerställer att varje inmatningsmeddelande behandlas exakt en gång. Med hjälp av Kafka Streams DSL, som är inspirerad av Java Stream API, kan strömprocessorer och statliga butiker vara flexibelt kedjade.

dessutom, när du startar flera instanser av en Kafka Streams-baserad applikation, bildar processerna automatiskt ett lastbalanserande, mycket tillgängligt bearbetningskluster utan att bero på andra externa system än Kafka.

för att illustrera arkitekturen i en Kafka Streams-applikation som använder statliga butiker, föreställ dig följande scenario: som järnvägsoperatör, varje gång en kund bokar en resa på vår hemsida, sätts ett nytt meddelande bestående av kund-id och en tidsstämpel in i ett Kafka-ämne. Specifikt infogar en eller flera Kafka-producenter dessa meddelanden i ett ämne med nio partitioner. Eftersom kund-id väljs som nyckel för varje meddelande, kommer data som tillhör en viss kund alltid att infogas i samma partition av ämnet. Implicit använder Kafka-producenter DefaultPartitioner för att tilldela meddelanden till partitioner.

nu antar vi att vi har en Kafka strömmar program som läser meddelanden från detta ämne och kvarstår dem i en stat butik. Eftersom nyckeln till varje meddelande endast består av kund-id, kommer motsvarande värde i statsbutiken alltid att vara tidsstämpeln för kundens senaste bokning. För att uppnå maximal grad av parallell bearbetning kan vi starta upp till nio instanser av Kafka Streams-applikationen. I det här fallet kommer varje applikationsinstans att tilldelas exakt en av ämnespartitionerna. För varje ingångspartition skapar Kafka Streams en separat statsbutik, som i sin tur bara innehåller data från de kunder som tillhör den partitionen.

den resulterande applikationsarkitekturen illustreras i diagrammet nedan.

den Kafka stream processorer som ansvarar för partitionerna 4 till 9 utelämnas i denna illustration. De streckade pilarna indikerar att nya meddelanden i en partition också sprids till ytterligare strömprocessorer och deras statliga butiker, vilket möjliggör en snabb misslyckande om den primärt tilldelade processorn skulle misslyckas.

det är möjligt att antingen använda i minnet eller ihållande statliga butiker i programmet. Operationer på tillståndsbutiker i minnet är ännu snabbare jämfört med den ihållande varianten, som internt använder en RocksDB-butik. Å andra sidan kan persistent state stores återställas snabbare om en Kafka Streams-applikation har misslyckats och måste startas om. Dessutom är datavolymen per butik inte begränsad av mängden huvudminne vid användning av ihållande tillståndsbutiker.

i vårt scenario är det inte nödvändigt att ha ETT ändringsloggämne som registrerar skrivoperationer till statsbutikerna: Alla data som behövs för att återställa en statsbutik kan erhållas från det ursprungliga inmatningsämnet.

lägga till en REST-slutpunkt till strömprocessorer

med den arkitektur som hittills presenterats har vi nio statliga butiker som kan användas för att hämta det senaste bokningsdatumet för de kunder som tillhör respektive inmatningsämnespartition.

nu, för att göra denna information tillgänglig från utsidan av Kafka strömmar processorer, vi behöver för att exponera en tjänst slutpunkt på var och en av stream processorprogram instanser och svara på inkommande förfrågningar från den interna Statliga butiken som hanteras av Kafka strömmar.

som ett ytterligare krav kan vi inte förvänta oss att den begärande applikationen vet vilken Kafka Streams-instans som för närvarande ansvarar för att behandla en viss kunds data. Följaktligen är varje service endpoint ansvarig för att omdirigera frågan till rätt applikationsinstans om kunddata inte är lokalt tillgängliga.

Vi valde att implementera service endpoint som ett REST API, vilket har fördelen att det är tillgängligt från alla klienter som stöder HTTP, och gör det möjligt att lägga till en transparent lastbalanserare mycket enkelt.

KafkaStreams-objektet, som är tillgängligt i alla Kafka Streams-applikationer, ger skrivskyddad åtkomst till alla lokala statliga butiker och kan också bestämma applikationsinstansen som är ansvarig för ett visst kund-id. När du använder det här objektet för att bygga vår REST-tjänst ser arkitekturen ut som följer:

en HTTP-klient kan skicka uppslagningsförfrågningar till någon av de återstående slutpunkterna för strömprocessorerna. Den streckade pilen anger hur en begäran vidarebefordras internt bland strömprocessorerna, om den inte kan besvaras från en lokal statsbutik.

Sammanfattningsvis använder vår föreslagna arkitektur Kafka-ämnen för att på ett tillförlitligt sätt lagra meddelandedata i vila och upprätthåller en andra representation av data i statliga butiker för att stödja snabba frågor.

säkerställa skalbarhet för applikationen

för att få en skalbar applikation måste vi se till att bearbetningsbelastningen är lika balanserad över alla instanser av Kafka Streams-applikationen. Belastningen på en enskild strömprocessor beror på mängden data och frågor som den måste hantera.

Mer specifikt är det viktigt att välja ett partitioneringsschema för Kafka-ämnet så att belastningen av inkommande meddelanden och frågor är välbalanserad över alla partitioner och följaktligen också alla strömprocessorer.

om lagring i minnet ska användas måste antalet partitioner i ämnet vara tillräckligt stort så att varje strömprocessor kan behålla datavolymen för en partition i huvudminnet. Observera att i händelse av felövergångar kan en strömprocessor till och med behöva hålla två partitioner i huvudminnet.

för att ta hänsyn till strömprocessorfel kan antalet standby-repliker konfigureras med inställningen num.standby.replicas I Kafka-strömmar, vilket säkerställer att ytterligare strömprocessorer också prenumererar på meddelanden från en viss processorpartition. I händelse av ett fel kan dessa processorer snabbt ta över svar på frågor istället för att börja rekonstruera statsbutiken först efter att ett fel redan har inträffat.

Slutligen måste det önskade antalet strömprocessorer passa till den tillgängliga hårdvaran. För varje strömprocessorapplikationsinstans bör minst en CPU-kärna reserveras. På flerkärniga system är det möjligt att öka antalet strömtrådar per applikationsinstans, vilket mildrar kostnader som uppstår genom att starta en separat Java-applikation per CPU-kärna.