Si u bë realitet Kafka

Si u bë realitet Kafka

Hej Habr!

Unë punoj në ekipin Tinkoff, i cili po zhvillon qendrën e vet të njoftimeve. Unë kryesisht zhvilloj në Java duke përdorur Spring boot dhe zgjidh probleme të ndryshme teknike që dalin në një projekt.

Shumica e mikroshërbimeve tona komunikojnë me njëri-tjetrin në mënyrë asinkrone përmes një ndërmjetësi mesazhesh. Më parë, ne përdorëm IBM MQ si ndërmjetës, i cili nuk mund të përballonte më ngarkesën, por në të njëjtën kohë kishte garanci të larta dërgese.

Si zëvendësim, na u ofrua Apache Kafka, e cila ka një potencial të lartë shkallëzimi, por, për fat të keq, kërkon një qasje pothuajse individuale të konfigurimit për skenarë të ndryshëm. Përveç kësaj, mekanizmi i dorëzimit të paktën një herë që funksionon në Kafka si parazgjedhje nuk lejonte ruajtjen e nivelit të kërkuar të konsistencës jashtë kutisë. Më pas, unë do të ndaj përvojën tonë në konfigurimin e Kafka-s, në veçanti, do t'ju tregoj se si të konfiguroni dhe jetoni me dorëzimin saktësisht një herë.

Dërgesë e garantuar dhe më shumë

Cilësimet e diskutuara më poshtë do të ndihmojnë në parandalimin e një numri problemesh me cilësimet e paracaktuar të lidhjes. Por së pari do të doja t'i kushtoja vëmendje një parametri që do të lehtësojë një korrigjim të mundshëm.

Kjo do të ndihmojë klienti.id për Prodhuesin dhe Konsumatorin. Në shikim të parë, mund të përdorni emrin e aplikacionit si vlerë, dhe në shumicën e rasteve kjo do të funksionojë. Edhe pse situata kur një aplikacion përdor disa Konsumatorë dhe ju u jepni të njëjtin klient.id, rezulton në paralajmërimin e mëposhtëm:

org.apache.kafka.common.utils.AppInfoParser — Error registering AppInfo mbean javax.management.InstanceAlreadyExistsException: kafka.consumer:type=app-info,id=kafka.test-0

Nëse dëshironi të përdorni JMX në një aplikacion me Kafka, atëherë ky mund të jetë një problem. Për këtë rast, është mirë të përdorni një kombinim të emrit të aplikacionit dhe, për shembull, emrit të temës si vlerë klient.id. Rezultati i konfigurimit tonë mund të shihet në daljen e komandës kafka-grupet e konsumatorëve nga shërbimet nga Confluent:

Si u bë realitet Kafka

Tani le të shohim skenarin për dërgimin e garantuar të mesazhit. Kafka Producer ka një parametër qafat, e cila ju lejon të konfiguroni pas sa pranimesh i duhen udhëheqësit të grupit për të marrë parasysh mesazhin e shkruar me sukses. Ky parametër mund të marrë vlerat e mëposhtme:

  • 0 - pranoj nuk do të merret parasysh.
  • 1 është parametri i paracaktuar, kërkohet vetëm 1 kopje për të pranuar.
  • −1 - kërkohet njohja nga të gjitha kopjet e sinkronizuara (konfigurimi i grupit min.insync.kopje).

Nga vlerat e listuara është e qartë se acks e barabartë me -1 jep garancinë më të fortë se mesazhi nuk do të humbasë.

Siç e dimë të gjithë, sistemet e shpërndara janë jo të besueshme. Për t'u mbrojtur nga defektet kalimtare, Kafka Producer ofron opsionin riprovon, e cila ju lejon të caktoni numrin e përpjekjeve të ridërgimit brenda dorëzimi.koha.ms. Meqenëse parametri i riprovave ka një vlerë të paracaktuar prej Integer.MAX_VALUE (2147483647), numri i riprovave të mesazhit mund të rregullohet duke ndryshuar vetëm delivery.timeout.ms.

Ne po shkojmë drejt dorëzimit saktësisht një herë

Cilësimet e listuara lejojnë Prodhuesin tonë të dërgojë mesazhe me një garanci të lartë. Le të flasim tani se si të sigurohemi që vetëm një kopje e një mesazhi të shkruhet në një temë të Kafkës? Në rastin më të thjeshtë, për ta bërë këtë, duhet të vendosni parametrin në Producer mundësoj.idempotencë të vërtetë. Idempotenca garanton që vetëm një mesazh është shkruar në një ndarje specifike të një teme. Parakushti për të mundësuar idempotencën janë vlerat acks = të gjitha, riprovo > 0, max.in.flight.requests.per.connection ≤ 5. Nëse këto parametra nuk janë specifikuar nga zhvilluesi, vlerat e mësipërme do të vendosen automatikisht.

Kur konfigurohet idempotenca, është e nevojshme të sigurohet që të njëjtat mesazhe të përfundojnë në të njëjtat ndarje çdo herë. Kjo mund të bëhet duke vendosur çelësin dhe parametrin partitioner.class në Producer. Le të fillojmë me çelësin. Duhet të jetë i njëjtë për çdo paraqitje. Kjo mund të arrihet lehtësisht duke përdorur ndonjë nga ID-të e biznesit nga postimi origjinal. Parametri partitioner.class ka një vlerë të paracaktuar − Ndarësi i paracaktuar. Me këtë strategji ndarjeje, si parazgjedhje ne veprojmë kështu:

  • Nëse ndarja është e specifikuar në mënyrë eksplicite gjatë dërgimit të mesazhit, atëherë ne e përdorim atë.
  • Nëse ndarja nuk është e specifikuar, por çelësi është specifikuar, zgjidhni ndarjen sipas hash-it të çelësit.
  • Nëse ndarja dhe çelësi nuk janë specifikuar, zgjidhni ndarjet një nga një (round-robin).

Gjithashtu, duke përdorur një çelës dhe dërgim idempotent me një parametër max.in.flight.kërkesat.për.lidhje = 1 ju jep përpunimin e thjeshtë të mesazheve tek Konsumatori. Vlen gjithashtu të mbani mend se nëse kontrolli i aksesit është konfiguruar në grupin tuaj, atëherë do t'ju nevojiten të drejta për të shkruar në mënyrë të pafuqishme në një temë.

Nëse papritur ju mungojnë aftësitë e dërgimit të pafuqishëm me çelës ose logjika nga ana e Prodhuesit kërkon ruajtjen e konsistencës së të dhënave midis ndarjeve të ndryshme, atëherë transaksionet do të vijnë në shpëtim. Përveç kësaj, duke përdorur një transaksion zinxhir, mund të sinkronizoni me kusht një rekord në Kafka, për shembull, me një rekord në bazën e të dhënave. Për të mundësuar dërgimin transaksional te Prodhuesi, ai duhet të jetë i pafuqishëm dhe i vendosur shtesë transaksionale.id. Nëse grupi juaj Kafka ka konfiguruar kontrollin e aksesit, atëherë një rekord transaksioni, si një rekord idempotent, do të ketë nevojë për leje shkrimi, të cilat mund të jepen me maskë duke përdorur vlerën e ruajtur në transaksional.id.

Formalisht, çdo varg, siç është emri i aplikacionit, mund të përdoret si një identifikues transaksioni. Por nëse lëshoni disa raste të të njëjtit aplikacion me të njëjtin transaksional.id, atëherë shembulli i parë i lëshuar do të ndalet me një gabim, pasi Kafka do ta konsiderojë atë një proces zombie.

org.apache.kafka.common.errors.ProducerFencedException: Producer attempted an operation with an old epoch. Either there is a newer producer with the same transactionalId, or the producer's transaction has been expired by the broker.

Për të zgjidhur këtë problem, emrit të aplikacionit i shtojmë një prapashtesë në formën e emrit të hostit, të cilin e marrim nga variablat e mjedisit.

Prodhuesi është i konfiguruar, por transaksionet në Kafka kontrollojnë vetëm shtrirjen e mesazhit. Pavarësisht nga statusi i transaksionit, mesazhi shkon menjëherë në temë, por ka atribute shtesë të sistemit.

Për të parandaluar leximin e mesazheve të tilla nga Konsumatori para kohe, ai duhet të vendosë parametrin izolim.niveli për vlerën read_committed. Një Konsumator i tillë do të jetë në gjendje të lexojë mesazhe jo-transaksionale si më parë, dhe mesazhe transaksionale vetëm pas një angazhimi.
Nëse keni vendosur të gjitha cilësimet e listuara më herët, atëherë keni konfiguruar saktësisht një herë dorëzimin. urime!

Por ka edhe një nuancë tjetër. Transactional.id, të cilin e konfiguruam më sipër, është në fakt prefiksi i transaksionit. Në menaxherin e transaksionit, atij i shtohet një numër sekuence. Identifikuesi i marrë i lëshohet transaksionale.id.skadimi.ms, i cili është konfiguruar në një grup Kafka dhe ka një vlerë të paracaktuar prej "7 ditë". Nëse gjatë kësaj kohe aplikacioni nuk ka marrë asnjë mesazh, atëherë kur të provoni dërgimin tjetër transaksional do të merrni InvalidPidMappingException. Koordinatori i transaksionit më pas do të lëshojë një numër të ri të sekuencës për transaksionin tjetër. Megjithatë, mesazhi mund të humbet nëse InvalidPidMappingException nuk trajtohet si duhet.

Në vend të totals

Siç mund ta shihni, nuk mjafton thjesht t'i dërgoni mesazhe Kafkës. Ju duhet të zgjidhni një kombinim të parametrave dhe të jeni të përgatitur për të bërë ndryshime të shpejta. Në këtë artikull, u përpoqa të tregoja në detaje konfigurimin e dorëzimit saktësisht një herë dhe përshkrova disa probleme me konfigurimet klient.id dhe transaksional.id që hasëm. Më poshtë është një përmbledhje e cilësimeve të Prodhuesit dhe Konsumatorit.

Producer:

  1. acks = të gjithë
  2. përsërit > 0
  3. mundësoj.idempotencë = e vërtetë
  4. max.in.flight. requests.per.connection ≤ 5 (1 për dërgim të rregullt)
  5. transaksional.id = ${application-name}-${hostname}

Konsumatori:

  1. izolim.nivel = lexuar_kommituar

Për të minimizuar gabimet në aplikacionet e ardhshme, ne bëmë mbështjellësin tonë mbi konfigurimin e pranverës, ku vlerat për disa nga parametrat e listuar janë vendosur tashmë.

Këtu janë disa materiale për vetë-studim:

Burimi: www.habr.com

Shto një koment