
Hi sa lahat. Sa artikulong ito sasabihin ko sa iyo kung bakit pinili namin sa Avito ang Kafka siyam na buwan na ang nakakaraan at kung ano ito. Ibabahagi ko ang isa sa mga kaso ng paggamit - isang broker ng mensahe. At panghuli, pag-usapan natin kung anong mga pakinabang ang nakuha natin sa paggamit ng diskarte sa Kafka bilang Serbisyo.
problema

Una, isang maliit na konteksto. Ilang oras na ang nakalipas nagsimula kaming lumayo sa monolitikong arkitektura, at ngayon ay mayroon nang ilang daang iba't ibang serbisyo ang Avito. Mayroon silang sariling mga repositoryo, kanilang sariling stack ng teknolohiya at responsable para sa kanilang bahagi ng lohika ng negosyo.
Ang isa sa mga problema sa isang malaking bilang ng mga serbisyo ay komunikasyon. Madalas na gustong malaman ng Serbisyo A ang impormasyong mayroon ang Serbisyo B Sa kasong ito, ina-access ng Serbisyo A ang Serbisyo B sa pamamagitan ng isang kasabay na API. Gustong malaman ng Serbisyo B kung ano ang nangyayari sa mga serbisyong D at D, at sila naman, ay interesado sa mga serbisyong A at B. Kapag maraming ganoong "mausisa" na serbisyo, ang mga koneksyon sa pagitan ng mga ito ay nagiging gusot.
Kasabay nito, ang serbisyo A ay maaaring maging hindi magagamit anumang oras. At ano ang dapat gawin ng serbisyo B at lahat ng iba pang serbisyong konektado dito sa kasong ito? At kung ang isang kadena ng sunud-sunod na magkakasabay na mga tawag ay kinakailangan upang makumpleto ang isang operasyon ng negosyo, ang posibilidad ng pagkabigo ng buong operasyon ay nagiging mas mataas (at kung mas mahaba ang chain, mas mataas ito).
Pagpili ng teknolohiya

Okay, malinaw ang mga problema. Maaaring alisin ang mga ito sa pamamagitan ng paglikha ng isang sentralisadong sistema ng pagmemensahe sa pagitan ng mga serbisyo. Ngayon ang bawat isa sa mga serbisyo ay kailangan lamang malaman ang tungkol sa sistema ng pagmemensahe na ito. Bilang karagdagan, ang system mismo ay dapat na fault-tolerant at pahalang na nasusukat, at gayundin, sa kaganapan ng mga aksidente, makaipon ng access buffer para sa kasunod na pagproseso.
Piliin natin ngayon ang teknolohiya kung saan ipapatupad ang paghahatid ng mensahe. Para magawa ito, unawain muna natin kung ano ang inaasahan natin dito:
- ang mga mensahe sa pagitan ng mga serbisyo ay hindi dapat mawala;
- maaaring madoble ang mga mensahe;
- ang mga mensahe ay maaaring itago at basahin sa lalim ng ilang araw (persistent buffer);
- maaaring mag-subscribe ang mga serbisyo sa data na interesado sila;
- maraming mga serbisyo ay maaaring basahin ang parehong data;
- ang mga mensahe ay maaaring maglaman ng detalyado, malaking kargamento (paglipat ng estado na dala ng kaganapan);
- Minsan kailangan mong tiyakin ang pagkakasunud-sunod ng mga mensahe.
Napakahalaga din para sa amin na piliin ang pinakanasusukat at maaasahang system na may mataas na throughput (hindi bababa sa 100k mensahe ng ilang kilobytes bawat segundo).
Sa puntong ito, nagpaalam kami sa RabbitMQ (mahirap panatilihing matatag sa mataas na rps), PGQ mula sa SkyTools (hindi sapat na mabilis at hindi maganda ang sukat) at NSQ (hindi paulit-ulit). Ginagamit namin ang lahat ng teknolohiyang ito sa aming kumpanya, ngunit hindi angkop ang mga ito para sa problemang niresolba.
Susunod, nagsimula kaming tumingin sa mga teknolohiyang bago sa amin - Apache Kafka, Apache Pulsar at NATS Streaming.
Pulsar ang unang itinapon. Napagpasyahan namin na ang Kafka at Pulsar ay medyo magkatulad na mga solusyon. At sa kabila ng katotohanan na ang Pulsar ay nasubok ng malalaking kumpanya, ay mas bago at nag-aalok ng mas mababang latency (sa teorya), nagpasya kaming iwanan ang Kafka sa dalawang ito bilang de facto na pamantayan para sa mga naturang gawain. Malamang na babalik kami sa Apache Pulsar sa hinaharap.
At ngayon ay may dalawang kandidatong natitira: NATS Streaming at Apache Kafka. Pinag-aralan namin ang parehong mga solusyon sa ilang detalye, at pareho ang mga ito ay angkop para sa gawain. Ngunit sa huli, natatakot kami sa kamag-anak na kabataan ng NATS Streaming (at ang katotohanan na ang isa sa mga pangunahing developer, si Tyler Treat, ay nagpasya na umalis sa proyekto at magsimula ng kanyang sarili - Liftbridge). Kasabay nito, ang Clustering mode ng NATS Streaming ay hindi nagbigay ng posibilidad ng malakas na horizontal scaling (marahil hindi na ito problema pagkatapos ng pagdaragdag ng partitioning mode sa 2017).
Gayunpaman, ang NATS Streaming ay isang cool na teknolohiya na nakasulat sa Go at sinusuportahan ng Cloud Native Computing Foundation. Hindi tulad ng Apache Kafka, hindi nito kailangan ang Zookeeper upang gumana (marahil ), dahil ipinapatupad nito ang RAFT sa loob. Kasabay nito, mas madaling pangasiwaan ang NATS Streaming. Hindi namin isinasantabi na babalik kami sa teknolohiyang ito sa hinaharap.
Gayunpaman, ngayon ang aming nagwagi ay ang Apache Kafka. Sa aming mga pagsubok, napatunayang medyo mabilis ito (higit sa isang milyong mensahe bawat segundo para sa pagbabasa at pagsulat na may dami ng mensahe na 1 kilobyte), medyo maaasahan, lubos na nasusukat at napatunayan ng karanasan sa paggawa ng malalaking kumpanya. Bilang karagdagan, sinusuportahan ng Kafka ang hindi bababa sa ilang malalaking komersyal na kumpanya (kami, halimbawa, ay gumagamit ng Confluent na bersyon), at ang Kafka ay mayroon ding binuo na ecosystem.
Pangkalahatang-ideya ng Kafka
Bago tayo magsimula, nais kong agad na magrekomenda ng isang mahusay na libro - "Kafka: Ang Depinitibong Gabay" (Mayroon ding pagsasalin sa Ruso, ngunit ang mga termino ay medyo nakakagulat). Naglalaman ito ng impormasyong kailangan mo para magkaroon ng pangunahing pag-unawa sa Kafka at kahit kaunti pa. Ang dokumentasyon ng Apache at ang blog ng Confluent ay mahusay din ang pagkakasulat at madaling basahin.
Kaya tingnan natin kung paano gumagana ang Kafka. Ang pangunahing topology ng Kafka ay binubuo ng producer, consumer, broker at zookeeper.
Broker

Ang broker ay responsable para sa pag-iimbak ng iyong data. Ang lahat ng data ay naka-imbak sa binary form, at ang broker ay kaunti ang nalalaman tungkol sa kung ano sila at kung ano ang kanilang istraktura.
Ang bawat lohikal na uri ng kaganapan ay karaniwang matatagpuan sa sarili nitong hiwalay na paksa. Halimbawa, ang kaganapan ng paglikha ng ad ay maaaring mahulog sa item.created na paksa, at ang kaganapan ng pagbabago nito ay maaaring mahulog sa item.changed. Maaaring ituring ang mga paksa bilang mga klasipikasyon ng kaganapan. Sa antas ng paksa, maaari kang magtakda ng mga parameter ng configuration gaya ng:
- ang dami ng data na nakaimbak at/o ang edad nito (retention.bytes, retention.ms);
- data redundancy factor (pagtitiklop na kadahilanan);
- maximum na laki ng isang mensahe (max.message.bytes);
- ang pinakamababang bilang ng mga pare-parehong replika kung saan maaaring maisulat ang data sa isang paksa (min.insync.replicas);
- ang kakayahang magsagawa ng failover sa isang hindi kasabay na lagging replica na may potensyal na pagkawala ng data (unclean.leader.election.enable);
- at marami pang iba ().
Sa turn, ang bawat paksa ay nahahati sa isa o higit pang mga partisyon. Ito ay sa mga partido na ang mga kaganapan sa huli ay nahuhulog. Kung mayroong higit sa isang broker sa cluster, ang mga partisyon ay ipapamahagi nang pantay-pantay sa lahat ng mga broker (hangga't maaari), na magbibigay-daan sa pag-load sa pagsulat at pagbabasa sa isang paksa na mai-scale sa ilang mga broker nang sabay-sabay.
Sa disk, ang data para sa bawat partition ay nakaimbak sa anyo ng mga segment na file, bilang default na katumbas ng isang gigabyte (kinokontrol sa pamamagitan ng log.segment.bytes). Ang isang mahalagang tampok ay ang data ay tinanggal mula sa mga partisyon (kapag ang pagpapanatili ay na-trigger) sa mga segment (hindi mo maaaring tanggalin ang isang kaganapan mula sa isang partisyon, maaari mo lamang tanggalin ang isang buong segment, at ang hindi aktibo lamang).
zookeeper
Ang Zookeeper ay gumaganap bilang isang metadata store at coordinator. Siya ang makakapagsabi kung ang mga broker ay buhay (maaari mong tingnan ito sa pamamagitan ng mga mata ng zookeeper gamit ang zookeeper-shell na may utos ls /brokers/ids), aling broker ang controller (get /controller), kung ang mga partisyon ay naka-synchronize sa kanilang mga replika (get /brokers/topics/topic_name/partitions/partition_number/state). Gayundin, ito ay sa zookeeper na ang producer at mamimili ay unang pupunta upang malaman kung aling broker kung aling mga paksa at partisyon ang nakaimbak. Sa mga kaso kung saan ang isang replication factor na mas malaki sa 1 ay tinukoy para sa isang paksa, ang zookeeper ay magsasaad kung aling mga partisyon ang mga pinuno (sila ay susulatan at babasahin mula sa). Sa kaganapan ng pagkabigo ng broker, ang impormasyon tungkol sa mga bagong partition ng lider ay itatala sa zookeeper (mula sa bersyon 1.1.0 nang hindi sabaysabay, ).
Sa mga mas lumang bersyon ng Kafka, ang zookeeper ay responsable din sa pag-iimbak ng mga offset, ngunit ngayon sila ay naka-imbak sa isang espesyal na paksa __consumer_offsets sa broker (bagaman maaari mo pa ring gamitin ang zookeeper para sa mga layuning ito).
Ang pinakamadaling paraan upang gawing kalabasa ang iyong data ay ang mawalan ng impormasyon mula sa zookeeper. Sa ganitong senaryo, napakahirap na maunawaan kung ano ang babasahin at kung saan mula.
Tagagawa
Ang producer ay kadalasang isang serbisyo na direktang nagsusulat ng data sa Apache Kafka. Pinipili ng Producer ang isang paksa kung saan iimbak ang mga mensahe ng paksa nito at magsisimulang magsulat ng impormasyon dito. Halimbawa, ang producer ay maaaring isang serbisyo ng ad. Sa kasong ito, magpapadala ito ng mga kaganapan tulad ng "nagawa ang ad", "na-update ang ad", "tinanggal ang ad", atbp. sa mga paksang pampakay. Ang bawat kaganapan ay isang key-value pair.
Bilang default, ang lahat ng mga kaganapan ay ipinamamahagi sa mga partisyon ng paksa gamit ang round-robin kung ang susi ay hindi tinukoy (nawawala ang pag-order), at sa pamamagitan ng MurmurHash (key) kung ang susi ay naroroon (nag-order sa loob ng isang partisyon).
Dapat pansinin kaagad na ginagarantiyahan ng Kafka ang pagkakasunud-sunod ng mga kaganapan sa loob lamang ng isang batch. Ngunit sa katotohanan ito ay madalas na hindi isang problema. Halimbawa, maaari mong tiyakin na idagdag ang lahat ng mga pagbabago sa parehong deklarasyon sa isang partisyon (sa gayon ay pinapanatili ang pagkakasunud-sunod ng mga pagbabagong ito sa loob ng deklarasyon). Maaari ka ring magpadala ng sequence number sa isa sa mga field ng event.
Mamimili

Responsable ang consumer sa pagkuha ng data mula sa Apache Kafka. Kung babalik tayo sa halimbawa sa itaas, ang mamimili ay maaaring isang serbisyo sa pagmo-moderate. Isu-subscribe ang serbisyong ito sa paksa ng serbisyo ng ad, at kapag lumitaw ang isang bagong ad, matatanggap ito at susuriin ito para sa pagsunod sa ilang partikular na patakaran.
Naaalala ng Apache Kafka kung anong kamakailang mga kaganapan ang natanggap ng mamimili (isang paksa ng serbisyo ang ginagamit para dito __consumer__offsets), sa gayo'y tinitiyak na kung matagumpay ang nabasa, ang mamimili ay hindi makakatanggap ng parehong mensahe nang dalawang beses. Gayunpaman, kung gagamitin mo ang enable.auto.commit = true na opsyon at ganap na italaga ang gawain ng pagsubaybay sa posisyon ng consumer sa paksa sa Kafka, maaari mong . Sa code ng produksyon, kadalasan ang posisyon ng consumer ay kinokontrol nang manu-mano (kinokontrol ng developer ang sandali kung kailan dapat mangyari ang commit ng read event).
Sa mga kaso kung saan ang isang consumer ay hindi sapat (halimbawa, ang daloy ng mga bagong kaganapan ay napakalaki), maaari kang magdagdag ng higit pang mga consumer sa pamamagitan ng pag-link sa kanila sa isang pangkat ng consumer. Ang isang pangkat ng consumer ay lohikal na kapareho ng isang consumer, ngunit may data na ipinamahagi sa mga miyembro ng grupo. Nagbibigay-daan ito sa bawat kalahok na kunin ang kanilang bahagi ng mga mensahe, sa gayo'y nasusukat ang bilis ng pagbabasa.
Mga resulta ng pagsubok

Hindi ako magsusulat ng maraming explanatory text dito, ibabahagi ko lang ang mga resultang nakuha. Isinagawa ang pagsubok sa 3 pisikal na makina (12 CPU, 384GB RAM, 15k SAS DISK, 10GBit/s Net), ang mga broker at zookeeper ay na-deploy sa lxc.
Subukan ang performance
Sa panahon ng pagsubok, nakuha ang mga sumusunod na resulta.
- Ang bilis ng pag-record ng 1KB na mga mensahe nang sabay-sabay ng 9 na producer ay 1300000 na kaganapan kada segundo.
- Ang bilis ng pagbabasa ng 1KB na mga mensahe nang sabay-sabay ng 9 na mga mamimili ay 1500000 mga kaganapan sa bawat segundo.
Pagsubok sa pagpapahintulot sa kasalanan
Sa panahon ng pagsubok, nakuha ang mga sumusunod na resulta (3 broker, 3 zookeeper).
- Ang hindi normal na pagwawakas ng isa sa mga broker ay hindi nagiging sanhi ng paghinto o pagiging hindi available ng cluster. Nagpapatuloy ang trabaho bilang normal, ngunit ang natitirang mga broker ay may mabigat na workload.
- Ang hindi normal na pagwawakas ng dalawang broker sa kaso ng isang cluster ng tatlong broker at min.isr = 2 ay humahantong sa cluster na hindi magagamit para sa pagsusulat, ngunit naa-access para sa pagbabasa. Kung min.isr = 1, patuloy na magagamit ang cluster para sa parehong pagbabasa at pagsusulat. Gayunpaman, ang mode na ito ay sumasalungat sa kinakailangan para sa mataas na seguridad ng data.
- Ang hindi normal na pagsara ng isa sa mga server ng Zookeeper ay hindi nagiging sanhi ng paghinto o pagiging hindi available ng cluster. Nagpapatuloy ang trabaho bilang normal.
- Ang hindi normal na pag-shutdown ng dalawang server ng Zookeeper ay nagreresulta sa pagiging hindi available ng cluster hanggang sa maibalik ang kahit isa sa mga server ng Zookeeper. Ang pahayag na ito ay totoo para sa isang Zookeeper cluster ng 3 server. Bilang resulta, pagkatapos ng pananaliksik, napagpasyahan na dagdagan ang cluster ng Zookeeper sa 5 server upang mapataas ang fault tolerance.
Kafka bilang isang serbisyo

Kami ay kumbinsido na ang Kafka ay isang mahusay na teknolohiya na nagbibigay-daan sa amin upang malutas ang gawain na itinalaga sa amin (pagpapatupad ng isang broker ng mensahe). Gayunpaman, nagpasya kaming ipagbawal ang mga serbisyo sa direktang pag-access sa Kafka at isinara ito sa itaas gamit ang isang serbisyo ng data-bus. Bakit natin ginawa ito? Sa katunayan, may ilang mga dahilan.
Kinuha ng data-bus ang lahat ng mga gawain na may kaugnayan sa pagsasama sa Kafka (pagpapatupad at pagsasaayos ng mga consumer at producer, pagsubaybay, pag-alerto, pag-log, pag-scale, atbp.). Kaya, ang pagsasama sa broker ng mensahe ay kasing simple hangga't maaari.
Pinahintulutan kami ng data-bus na umalis sa isang partikular na wika o library para sa pakikipagtulungan sa Kafka.
Pinahintulutan ng data-bus ang ibang mga serbisyo na alisin ang layer ng storage. Marahil sa isang punto ay papalitan natin ang Kafka sa Pulsar, at walang makakapansin ng anuman (lahat ng mga serbisyo ay alam lamang ang tungkol sa data-bus API).
Kinuha ng data-bus ang pagpapatunay ng mga schema ng kaganapan.
Ang pagpapatunay ay ipinatupad gamit ang data-bus.
Sa ilalim ng pabalat ng data-bus, maaari naming tahimik na i-update ang mga bersyon ng Kafka nang walang downtime, sentral na pamahalaan ang mga configuration ng mga producer, consumer, broker, atbp.
Pinayagan kami ng data-bus na magdagdag ng mga feature na kailangan namin na wala sa Kafka (gaya ng mga paksa sa pag-audit, mga anomalya sa pagsubaybay sa cluster, paggawa ng DLQ, atbp.).
Binibigyang-daan ka ng data-bus na ipatupad ang failover sa gitna para sa lahat ng mga serbisyo.
Sa ngayon, para simulan ang pagpapadala ng mga event sa message broker, kailangan mo lang magkonekta ng maliit na library sa iyong service code. Ito lang. May kakayahan kang magsulat, magbasa at mag-scale gamit ang isang linya ng code. Ang buong pagpapatupad ay nakatago mula sa iyo, na may lamang ng ilang batch size handles lumalabas. Sa ilalim ng hood, pinapataas ng serbisyo ng data-bus ang kinakailangang bilang ng mga producer at consumer instance sa Kubernetes at nagbibigay sa kanila ng kinakailangang configuration, ngunit ang lahat ng ito ay malinaw sa iyong serbisyo.
Siyempre, walang pilak na bala, at ang diskarte na ito ay may mga limitasyon.
- Ang data-bus ay kailangang suportahan sa loob ng bahay, kumpara sa mga third-party na library.
- Pinapataas ng data-bus ang bilang ng mga pakikipag-ugnayan sa pagitan ng mga serbisyo at ng broker ng mensahe, na nagreresulta sa mas mababang pagganap kumpara sa walang laman na Kafka.
- Hindi lahat ay maitatago sa mga serbisyo nang napakadali; hindi namin gustong i-duplicate ang functionality ng KSQL o Kafka Stream sa data-bus, kaya minsan kailangan naming payagan ang mga serbisyo na direktang pumunta.
Sa aming kaso, ang mga kalamangan ay lumampas sa mga kahinaan, at ang desisyon na sakupin ang broker ng mensahe gamit ang isang hiwalay na serbisyo ay nabigyang-katwiran. Sa taon ng operasyon wala kaming anumang malalang aksidente o problema.
PS Salamat sa aking kasintahan, si Ekaterina Obalyaeva, para sa mga cool na larawan para sa artikulong ito. Kung nagustuhan mo sila, may darating pang mga ilustrasyon.
Pinagmulan: www.habr.com
