
Tere kõigile. Selles artiklis räägin, miks me Avitos valisime Kafka dev olla kuus kuud tagasi ja mida see endast kujutab. Jagame ühte kasutusjuhtu — sõnumite vahetaja. Lõpuks räägime, milliseid eeliseid oleme saanud Kafka as a Service lähenemisest.
Probleem

Alustuseks natuke konteksti. Mõni aeg tagasi hakkasime loobuma monoliitsest arhitektuurist ja nüüd on Avitos juba mitu sada erinevat teenust. Neil on oma andmehoidlad, oma tehnoloogia stack ja nad vastutavad oma osa äriloogika eest.
Üks probleem suure arvu teenustega on suhtlemine. Teenus A soovib sageli teada teavet, mis teenusel B on. Sel juhul pöördub teenus A teenuse B poole sünkrone API kaudu. Teenus B tahab teada, mis toimub teenustes G ja D, ning need, omakorda, huvituvad teenustest A ja B. Kui selliseid "uudishimulikke" teenuseid on palju, muutuvad sidemed nende vahel segaseks klambriks.
Sel juhul võib teenus A igal hetkel olla kättesaamatu. Mida teha teenusele B ja kõigile teistele selle teenuse otseselt sõltuvatele teenustele? Kui äriprotsessi jaoks on vajalik järjestikuste sünkroonsete kutsungite ahel, suureneb kogu operatsiooni nurjumise tõenäosus veelgi (ja mida pikem on see ahel, seda suurem on tõenäosus).
Tehnoloogia valik

Olgu, probleemid on selged. Need on likvideeritavad, luues teenuste vahel keskustavate sõnumiteži. Nüüd piisab, kui iga teenus tunneb ainult selle sõnumite süsteemi. Lisaks peab see süsteem olema talitlushäirekindel ja horisontaalselt skaleeritav, ning hädaolukordade korral peab see koguma enda sisse puhvri edasi töötlemiseks.
Valime nüüd tehnoloogia, millel sõnumite edastamine põhineb. Selleks peame esmalt aru saama, mida me temalt ootame:
- teenuste vahel ei tohi sõnumid kaduma minna;
- sõnumid võivad duplitseerida;
- sõnumid võivad olla salvestatud ja loetavad mitu päeva (püsiv puhver);
- teenused saavad tellida huvitavaid andmeid;
- mitmed teenused saavad lugeda samu andmeid;
- teated võivad sisaldada põhjalikku, mahukat payload'i (event-carried state transfer);
- mõnikord on vajalik sõnumite järjekorra tagamine.
Samuti oli meil kriitiliselt oluline valida maksimaalselt skaleeritav ja usaldusväärne süsteem kõrge läbilaskevõimega (vähemalt 100k sõnumit mõne kilobaidi sekundis).
Selles etapis jätsime hüvasti RabbitMQ'ga (raskesti stabiilsena hoida kõrgetel rps-del), PGQ'ga SkyTools'ist (liialt aeglane ja halvasti skaleeritav) ning NSQ'ga (mitte püsiv). Kõiki neid tehnoloogiaid kasutatakse meie firmas, kuid antud ülesande jaoks nad ei sobinud.
Edasi hakkasime vaatama meie jaoks uusi tehnoloogiaid — Apache Kafka, Apache Pulsar ja NATS Streaming.
Esimese asjana loobusime Pulsarist. Otsustasime, et Kafka ja Pulsar on omavahel üsna sarnased lahendused. Ja hoolimata sellest, et Pulsar on suurtelt ettevõtetelt testitud, on see uuem ja pakub teoreetiliselt madalamat latentsust, otsustasime nendest kahest jätta Kafka, kui de facto standard nende ülesannete jaoks. Tõenäoliselt naaseme Apache Pulsari juurde tulevikus.
Ja jäid alles kaks kandidaati: NATS Streaming ja Apache Kafka. Oleme mõlemat lahendust küllalt põhjalikult uurinud ja mõlemad sobisid meie vajadustele. Kuid lõpuks kartisime NATS Streaming'i suhtelist nooruslikkust (ning seda, et üks peamistest arendajatest, Tyler Treat, otsustas projektist lahkuda ja alustada oma ettevõtet — Liftbridge). Samuti ei pakkunud NATS Streaming'i rühmitamise režiim head horisontaalset skaleeritavust (tõenäoliselt pole see enam probleem, kuna partitioning režiim lisati 2017. aastal).
Sellegipoolest on NATS Streaming erakordne tehnoloogia, mis on kirjutatud Go keeles ja mida toetab Cloud Native Computing Foundation. Erinevalt Apache Kafka'st ei vajata selle tööks Zookeeper'it (võib-olla, ), kuna see rakendab sisemiselt RAFT'i. NATS Streaming on samas lihtsam haldamisel. Me ei välista, et tulevikus tuleme sellele tehnoloogiale veel tagasi.
Ja, ja, ja, aga tänaseks päevaks on meie võitja Apache Kafka. Meie testides näitas see end piisavalt kiirelt (üle miljoni sõnumi sekundis lugemise ja kirjutamise puhul, sõnumite suuruseks 1 kilobait), piisavalt usaldusväärselt, hästi skaleeritavalt ja suurte ettevõtete kogemusega tõestatud. Lisaks toetab Kafka vähemalt mitmeid suuri kaubanduslikke ettevõtteid (me kasutame näiteks Confluent versiooni), samuti on Kafka'l välja arendatud ökosüsteem.
Kafka ülevaade
Enne kui alustada, soovitan kohe suurepärast raamatut — „Kafka: The Definitive Guide“ (tõlge on saadaval, aga terminid võivad natuke keeruliseks osutuda). Siit leiate teavet, mis on vajalik Kafka põhitõdede mõistmiseks ja isegi rohkem. Apache'i enda dokumentatsioon ja Confluenti blogi on samuti suurepäraselt kirjutatud ja kergesti loetavad.
Nii et vaatame, kuidas Kafka linnulennult välja näeb. Kafka põhistruktuur koosneb tootjatest (producer), tarbijatest (consumer), vahendajatest (broker) ja zookeeperist.
Vahendaja (Broker)

Teie andmete säilitamise eest vastutab vahendaja (broker). Kõik andmed salvestatakse binaarses vormingus ning vahendaja ei tea nende struktuurist ega sisust suurt midagi.
Iga sündmuste loogiline tüüp asub tavaliselt oma eraldi teemas (topic). Näiteks võib kuulutuse loomise sündmus kuuluda teema item.created alla, muutmise sündmus aga item.changed alla. Teemasid võib käsitleda sündmuste klassifikaatoritena. Teema tasemel saab määrata selliseid konfiguratsiooniparameetreid nagu:
- salvestatava andmemaht ja/või nende vanus (retention.bytes, retention.ms);
- andmete redundantssuse faktor (replication factor);
- ühe sõnumi maksimaalne suurus (max.message.bytes);
- minimaalne arvu nõutud koopiaid, mille juures saab teemasse andmeid kirjutada (min.insync.replicas);
- võimalus viia läbi failover mitte-sünkroonsele mahajäänud koopiale, millega võib kaasneda andmete kadu (unclean.leader.election.enable);
- ja veel palju muud ().
Igal juhul jaguneb iga teema üheks või mitmeks partitsiooniks (partition). Just partitsioonidesse jõuavad lõpuks sündmused. Kui klastris on rohkem kui üks maakler, jaotatakse partitsioonid kõikide maaklerite vahel ühtlaselt (kui see on võimalik), mis võimaldab koormust kirjutamisel ja lugemisel ühte teema korraga jagada mitme maakleri vahel.
Ketaste andmed iga partitsiooni kohta säilitatakse failide segmentidena, mille vaikimisi suurus on üks gigabait (kontrollitakse läbi log.segment.bytes). Oluline omadus on see, et andmete eemaldamine partitsioonidest (retention'i toimimisel) toimub just segmentide kaupa (ei saa eemaldada ühte sündmust partitsioonist, saab eemaldada ainult kogu segmenti, kuid ainult mitteaktiivset).
Zookeeper
Zookeeper toimib metaandmete ladustamise ja koordinaatorina. Just tema suudab öelda, kas maaklerid on elus (seda saab vaadata zookeeper'i kaudu, kasutades käsku ls /brokers/ids), milline maakler on kontroller (get /controller), kas partitsioonid on oma koopiatega sünkroonis (get /brokers/topics/topic_name/partitions/partition_number/state). Samuti lähevad tootjad ja tarbijad esmalt zookeeper'i, et teada saada, millisel maakleril millised teemad ja partitsioonid on. Juhul kui teema jaoks on määratud replikatsiooni tegur suurem kui 1, näitab zookeeper, millised partitsioonid on liidrid (sellesse kirjutatakse ja sealt loetakse). Kui maakler kukub, kantakse zookeeper'i kirja teave uute liidri-partitsioonide kohta (alates versioonist 1.1.0 asünkroonselt, ).
Vanemates versioonides vastutas Kafka zookeeper ka ofsetite hoidmise eest, kuid nüüd säilitatakse need eraldi teemas. __consumer_offsets brokeris (kuigi saate nende eesmärkide jaoks endiselt zookeeperit kasutada).
Lihtsaim viis oma andmed kõrvitsaks muuta on kaotada teave zookeeperiga. Sellises stsenaariumis on väga raske mõista, mida ja kust lugeda.
Producer
Producer on enamasti teenus, mis kirjutab andmeid otse Apache Kafka-sse. Producer valib teema, kuhu tal on plaanis salvestada oma teemakohased sõnumid, ja hakkab sinna teavet sisestama. Näiteks võib producer olla reklaamiteenus, mis saadab teemadesse selliseid sündmusi nagu „reklaam loodud“, „reklaam uuendatud“, „reklaam kustutatud“ jne. Iga sündmus esindab võtme-väärtuse paari.
Vaikimisi jaotatakse kõik sündmused teema vahel round-robin meetodil, kui võtme ei ole määratud (kaotades järjestuse), ja MurmurHashi kaudu (võtme) korral, kui võtme olemasolu on. (järjestus ühe partitsiooni raames).
Siinkohal tasub märkida, et Kafka tagab sündmuste järjekorra ainult ühe partitsiooni raames. Kuid tegelikult ei ole see sageli probleem. Näiteks saab kõik sama kuulutuse muudatused järjekindlalt ühte partitsiooni lisada (hoides seeläbi nende muudatuste järjekorda kuulutuse raames). Samuti saab sündmuste ühes väljas edastada järjestusnumber.
Consumer

Consumer vastutab andmete vastuvõtmise eest Apache Kafka'st. Kui naasta eelneva näite juurde, võib consumer olla modereerimisteenus. See teenus registreerib end kuulutuste teenuse teemale, ja kui ilmub uus kuulutus, saab see selle ja analüüsib selle vastavust teatud määratud poliitikatele.
Apache Kafka salvestab, millised olid viimased sündmused, mille consumer sai (selleks kasutatakse teenusteemade teenust __consumer__offsets), tagades sellega, et pärast eduka lugemise korral ei saa consumer sama sõnumit kaks korda. Siiski, kui kasutada suvandit enable.auto.commit = true ja täielikult usaldada consumer'i positsiooni jälgimist teemal Kafkele, võib . Tootmisprotsessis juhitakse tarbija asukohta enamasti käsitsi (arendaja haldab hetke, mil kirjedatud sündmus peab toimuma).
Situatsioonides, kus üks tarbija ei piisa (näiteks kui uusi sündmusi saabub väga palju), on võimalik lisada veel mitu tarbijat, sidudes need tarbijagruppi. Tarbijagrupp esindab loogiliselt sama tarbijat, kuid andmete jaotamisega grupi liikmete vahel. See võimaldab igal liikmel võtta oma osa sõnumitest, skaleerides seeläbi lugemise kiirus.
Testimise tulemused

Siin ei hakka ma palju selgitusi andma, jagan lihtsalt saadud tulemusi. Testimine viidi läbi 3 füüsilisel masinal (12 CPU, 384GB RAM, 15k SAS DISK, 10GBit/s Net), vahendajad ja zookeeper olid juurutatud lxc-is.
Jõudluse testimine
Testimise käigus saadi järgmised tulemused.
- 1KB suuruste sõnumite kirjutamise kiirus 9 tootjaga — 1300000 sündmust sekundis.
- 1KB suuruste sõnumite lugemise kiirus 9 tarbijaga — 1500000 sündmust sekundis.
Kätkestustaluvuse testimine
Testimise käigus saadi järgmised tulemused (3 vahendajat, 3 zookeeperi).
- Üksikute brokerite ootamatu sulgemine ei põhjusta klastrite seiskumist või kättesaamatust. Töö jätkub tavapäraselt, kuid ülejäänud brokeritele lasub suurem koormus.
- Kaks brokerit ootamatu sulgemise korral kolmest brokerist klastris ja min.isr = 2 toob kaasa klastrite kirjutusvõime kättesaamatuse, kuid lugemise kättesaadavuse. Kui min.isr = 1, jääb klaster kättesaadavaks nii lugemiseks kui ka kirjutamiseks. Siiski on see režiim vastuolus kõrge andmekaitse nõudega.
- Üksikute Zookeeperi serverite ootamatu sulgemine ei põhjusta klastrite seiskumist ega kättesaamatust. Töö jätkub tavapäraselt.
- Kaks Zookeeperi serveri ootamatut sulgemist toob kaasa klastrite kättesaamatuse kuni vähemalt ühe Zookeeperi serveri töö taastamiseni. See väide kehtib kolme serveriga Zookeeperi klastrite kohta. Uuringute tulemusena otsustati Zookeeperi klastrit suurendada viie serverini, et suurendada talitlushäiret taluvust.
Kafka as a service

Oleme veendunud, et Kafka on suurepärane tehnoloogia, mis võimaldab lahendada meie ülesande (sõnumite vahendaja rakendamine). Siiski otsustasime keelata teenustel Kafka otsene kasutamine ja sulgesime selle data-bus teenuse taha. Miks me seda tegime? Tegelikult on mitmeid põhjuseid.
Data-bus on enda peale võtnud kõik Kafka integreerimisega seotud ülesanded (tarvitsiate ja tootjate rakendamine ja seadistamine, jälgimine, häired, logimine, skaleerimine jne). Seega toimub sõnumivahendajaga integreerimine võimalikult lihtsalt.
Data-bus võimaldas abstrakteerida välja konkreetse keele või teegi, millega Kafka töötada.
Data-bus võimaldas teistel teenustel abstrakteerida salvestuskiht. Võib-olla vahetame kunagi Kafka Pulsari vastu, ja keegi ei märkagi (kõik teenused tunnevad ainult data-bus API-d).
Data-bus võttis enda peale sündmuste skeemide valideerimise.
Data-bus abil on rakendatud autentimine.
Data-busi kaudu saame ilma seisakuteta ja märkamatult uuendada Kafka versioone, hallata keskselt tootjate, tarvitsiate, vahendajate jm konfiguratsioone.
Data-bus võimaldab lisada vajalikud funktsioonid, mida Kafka ei paku (näiteks teema auditeerimine, anomaaliate jälgimine klastris, DLQ loomine jne).
Data-bus võimaldab tsentraliseeritult rakendada kõigi teenuste failover'it.
Praegu piisab, kui ühendate oma teenuse koodi väikese teegiga, et alustada sündmuste saatmist sõnumibrokerisse. Ja see on kõik. Teil on võimalus kirjutada, lugeda ja skaleerida ühe koodireaga. Terve teostus on teie eest varjatud, väljapoole paisatakse vaid mõned käsud, näiteks partii suurus. Tagaplaanil tõstab teenus data-bus Kuberneteses vajalikud producer'ite ja consumer'ite instantsid ning varustab need vajaliku konfigureerimisega, kuid kõik see on teie teenuse jaoks läbipaistev.
Muidugi, hõbedaseid kuule ei ole ja sellel lähenemisel on omad piirangud.
- Data-bus'i peate ise toetama, erinevalt kolmandate osapoolte teekidest.
- Data-bus suurendab teenuste ja sõnumibrokeri vaheliste interaktsioonide arvu, mis toob kaasa madalama jõudluse võrreldes puhta Kafka'ga.
- Kõike ei saa teenustelt nii lihtsalt peita, me ei sooviks KSQL või Kafka Streams funktsionaalsust data-bus'is dubleerida, seega vahel on kohustus lubada teenustel otse ühendust võtta.
Meie puhul kaaluvad plussid rohkem kui miinused ja otsus sulgeda sõnumite vahendaja eraldi teenusena oli õigustatud. Ühe aasta jooksul pole meil olnud tõsiseid õnnetusi ega probleeme.
P.S. Aitäh mu tüdruksõbrale, Jekaterina Obalajevale, artikli ägedate piltide eest. Kui need teile meeldivad, leidub veel rohkem illustratsioone.
Allikas: habr.com
