
Hei alle sammen. I denne artikkelen vil jeg forklare hvorfor vi hos Avito for ni mĂ„neder siden valgte Kafka, og hva det er. Jeg vil dele en av bruksomrĂ„dene â meldingsmegler. Til slutt vil vi diskutere hvilke fordeler vi har fĂ„tt fra Ă„ bruke tilnĂŠrmingen Kafka as a Service.
Problemet

FÞrst litt kontekst. For en tid siden begynte vi Ä bevege oss bort fra monolittisk arkitektur, og nÄ har Avito flere hundre forskjellige tjenester. De har sine egne lagringslÞsninger, sin teknologistack og er ansvarlige for sin del av forretningslogikken.
Et av problemene med et stort antall tjenester er kommunikasjonen. Tjeneste A vil ofte vite informasjonen som tjeneste B har. I dette tilfellet kontakter tjeneste A tjeneste B via et synkront API. Tjeneste C Þnsker Ä vite hva som skjer med tjenester D og E, og de er igjen interesserte i tjenester A og B. NÄr det blir mange «nysgjerrige» tjenester, blir forbindelsene mellom dem til en komplisert sammenfiltring.
I tillegg kan tjeneste A nÄr som helst bli utilgjengelig. Hva skal tjeneste B og alle andre avhengige tjenester gjÞre i sÄ fall? Og hvis en forretningsoperasjon krever at man utfÞrer en kjede av sekvensielle synkrone anrop, Þker sannsynligheten for at hele operasjonen mislykkes enda mer (og den er desto hÞyere jo lengre denne kjeden er).
Valg av teknologi

Greit, problemene er klare. De kan lĂžses ved Ă„ lage et sentralisert meldingssystem mellom tjenestene. NĂ„ trenger hver tjeneste bare Ă„ vite om dette meldingssystemet. I tillegg bĂžr selve systemet vĂŠre feilresistent og horisontalt skalerbart, samt i tilfelle av brudd lagre forespĂžrslene for senere behandling.
La oss nÄ velge teknologien som skal implementere meldingsleveransen. For dette mÄ vi fÞrst forstÄ hva vi forventer fra den:
- meldinger mellom tjenester mÄ ikke gÄ tapt;
- meldinger kan dupliseres;
- meldinger kan lagres og leses i flere dager (persistent buffer);
- tjenester kan abonnere pÄ data de er interessert i;
- flere tjenester kan lese de samme dataene;
- meldinger kan inneholde detaljert, omfattende payload (event-carried state transfer);
- noen ganger er det behov for garantert rekkefĂžlge av meldinger.
Det var ogsÄ kritisk viktig for oss Ä velge et sÄ skalerbart og pÄlitelig system som mulig med hÞy kapasitet (minst 100k meldinger pÄ flere kilobyte per sekund).
PÄ dette stadiet tok vi farvel med RabbitMQ (vanskelig Ä opprettholde stabilitet ved hÞye rps), PGQ fra SkyTools (ikke rask nok og dÄrlig skalerbar) og NSQ (ikke persisterende). Alle disse teknologiene brukes i selskapet vÄrt, men de passet ikke til oppgaven vi skulle lÞse.
Deretter begynte vi Ă„ se pĂ„ nye teknologier for oss â Apache Kafka, Apache Pulsar og NATS Streaming.
Vi droppet Pulsar fÞrst. Vi mente at Kafka og Pulsar er ganske like lÞsninger. Og selv om Pulsar er testet av store selskaper, er nyere og teoretisk tilbyr lavere latency, bestemte vi oss for Ä beholde Kafka av disse to, som de facto-standard for slike oppgaver. Vi kommer sannsynligvis til Ä gÄ tilbake til Apache Pulsar i fremtiden.
Og vi hadde to kandidater igjen: NATS Streaming og Apache Kafka. Vi har studert begge lĂžsninger ganske grundig, og begge passet oppgaven. Men til slutt var vi bekymret for den relative ungdommen til NATS Streaming (og at en av hovedutviklerne, Tyler Treat, bestemte seg for Ă„ forlate prosjektet og starte sitt eget â Liftbridge). I tillegg ga ikke Clustering-modusen til NATS Streaming muligheten for sterk horisontal skalerbarhet (muligens er dette ikke lenger et problem etter at partitioning-modusen ble lagt til i 2017).
Likevel er NATS Streaming en imponerende teknologi, skrevet i Go og har stÞtte fra Cloud Native Computing Foundation. I motsetning til Apache Kafka trenger den ikke Zookeeper for Ä fungere (kanskje, ), siden den innvendig implementerer RAFT. I tillegg er NATS Streaming enklere Ä administrere. Vi utelukker ikke at vi i fremtiden kanskje vil gÄ tilbake til denne teknologien.
Og likevel ble Apache Kafka vÄr vinner i dag. I testene vÄre viste den seg Ä vÊre rask nok (over en million meldinger per sekund ved lesing og skriving med en meldingstÞrrelse pÄ 1 kilobyte), tilstrekkelig pÄlitelig, godt skalerbar og bevist i produksjon av store selskaper. I tillegg stÞtter Kafka minst flere store kommersielle selskaper (vi bruker for eksempel Confluent-versjonen), samt at Kafka har et utviklet Þkosystem.
Oversikt over Kafka
FĂžr du begynner, vil jeg umiddelbart anbefale en utmerket bok â «Kafka: The Definitive Guide» (finnes ogsĂ„ i norsk oversettelse, men begrepene kan vĂŠre litt forvirrende). Den inneholder informasjonen som er nĂždvendig for en grunnleggende forstĂ„else av Kafka, og til og med litt mer. Selve dokumentasjonen fra Apache og bloggen fra Confluent er ogsĂ„ godt skrevet og lett Ă„ lese.
La oss se pÄ hvordan Kafka er strukturert fra et fugleperspektiv. Den grunnleggende topologien til Kafka bestÄr av producer, consumer, broker og zookeeper.
Broker

Brokeren (broker) er ansvarlig for lagringen av dataene dine. All informasjon lagres i binĂŠrt format, og brokeren vet lite om hva de representerer og hva som er strukturen deres.
Hver logisk type hendelse befinner seg vanligvis i sin egen separate topic. For eksempel kan en hendelse for opprettelse av et annonseinnlegg havne i topicen item.created, mens en hendelse for endringen av den havner i item.changed. Topics kan betraktes som klassifikatorer av hendelser. PÄ topic-nivÄ kan slike konfigurasjonsparametre settes som:
- volumet av lagrede data og/eller deres alder (retention.bytes, retention.ms);
- replikasjonsfaktor (replication factor);
- maksimal stÞrrelse pÄ en melding (max.message.bytes);
- minimum antall bekreftede kopier, for at data kan skrives til topicen (min.insync.replicas);
- muligheten for Ă„ gjennomfĂžre failover til en usynkronisert, forsinket kopi med potensiell dataforlust (unclean.leader.election.enable);
- og mange andre ().
Hver topic deles igjen opp i en eller flere partitioner. Det er i partitionene hendelsene havner til slutt. Hvis det er mer enn én broker i klyngen, vil partitionene bli fordelt jevnt over alle brokerne (sÄ langt det er mulig), noe som gjÞr det mulig Ä skalere bÄde skriving og lesing i en topic til flere brokere samtidig.
Dataene for hver partition lagres pÄ disken i form av segmentfiler, som som standard er én gigabyte store (kontrollert via log.segment.bytes). En viktig detalj er at fjerning av data fra partitionene (nÄr retention utlÞses) skjer i segmenter (man kan ikke slette én hendelse fra en partition, man kan bare slette ett helt segment, og kun inaktivt).
Zookeeper
Zookeeper fungerer som lager for metadata og koordinator. Det er det som kan si om brokerne er aktive (man kan se pÄ dette med zookeeper via zookeeper-shell kommandoen ls /brokers/ids), hvilken av brokerne som er kontroller (get /controller), er partisjonene i synkron tilstand med sine replikater (get /brokers/topics/topic_name/partitions/partition_number/state). Det er ogsÄ til zookeeper at produsent og konsument fÞrst gÄr for Ä finne ut hvilken broker som lagrer hvilke emner og partitioner. I tilfeller der emnet har en replikasjonsfaktor stÞrre enn 1, vil zookeeper indikere hvilke partitioner som er ledere (der skriving vil skje og fra dem vil lesing ogsÄ foregÄ). Ved et broker-nedbrudd vil informasjon om nye leder-partitioner bli registrert i zookeeper (siden versjon 1.1.0 asynkront, ).
I eldre versjoner av Kafka var zookeeper ogsÄ ansvarlig for Ä lagre offsets, men nÄ lagres de i et spesialemne __consumer_offsets pÄ brokeren (selv om du fortsatt kan bruke zookeeper for disse formÄlene).
Den enkleste mÄten Ä gjÞre dataene dine om til gresskar pÄ er nettopp Ä miste informasjonen fra zookeeper. I et slikt scenario vil det vÊre svÊrt vanskelig Ä forstÄ hva og hvorfra man mÄ lese.
Produsent
Produsent â er oftest en tjeneste som utfĂžrer den direkte skrivingen av data til Apache Kafka. Produsenten velger emnet der temaene skal lagres, og begynner Ă„ skrive informasjon inn i det. For eksempel kan en annonsetjeneste fungere som produsent. I sĂ„ fall vil den sende hendelser som «annonse opprettet», «annonse oppdatert», «annonse slettet» osv. til de tematiske emnene. Hver hendelse representerer et nĂžkkel-verdi-par.
Som standard fordeles alle hendelser jevnt over partitionene i emnet med round-robin, med mindre en nĂžkkel er angitt (og mister rekkefĂžlge), og ved hjelp av MurmurHash (nĂžkkel) hvis en nĂžkkel er til stede (rekkefĂžlge innen en partition).
Her bÞr det bemerkes at Kafka garanterer rekkefÞlge av hendelser bare innen en partition. Men i virkeligheten er dette ofte ikke et problem. For eksempel kan man garantert legge til alle endringer av den samme annonsen i én partition (og dermed opprettholde rekkefÞlgen pÄ disse endringene innenfor annonsen). Det er ogsÄ mulig Ä sende rekkefÞlgenummeret i et av feltene i hendelsen.
Konsument

Consumeren er ansvarlig for Ä hente data fra Apache Kafka. Hvis vi gÄr tilbake til eksemplet ovenfor, kan consumeren vÊre en modereringstjeneste. Denne tjenesten vil abonnere pÄ emnet til annonsetjenesten, og ved publisering av en ny annonse vil den motta og analysere den for Ä se om den samsvarer med bestemte policyer.
Apache Kafka husker hvilke siste hendelser consumeren har mottatt (for dette brukes kontrolltoppikket __consumer__offsets), og dermed garanterer at nÄr en consumer leser vellykket, ikke vil motta den samme meldingen to ganger. Imidlertid, hvis man bruker alternativet enable.auto.commit = true og overlater ansvaret for Ä spore consumerens posisjon i emnet til Kafka, kan man . I produksjonskode kontrolleres som oftest posisjonen til consumeren manuelt (utvikleren styrer tidspunktet for nÄr commit av den leste hendelsen nÞdvendigvis mÄ skje).
I tilfeller hvor én consumer ikke er nok (for eksempel nÄr strÞmmen av nye hendelser er veldig stor), kan man legge til flere consumere og knytte dem sammen i en consumergruppe. Consumergruppen representerer logisk akkurat den samme consumeren, men med fordeling av data mellom gruppemedlemmene. Dette gjÞr at hver deltaker kan ta sin andel av meldingene, og dermed skalere lesefarten.
Testresultater

Her vil jeg ikke skrive mye forklarende tekst, bare dele de innsamlede resultatene. Testingen ble utfÞrt pÄ 3 fysiske maskiner (12 CPU, 384 GB RAM, 15k SAS DISK, 10 GBit/s nett), og brokerne samt zookeeper ble rullet ut i lxc.
Ytelsestesting
Under testingen ble fÞlgende resultater oppnÄdd.
- Skrivehastigheten for meldinger pĂ„ 1 KB med 9 produsenter samtidig â 1,300,000 hendelser per sekund.
- Lesehastigheten for meldinger pĂ„ 1 KB med 9 consumere samtidig â 1,500,000 hendelser per sekund.
Testing av feiltoleranse
Under testingen ble fÞlgende resultater oppnÄdd (3 brokere, 3 zookeeper).
- En uventet avslutning av en av brokerne fÞrer ikke til stopp eller utilgjengelighet av klyngen. Arbeidet fortsetter som normalt, men de gjenvÊrende brokerne mÄ hÄndtere en stÞrre belastning.
- Uventet opphÞr av to meglere i et kluster med tre meglere og min.isr = 2 fÞrer til at klusteret ikke er tilgjengelig for skriving, men er tilgjengelig for lesing. I tilfelle min.isr = 1, vil klusteret forbli tilgjengelig for bÄde lesing og skriving. Denne tilnÊrmingen strider imidlertid mot kravet til hÞy dataintegritet.
- Uventet opphĂžr av en av Zookeeper-serverne fĂžrer ikke til at klusteret stopper eller blir utilgjengelig. Driften fortsetter som normalt.
- Uventet opphÞr av to Zookeeper-servere fÞrer til at klusteret blir utilgjengelig til minst én av Zookeeper-serverne blir gjenopprettet. Denne pÄstanden er sann for et Zookeeper-kluster med 3 servere. Som et resultat av undersÞkelser har det blitt besluttet Ä Þke Zookeeper-klusteret til 5 servere for Ä forbedre feilmotstand.
Kafka som en tjeneste

Vi har bekreftet at Kafka er en utmerket teknologi som gjĂžr det mulig Ă„ lĂžse oppgaven vi hadde (implementering av en meldingsmegler). Likevel bestemte vi oss for Ă„ forby tjenestene Ă„ kontakte Kafka direkte, og lukket den bak en databus. Hvorfor gjorde vi dette? Det er faktisk flere grunner.
Databussen har tatt ansvar for alle oppgavene knyttet til integrering med Kafka (implementering og konfigurasjon av forbrukere og produsenter, overvÄking, varsling, logging, skalerbarhet osv.). Dermed skjer integrasjonen med meldingsmegleren pÄ en sÄ enkel mÄte som mulig.
Databussen har gjort det mulig Ä abstrahere fra spesifikke sprÄk eller biblioteker for Ä jobbe med Kafka.
Databussen har gjort det mulig for andre tjenester Ä abstrahere fra lagringslaget. Kanskje, pÄ et tidspunkt, vil vi bytte Kafka med Pulsar, og ingen vil merke noe (alle tjenester kjenner bare API-en til databussen).
Databussen har pÄtatt seg validering av hendelsesskjemaer.
Ved hjelp av databussen er autentisering implementert.
Under dekke av databussen kan vi oppdatere versjoner av Kafka uten nedetid, anonymt, sentralt administrere konfigurasjoner av produsenter, forbrukere, meglere osv.
Databussen har gjort det mulig Ä legge til nÞdvendige funksjoner som ikke finnes i Kafka (som auditing av emner, overvÄking av anomalier i klusteret, oppretting av DLQ osv.).
Databussen gjĂžr det mulig Ă„ implementere failover sentralt for alle tjenester.
For now, to start sending events to the message broker, it's enough to connect a small library to your service's code. That's all. You gain the ability to write, read, and scale with just a line of code. All implementation details are hidden from you; only a few parameters like batch size are exposed. Under the hood, the data-bus service spins up the necessary number of producer and consumer instances in Kubernetes and provides them with the required configuration, but all of this is transparent to your service.
Of course, there is no silver bullet, and this approach has its limitations.
- Data-bus needs to be maintained on your own, unlike third-party libraries.
- Data-bus increases the number of interactions between services and the message broker, which leads to reduced performance compared to raw Kafka.
- Not everything can be so easily hidden from services; duplicating KSQL or Kafka Streams functionality in data-bus is not what we want, so sometimes we have to allow services to access it directly.
In our case, the advantages outweighed the disadvantages, and the decision to shield the message broker with a separate service proved to be justified. In a year of operation, we have not encountered any serious outages or issues.
P.S. Thanks to my girlfriend, Ekaterina Obalayeva, for the cool illustrations for this article. If you liked them, there will be even more illustrations.
Kilde: habr.com
