Hej, Habr!
Vi pÄminner om att vi, efter boken om har slÀppt ett lika intressant verk om biblioteket .

Just nu utforskar communityn fortfarande grÀnserna för detta kraftfulla verktyg. Nyligen publicerades en artikel som vi vill introducera er för. Författaren berÀttar utifrÄn egen erfarenhet hur man gör ett distribuerat datalager av Kafka Streams. Trevlig lÀsning!
Biblioteket Apache anvÀnds vÀrlden över i företag för distribuerad strömhantering ovanpÄ Apache Kafka. En av de mest underskattade aspekterna av detta ramverk Àr att det möjliggör lagring av lokalt tillstÄnd baserat pÄ strömhantering.
I denna artikel kommer jag att berÀtta hur vi pÄ vÄrt företag framgÄngsrikt har utnyttjat denna möjlighet vid utveckling av en produkt för sÀkerheten av molnapplikationer. Med hjÀlp av Kafka Streams skapade vi mikrotjÀnster med delat tillstÄnd, dÀr var och en fungerar som en robust och hög tillgÀnglig kÀlla till pÄlitlig information om objektens tillstÄnd i systemet. För oss Àr detta ett steg framÄt bÄde nÀr det gÀller tillförlitlighet och underhÄll.
Om du Ă€r intresserad av ett alternativt tillvĂ€gagĂ„ngssĂ€tt som möjliggör anvĂ€ndningen av en enda central databas för att stödja det formella tillstĂ„ndet för dina objekt â lĂ€s vidare, det kommer att vara intressant...
Varför vi ansÄg att det var dags att förÀndra vÄra metoder för att arbeta med delat tillstÄnd
Vi behövde upprÀtthÄlla tillstÄndet för olika objekt baserat pÄ agentrapporter (till exempel: utsattes webbplatsen för en attack?). Före övergÄngen till Kafka Streams förlitade vi oss ofta pÄ en enda central databas (+ tjÀnste-API) för att hantera tillstÄnd. Denna metod har sina nackdelar: i blir upprÀtthÄllande av konsekvens och synkronisering en verklig utmaning. Databasen kan bli en flaskhals, eller hamna i och drabbas av oförutsÀgbarhet.

Illustration 1: ett typiskt scenario med delat tillstÄnd som uppstod före övergÄngen till
Kafka och Kafka Streams: agenter rapporterar sina uppfattningar via API, det uppdaterade tillstÄndet berÀknas genom databasen.
LĂ€r kĂ€nna Kafka Streams â det har blivit enkelt att skapa mikrotjĂ€nster med delat tillstĂ„nd.
För ungefĂ€r ett Ă„r sedan beslutade vi oss för att noggrant se över vĂ„ra arbetsflöden med delat tillstĂ„nd för att hantera vissa problem. Vi bestĂ€mde oss raskt för att prova Kafka Streams â det Ă€r allmĂ€nt kĂ€nt hur skalbar, högt tillgĂ€nglig och fel-tolerant den Ă€r, samt vilket rikt strömmande funktionalitet den har (inklusive transformationer med tillstĂ„ndssparande). Detta var precis vad vi behövde, för att inte nĂ€mna hur mogen och pĂ„litlig meddelandesystemet Ă€r i Kafka.
Varje mikrotjÀnst med delat tillstÄnd som vi har skapat bygger pÄ en instans av Kafka Streams med en ganska enkel topologi. Den bestod av 1) en kÀlla 2) en processor med ett bestÀndigt lagringsutrymme för nycklar och vÀrden 3) en ström:

Illustration 2: den förinstÀllda topologin för vÄra strömmande instanser av mikrotjÀnster med delat tillstÄnd. Observera att det ocksÄ finns ett lager hÀr dÀr metadata om schemalÀggningen lagras.
Med detta nya tillvĂ€gagĂ„ngssĂ€tt skapar agenter meddelanden som skickas till den ursprungliga topiken, och konsumenter â lĂ„t oss sĂ€ga en tjĂ€nst för e-postnotifikationer â tar emot det berĂ€knade delade tillstĂ„ndet via strömmen (utgĂ„ngstopiken).

Illustration 3: ett nytt exempel pÄ uppgiftström för scenariot med delade mikrotjÀnster: 1) agenten genererar ett meddelande som gÄr till Kafka:s ursprungliga topik; 2) mikrotjÀnsten med delat tillstÄnd (som anvÀnder Kafka Streams) bearbetar det och skriver det berÀknade tillstÄndet till Kafka:s slutliga topik; dÀrefter 3) konsumenterna tar emot det nya tillstÄndet.
Hej, och detta inbyggda lagringsutrymme för nycklar och vÀrden Àr verkligen mycket anvÀndbart!
Som nÀmnts tidigare innehÄller vÄr topologi med delat tillstÄnd ett lagringsutrymme för nycklar och vÀrden. Vi har hittat flera anvÀndningsomrÄden för det, och tvÄ av dem beskrivs nedan.
Alternativ #1: anvÀndning av lagringsutrymmet för nycklar och vÀrden vid berÀkningar
VÄrt första nyckel-vÀrde-lager innehöll hjÀlpdata som vi behövde för vÄra berÀkningar. Till exempel, i vissa fall definierades det delade tillstÄndet enligt principen 'majoritetens röster'. I lagret kunde vi hÄlla alla de senaste rapporterna frÄn agenter om tillstÄndet för ett visst objekt. NÀr vi sedan fick en ny rapport frÄn en agent kunde vi spara den, hÀmta rapporterna frÄn alla andra agenter om samma objekts tillstÄnd och upprepa berÀkningen.
Nedan i illustration 4 visas hur vi öppnade tillgÄngen till nyckel-vÀrde-lagret för processorens bearbetande metod, sÄ att ett nytt meddelande sedan kunde behandlas.

Illustration 4: öppna tillgÄng till nyckel-vÀrde-lagret för processorens bearbetande metod (efter detta mÄste varje scenario som arbetar med delat tillstÄnd implementera metoden doProcess)
Alternativ #2: skapa ett CRUD API ovanpÄ Kafka Streams
Efter att ha stÀllt in vÄr grundlÀggande uppgiftström började vi försöka skriva ett RESTful CRUD API för vÄra mikrotjÀnster med delat tillstÄnd. Vi ville kunna hÀmta tillstÄndet för vissa eller alla objekt, samt stÀlla in eller ta bort tillstÄndet för ett objekt (detta Àr anvÀndbart vid stöd för backend).
För att stödja alla API:er för HÀmta tillstÄnd, varje gÄng vi behövde berÀkna tillstÄndet pÄ nytt under bearbetningen, sparade vi det lÀnge i det inbyggda nyckel-vÀrde-lagret. I sÄdana fall blir det tillrÀckligt enkelt att implementera ett sÄdant API med hjÀlp av en enda instans av Kafka Streams, som visas i listan nedan:

Illustration 5: anvÀndning av det inbyggda nyckel-vÀrde-lagret för att hÀmta det förberÀknade tillstÄndet för ett objekt
Att uppdatera tillstÄndet för ett objekt via API Àr ocksÄ enkelt att implementera. I princip behöver man bara skapa en Kafka-producent och anvÀnda den för att göra en skrivning som innehÄller det nya tillstÄndet. Detta garanterar att alla meddelanden som genereras via API kommer att behandlas pÄ samma sÀtt som de som kommer frÄn andra producenter (t.ex. agenter).

Illustration 6: tillstÄndet för ett objekt kan anges med en Kafka-producent
En liten komplikation: Kafka har mÄnga partitioner
Vi ville dÀrefter fördela den bearbetningsrelaterade belastningen och förbÀttra tillgÀngligheten genom att tillhandahÄlla en kluster av mikrotjÀnster med delat tillstÄnd för varje scenari. InstÀllningen var enkel för oss: efter att vi konfigurerat alla instanser sÄ att de arbetade med samma app-ID (och med samma initiala servrar), gjordes praktiskt taget allt annat automatiskt. Vi bestÀmde ocksÄ att varje ursprunglig topic skulle bestÄ av flera partitioner sÄ att varje instans kunde tilldelas en delmÀngd av dessa partitioner.
Jag nÀmner ocksÄ att det Àr vanligt hÀr att göra en backup av tillstÄndslagring, sÄ att denna kopia till exempel kan flyttas till en annan instans vid ÄterstÀllning efter ett fel. För varje tillstÄndslagring i Kafka Streams skapas en replikerad topic med en förÀndringslogg (som spÄrar lokala uppdateringar). PÄ sÄ sÀtt sÀkerstÀller Kafka kontinuerligt tillstÄndslagringen. SÄ i hÀndelse av att en viss instans av Kafka Streams misslyckas kan tillstÄndslagringen snabbt ÄterstÀllas pÄ en annan instans, dit de relevanta partitionerna flyttas. VÄra tester visade att detta görs pÄ nÄgra sekunder Àven om lagringen innehÄller miljontals poster.
ĂvergĂ„ngen frĂ„n en mikrotjĂ€nst med delat tillstĂ„nd till ett kluster av mikrotjĂ€nster gör det inte sĂ„ triviellt att implementera Get State API. I den nya situationen innehĂ„ller tillstĂ„ndslagringen för varje mikrotjĂ€nst endast en del av den övergripande bilden (de objekt vars nycklar avbildades pĂ„ en specifik partition). Vi var tvungna att avgöra vilken instans som innehöll tillstĂ„ndet för det objekt vi behövde, och vi gjorde detta baserat pĂ„ metadata frĂ„n flöden, som visas nedan:

Illustration 7: med hjÀlp av metadata frÄn flöden bestÀmmer vi frÄn vilken instans vi ska begÀra tillstÄndet för det erforderliga objektet; en liknande ansats anvÀndes med GET ALL API
Huvudsakliga slutsatser
TillstÄndslagringar i Kafka Streams kan de facto fungera som en distribuerad databas,
- som stÀndigt replikerats i Kafka
- OvanpÄ ett sÄdant system Àr det enkelt att bygga ett CRUD API
- Bearbetning av flera partitioner blir lite mer komplicerat
- Det Àr ocksÄ möjligt att lÀgga till en eller flera tillstÄndslager i strömningstopologin för att lagra hjÀlpdata. Denna variant kan anvÀndas för:
- LÄngsiktig lagring av data som behövs för berÀkningar under strömbehandling
- LÄngsiktig lagring av data som kan vara anvÀndbara vid nÀsta initiering av en strömmande instans
- mycket annatâŠ
Tack vare dessa och andra fördelar passar Kafka Streams utmÀrkt för att stödja globalt tillstÄnd i ett distribuerat system som vÄrt. Kafka Streams har visat sig vara mycket pÄlitlig i produktionen (sedan den implementerades har vi praktiskt taget inte tappat nÄgra meddelanden), och vi Àr övertygade om att dess möjligheter inte Àr begrÀnsade till detta!
KĂ€lla: habr.com
