Përvojë në zhvillimin e shërbimit të mjetit të rimbursimit me një API asinkron në Kafka

Çfarë mund ta detyrojë një kompani kaq të madhe si Lamoda, me një proces të efektshëm dhe dhjetëra shërbime të ndërlidhura, të ndryshojë ndjeshëm qasjen e saj? Motivimi mund të jetë krejtësisht i ndryshëm: nga legjislativi tek dëshira për të eksperimentuar, e natyrshme në të gjithë programuesit.

Por kjo nuk do të thotë që nuk mund të mbështeteni në përfitime shtesë. Sergey Zaika do t'ju tregojë se çfarë saktësisht mund të fitoni nëse zbatoni API-në e drejtuar nga ngjarjet në Kafka (pak). Gjithashtu do të flitet patjetër për të shtëna të mëdha dhe zbulime interesante - eksperimenti nuk mund të bëjë pa to.

Përvojë në zhvillimin e shërbimit të mjetit të rimbursimit me një API asinkron në Kafka

Mohim përgjegjësie: Ky artikull bazohet në materialet nga një takim që Sergey mbajti në nëntor 2018 në HighLoad++. Përvoja e drejtpërdrejtë e Lamodës për të punuar me Kafkën tërhoqi dëgjuesit jo më pak se raportet e tjera në orar. Ne mendojmë se ky është një shembull i shkëlqyer i faktit se ju mund dhe duhet të gjeni gjithmonë njerëz me të njëjtin mendim, dhe organizatorët e HighLoad++ do të vazhdojnë të përpiqen të krijojnë një atmosferë të favorshme për këtë.

Rreth procesit

Lamoda është një platformë e madhe e-commerce që ka qendrën e saj të kontaktit, shërbimin e dorëzimit (dhe shumë filiale), një studio fotografike, një depo të madhe dhe e gjithë kjo funksionon në softuerin e vet. Ka dhjetëra mënyra pagese, partnerë b2b që mund të përdorin disa ose të gjitha këto shërbime dhe duan të dinë informacione të përditësuara për produktet e tyre. Përveç kësaj, Lamoda operon në tre vende përveç Federatës Ruse dhe gjithçka është pak më ndryshe atje. Në total, ka ndoshta më shumë se njëqind mënyra për të konfiguruar një porosi të re, e cila duhet të përpunohet në mënyrën e vet. E gjithë kjo funksionon me ndihmën e dhjetëra shërbimeve që ndonjëherë komunikojnë në mënyra jo të dukshme. Ekziston gjithashtu një sistem qendror përgjegjësia kryesore e të cilit është statusi i porosive. Ne e quajmë BOB, unë punoj me të.

Mjeti i rimbursimit me API të drejtuar nga ngjarjet

Fjala e drejtuar nga ngjarjet është mjaft e çuditshme; pak më tej do të përcaktojmë më në detaje se çfarë nënkuptohet me këtë. Do të filloj me kontekstin në të cilin vendosëm të provojmë qasjen API të drejtuar nga ngjarjet në Kafka.

Përvojë në zhvillimin e shërbimit të mjetit të rimbursimit me një API asinkron në Kafka

Në çdo dyqan, përveç porosive për të cilat klientët paguajnë, ka raste kur dyqanit i kërkohet të kthejë para për shkak se produkti nuk i përshtatej klientit. Ky është një proces relativisht i shkurtër: ne sqarojmë informacionin, nëse është e nevojshme, dhe transferojmë paratë.

Por kthimi u bë më i ndërlikuar për shkak të ndryshimeve në legjislacion dhe ne duhej të zbatonim një mikroshërbim të veçantë për të.

Përvojë në zhvillimin e shërbimit të mjetit të rimbursimit me një API asinkron në Kafka

Motivimi ynë:

  1. Ligji FZ-54 - me pak fjalë, ligji kërkon raportimin në zyrën e taksave për çdo transaksion monetar, qoftë kthim apo faturë, brenda një SLA mjaft të shkurtër prej disa minutash. Ne, si një kompani e-commerce, kryejmë mjaft operacione. Teknikisht, kjo do të thotë përgjegjësi e re (dhe për rrjedhojë një shërbim i ri) dhe përmirësime në të gjitha sistemet e përfshira.
  2. Ndarja BOB është një projekt i brendshëm i kompanisë për të çliruar BOB nga një numër i madh i përgjegjësive jo thelbësore dhe për të zvogëluar kompleksitetin e tij të përgjithshëm.

Përvojë në zhvillimin e shërbimit të mjetit të rimbursimit me një API asinkron në Kafka

Ky diagram tregon sistemet kryesore Lamoda. Tani shumica e tyre janë më shumë një plejadë prej 5-10 mikroshërbimesh rreth një monoliti në tkurrje. Ata po rriten ngadalë, por ne po përpiqemi t'i bëjmë më të vogla, sepse vendosja e fragmentit të përzgjedhur në mes është e frikshme - nuk mund të lejojmë që të bjerë. Ne jemi të detyruar të rezervojmë të gjitha shkëmbimet (shigjetat) dhe të marrim parasysh faktin që ndonjë prej tyre mund të rezultojë i padisponueshëm.

BOB gjithashtu ka mjaft shkëmbime: sistemet e pagesave, sistemet e dërgesave, sistemet e njoftimit, etj.

Teknikisht BOB është:

  • ~ 150 mijë rreshta kodi + ~ 100 mijë rreshta testesh;
  • php7.2 + Zend 1 & Komponentët Symfony 3;
  • >100 API & ~50 integrime të jashtme;
  • 4 vende me logjikën e tyre të biznesit.

Vendosja e BOB është e shtrenjtë dhe e dhimbshme, sasia e kodit dhe e problemeve që zgjidh është e tillë që askush nuk mund t'i vendosë të gjitha në kokë. Në përgjithësi, ka shumë arsye për ta thjeshtuar atë.

Procesi i Kthimit

Fillimisht, dy sisteme përfshihen në proces: BOB dhe Pagesa. Tani shfaqen edhe dy të tjera:

  • Shërbimi i Fiskalizimit, i cili do të kujdeset për problemet me fiskalizimin dhe komunikimin me shërbimet e jashtme.
  • Mjeti i rimbursimit, i cili thjesht përmban shkëmbime të reja në mënyrë që të mos fryjë BOB.

Tani procesi duket si ky:

Përvojë në zhvillimin e shërbimit të mjetit të rimbursimit me një API asinkron në Kafka

  1. BOB merr një kërkesë për një rimbursim.
  2. BOB flet për këtë mjet rimbursimi.
  3. Mjeti i Rimbursimit i thotë Pagesës: "Ktheji paratë".
  4. Pagesa i kthen paratë.
  5. Mjeti i rimbursimit dhe BOB sinkronizojnë statuset me njëri-tjetrin, sepse tani për tani ata të dy kanë nevojë për të. Ne nuk jemi ende gati të kalojmë plotësisht në Mjetin e Rimbursimit, pasi BOB ka një UI, raporte për kontabilitet dhe në përgjithësi shumë të dhëna që nuk mund të transferohen kaq lehtë. Ju duhet të uleni në dy karrige.
  6. Kërkesa për fiskalim ikën.

Si rezultat, ne bëmë një lloj autobusi ngjarjesh në Kafka - ngjarje-autobus, mbi të cilin filloi gjithçka. Hurra, tani kemi një pikë të vetme dështimi (sarkazëm).

Përvojë në zhvillimin e shërbimit të mjetit të rimbursimit me një API asinkron në Kafka

Të mirat dhe të këqijat janë mjaft të dukshme. Ne bëmë një autobus, që do të thotë se tani të gjitha shërbimet varen prej tij. Kjo thjeshton dizajnin, por fut një pikë të vetme dështimi në sistem. Kafka do të rrëzohet, procesi do të ndalet.

Çfarë është një API e drejtuar nga ngjarjet

Një përgjigje e mirë për këtë pyetje është në raportin e Martin Fowler (GOTO 2017) "Shumë kuptime të arkitekturës së drejtuar nga ngjarjet".

Shkurtimisht çfarë bëmë:

  1. Përfundoni të gjitha shkëmbimet asinkrone nëpërmjet ruajtja e ngjarjeve. Në vend që të informojmë çdo konsumator të interesuar për një ndryshim të statusit në rrjet, ne shkruajmë një ngjarje në lidhje me një ndryshim statusi në një ruajtje të centralizuar dhe konsumatorët e interesuar për këtë temë lexojnë gjithçka që shfaqet nga atje.
  2. Ngjarja në këtë rast është një njoftim (Njoftimet) që diçka ka ndryshuar diku. Për shembull, statusi i porosisë ka ndryshuar. Një konsumator që është i interesuar për disa të dhëna që shoqërojnë ndryshimin e statusit që nuk përfshihen në njoftim, mund ta zbulojë vetë statusin e tij.
  3. Opsioni maksimal është burimi i plotë i ngjarjeve, transferimi i shtetit, në të cilën ngjarje përmban të gjithë informacionin e nevojshëm për përpunim: nga erdhi dhe në çfarë statusi shkuan, si ndryshuan saktësisht të dhënat, etj. Pyetja e vetme është fizibiliteti dhe sasia e informacionit që mund të përballoni për të ruajtur.

Si pjesë e lançimit të Mjetit të Rimbursimit, ne përdorëm opsionin e tretë. Ky përpunim i thjeshtuar i ngjarjeve pasi nuk kishte nevojë për nxjerrjen e informacionit të detajuar, plus eliminoi skenarin ku çdo ngjarje e re gjeneron një shpërthim kërkesash për të marrë sqarime nga konsumatorët.

Shërbimi i veglave të rimbursimit jo i ngarkuar, kështu që Kafka ka më shumë një shije të stilolapsit sesa një domosdoshmëri. Nuk mendoj se nëse shërbimi i rimbursimit do të bëhej një projekt me ngarkesë të lartë, biznesi do të ishte i lumtur.

Shkëmbimi asinkron SI ËSHTË

Për shkëmbimet asinkrone, departamenti PHP zakonisht përdor RabbitMQ. Ne mblodhëm të dhënat për kërkesën, i vendosëm në radhë dhe konsumatori i të njëjtit shërbim i lexoi dhe e dërgoi (ose nuk e dërgoi). Për vetë API-në, Lamoda përdor në mënyrë aktive Swagger. Ne hartojmë një API, e përshkruajmë atë në Swagger dhe gjenerojmë kodin e klientit dhe serverit. Ne përdorim gjithashtu një JSON RPC 2.0 pak të përmirësuar.

Në disa vende përdoren autobusë ESB, disa jetojnë në activeMQ, por, në përgjithësi, RabbitMQ - standard.

Shkëmbimi asinkron TO BE

Kur dizajnoni shkëmbimin përmes ngjarjeve-autobus, mund të gjurmohet një analogji. Ne përshkruajmë në mënyrë të ngjashme shkëmbimin e të dhënave të ardhshme përmes përshkrimeve të strukturës së ngjarjeve. Formati yaml, ne duhej ta bënim vetë gjenerimin e kodit, gjeneratori krijon DTO sipas specifikimeve dhe i mëson klientët dhe serverët të punojnë me ta. Brezi shkon në dy gjuhë - golang dhe php. Kjo ndihmon për të mbajtur bibliotekat konsistente. Gjeneratori është i shkruar në golang, prandaj ka marrë emrin gogi.

Burimi i ngjarjeve në Kafka është një gjë tipike. Ekziston një zgjidhje nga versioni kryesor i ndërmarrjes i Kafka Confluent, ekziston nakadi, një zgjidhje nga vëllezërit tanë të domenit Zalando. Tona motivim për të filluar me vaniljen Kafka - kjo do të thotë ta lëmë zgjidhjen të lirë derisa të vendosim përfundimisht nëse do ta përdorim atë kudo, dhe gjithashtu t'i lëmë vetes hapësirë ​​për manovrim dhe përmirësime: ne duam mbështetje për JSON RPC 2.0, gjeneratorë për dy gjuhë dhe le të shohim se çfarë tjetër.

Është ironike që edhe në një rast kaq të lumtur, kur ka një biznes afërsisht të ngjashëm, Zalando, që ka bërë një zgjidhje afërsisht të ngjashme, ne nuk mund ta përdorim me efektivitet.

Modeli arkitektonik në nisje është si vijon: ne lexojmë drejtpërdrejt nga Kafka, por shkruajmë vetëm përmes ngjarjeve-autobus. Ka shumë gjëra gati për të lexuar në Kafka: ndërmjetës, balancues dhe është pak a shumë gati për shkallëzim horizontal, doja ta mbaja këtë. Ne donim ta përfundonim regjistrimin përmes një autobusi Gateway të njohur si Events, dhe ja pse.

Ngjarje-autobus

Ose një autobus ngjarjesh. Kjo është thjesht një portë http pa shtetësi, e cila merr disa role të rëndësishme:

  • Prodhimi i Validimit — ne kontrollojmë që ngjarjet plotësojnë specifikimet tona.
  • Sistemi kryesor i ngjarjeve, pra ky është sistemi kryesor dhe i vetëm në kompani që i përgjigjet pyetjes se cilat ngjarje me cilat struktura konsiderohen të vlefshme. Vërtetimi thjesht përfshin llojet e të dhënave dhe numrat për të specifikuar rreptësisht përmbajtjen.
  • Funksioni hash për ndarjen - struktura e mesazhit të Kafkës është vlera kryesore dhe duke përdorur hash-in e çelësit llogaritet se ku duhet vendosur.

Pse

Ne punojmë në një kompani të madhe me një proces të thjeshtë. Pse të ndryshoni ndonjë gjë? Ky është një eksperiment, dhe ne presim të korrim disa përfitime.

1:n+1 shkëmbime (një në shumë)

Kafka e bën shumë të lehtë lidhjen e konsumatorëve të rinj me API.

Le të themi se keni një direktori që duhet ta mbani të përditësuar në disa sisteme njëherësh (dhe në disa të reja). Më parë, ne shpikëm një paketë që zbatonte set-API, dhe sistemi kryesor informohej për adresat e konsumatorëve. Tani sistemi master dërgon përditësime për temën dhe të gjithë ata që janë të interesuar e lexojnë atë. Është shfaqur një sistem i ri - ne e nënshkruam atë për temën. Po, edhe pako, por më e thjeshtë.

Në rastin e mjetit të rimbursimit, i cili është një pjesë BOB, është e përshtatshme për ne t'i mbajmë ato të sinkronizuara përmes Kafkës. Pagesa thotë se paratë janë kthyer: BOB, RT e kanë marrë vesh këtë, kanë ndryshuar statuset e tyre, Shërbimi i Fiskalizimit ka marrë vesh për këtë dhe ka lëshuar një çek.

Përvojë në zhvillimin e shërbimit të mjetit të rimbursimit me një API asinkron në Kafka

Ne kemi plane për të krijuar një shërbim të unifikuar të njoftimeve që do të njoftojë klientin për lajmet në lidhje me porosinë/kthimet e tij. Tani kjo përgjegjësi është e shpërndarë ndërmjet sistemeve. Do të mjaftojë që ne të mësojmë Shërbimin e Njoftimeve që të kap informacionin përkatës nga Kafka dhe t'i përgjigjet atij (dhe t'i çaktivizojë këto njoftime në sisteme të tjera). Nuk do të kërkohen shkëmbime të reja të drejtpërdrejta.

Të drejtuara nga të dhënat

Informacioni ndërmjet sistemeve bëhet transparent - pa marrë parasysh se çfarë "ndërmarrjeje të përgjakshme" keni dhe pa marrë parasysh se sa e pasur është numri juaj i mbetur. Lamoda ka një departament të Analitikës së të Dhënave që mbledh të dhëna nga sistemet dhe i vendos ato në një formë të ripërdorshme, si për biznesin ashtu edhe për sistemet inteligjente. Kafka ju lejon t'u jepni atyre shpejt shumë të dhëna dhe ta mbani atë rrjedhën e informacionit të përditësuar.

Regjistri i përsëritjes

Mesazhet nuk zhduken pasi lexohen, si në RabbitMQ. Kur një ngjarje përmban informacion të mjaftueshëm për përpunim, ne kemi një histori të ndryshimeve të fundit në objekt dhe, nëse dëshirohet, aftësinë për të zbatuar këto ndryshime.

Periudha e ruajtjes së regjistrit të replikimit varet nga intensiteti i shkrimit në këtë temë; Kafka ju lejon të vendosni në mënyrë fleksibël kufijtë në kohën e ruajtjes dhe vëllimin e të dhënave. Për tema intensive, është e rëndësishme që të gjithë konsumatorët të kenë kohë për të lexuar informacionin përpara se ai të zhduket, edhe në rastin e mosfunksionimit afatshkurtër. Zakonisht është e mundur të ruhen të dhëna për njësi ditësh, e cila është mjaft e mjaftueshme për mbështetje.

Përvojë në zhvillimin e shërbimit të mjetit të rimbursimit me një API asinkron në Kafka

Më pas, pak ritregim të dokumentacionit, për ata që nuk e njohin Kafkën (foto është edhe nga dokumentacioni)

AMQP ka radhë: ne shkruajmë mesazhe në një radhë për konsumatorin. Në mënyrë tipike, një radhë përpunohet nga një sistem me të njëjtën logjikë biznesi. Nëse keni nevojë të njoftoni disa sisteme, mund ta mësoni aplikacionin të shkruajë në disa radhë ose të konfiguroni shkëmbimin me mekanizmin fanout, i cili i klonon ato vetë.

Kafka ka një abstraksion të ngjashëm temë, në të cilat shkruani mesazhe, por ato nuk zhduken pas leximit. Si parazgjedhje, kur lidheni me Kafka, i merrni të gjitha mesazhet dhe keni opsionin për të ruajtur aty ku e keni lënë. Domethënë, ju lexoni në mënyrë sekuenciale, ju nuk mund ta shënoni mesazhin si të lexuar, por ruani ID-në nga e cila më pas mund të vazhdoni të lexoni. ID-ja që keni vendosur quhet offset dhe mekanizmi është commit offset.

Prandaj, logjika e ndryshme mund të zbatohet. Për shembull, ne kemi BOB në 4 raste për vende të ndryshme - Lamoda është në Rusi, Kazakistan, Ukrainë, Bjellorusi. Meqenëse ato janë vendosur veçmas, ato kanë konfigurime paksa të ndryshme dhe logjikën e tyre të biznesit. Ne tregojmë në mesazh se cilit shtet i referohet. Çdo konsumator BOB në secilin vend lexon me një grup ID të ndryshëm dhe nëse mesazhi nuk vlen për ta, ata e kalojnë atë, d.m.th. menjëherë kryen kompensimin +1. Nëse e njëjta temë lexohet nga Shërbimi ynë i Pagesave, atëherë ai e bën këtë me një grup të veçantë, dhe për këtë arsye kompensimet nuk kryqëzohen.

Kërkesat e ngjarjes:

  • Plotësia e të dhënave. Do të doja që ngjarja të kishte të dhëna të mjaftueshme që të mund të përpunohej.

  • Integriteti. Ne i delegojmë Events-bus verifikimin që ngjarja është e qëndrueshme dhe se mund ta përpunojë atë.
  • Rendi është i rëndësishëm. Në rast kthimi, ne jemi të detyruar të punojmë me historinë. Me njoftimet, porosia nuk është e rëndësishme, nëse janë njoftime homogjene, emaili do të jetë i njëjtë pavarësisht se cila porosi ka ardhur e para. Në rastin e rimbursimit, ekziston një proces i qartë; nëse ndryshojmë rendin, do të lindin përjashtime, rimbursimi nuk do të krijohet ose përpunohet - do të përfundojmë në një status tjetër.
  • Konsistenca. Ne kemi një dyqan dhe tani krijojmë ngjarje në vend të një API. Ne kemi nevojë për një mënyrë për të transmetuar shpejt dhe lirë informacionin në lidhje me ngjarjet e reja dhe ndryshimet në ato ekzistuese në shërbimet tona. Kjo arrihet përmes një specifikimi të përbashkët në një depo të veçantë git dhe gjeneratorë kodesh. Prandaj, klientët dhe serverët në shërbime të ndryshme janë të koordinuara.

Kafka në Lamoda

Ne kemi tre instalime të Kafkës:

  1. Regjistrat;
  2. R&D;
  3. Ngjarje-autobus.

Sot po flasim vetëm për pikën e fundit. Në ngjarje-autobus, ne nuk kemi instalime shumë të mëdha - 3 ndërmjetës (server) dhe vetëm 27 tema. Si rregull, një temë është një proces. Por kjo është një pikë delikate, dhe ne do ta prekim tani.

Përvojë në zhvillimin e shërbimit të mjetit të rimbursimit me një API asinkron në Kafka

Më sipër është grafiku rps. Procesi i rimbursimit shënohet me një vijë bruz (po, ajo në boshtin X), dhe vija rozë është procesi i përditësimit të përmbajtjes.

Katalogu Lamoda përmban miliona produkte dhe të dhënat përditësohen gjatë gjithë kohës. Disa koleksione dalin nga moda, të reja dalin për t'i zëvendësuar dhe modele të reja shfaqen vazhdimisht në katalog. Ne përpiqemi të parashikojmë se çfarë do të jetë interesante për klientët tanë nesër, kështu që vazhdimisht blejmë gjëra të reja, i fotografojmë dhe përditësojmë vitrinën.

Majat rozë janë përditësime të produkteve, domethënë ndryshime në produkte. Mund të shihet se djemtë fotografuan, fotografuan dhe pastaj përsëri! — ngarkoi një paketë ngjarjesh.

Rastet e përdorimit të Lamoda Events

Ne përdorim arkitekturën e ndërtuar për operacionet e mëposhtme:

  • Gjurmimi i statusit të kthimit: thirrje për veprim dhe ndjekja e statusit nga të gjitha sistemet e përfshira. Pagesa, statuset, fiskalizimi, njoftimet. Këtu ne testuam qasjen, bëmë mjete, mblodhëm të gjitha gabimet, shkruam dokumentacionin dhe u thamë kolegëve tanë se si ta përdornin atë.
  • Përditësimi i kartave të produktit: konfigurimi, meta-të dhënat, karakteristikat. Një sistem lexon (i cili shfaq), dhe disa shkruajnë.
  • Email, push dhe sms: porosia është mbledhur, porosia ka ardhur, kthimi është pranuar etj., ka shumë.
  • Stoku, rinovimi i magazinës — përditësimi sasior i artikujve, vetëm numrat: mbërritja në magazinë, kthimi. Është e nevojshme që të gjitha sistemet që lidhen me rezervimin e mallrave të funksionojnë me të dhënat më aktuale. Aktualisht, sistemi i përditësimit të aksioneve është mjaft kompleks; Kafka do ta thjeshtojë atë.
  • Analiza e të dhënave (departamenti i R&D), mjetet e ML, analitika, statistikat. Ne duam që informacioni të jetë transparent - Kafka është i përshtatshëm për këtë.

Tani pjesa më interesante për gungat e mëdha dhe zbulimet interesante që kanë ndodhur gjatë gjashtë muajve të fundit.

Problemet e projektimit

Le të themi se duam të bëjmë një gjë të re - për shembull, transferojmë të gjithë procesin e dorëzimit te Kafka. Tani një pjesë e procesit zbatohet në Përpunimin e Porosisë në BOB. Ekziston një model statusi pas transferimit të një porosie në shërbimin e dorëzimit, lëvizjes në një magazinë të ndërmjetme, e kështu me radhë. Ekziston një monolit i tërë, madje dy, plus një grup API të dedikuara për ofrimin. Ata dinë shumë më tepër për shpërndarjen.

Këto duket se janë zona të ngjashme, por Përpunimi i Porosisë në BOB dhe Sistemi i Transportit kanë statuse të ndryshme. Për shembull, disa shërbime korriere nuk dërgojnë statuse të ndërmjetme, por vetëm ato përfundimtare: "të dorëzuara" ose "të humbura". Të tjerët, përkundrazi, raportojnë me shumë detaje për lëvizjen e mallrave. Secili ka rregullat e veta të vlefshmërisë: për disa, emaili është i vlefshëm, që do të thotë se do të përpunohet; per te tjeret nuk vlen por porosia do te perpunohet gjithsesi sepse ka nje numer telefoni per kontakt dhe dikush do te thote qe nje porosi e tille nuk do te perpunohet fare.

Rrjedha e të dhënave

Në rastin e Kafkës, shtrohet çështja e organizimit të rrjedhës së të dhënave. Kjo detyrë përfshin zgjedhjen e një strategjie të bazuar në disa pika; le t'i kalojmë të gjitha.

Në një temë apo në të ndryshme?

Ne kemi një specifikim të ngjarjes. Në BOB ne shkruajmë se një porosi e tillë duhet të dorëzohet dhe tregojmë: numrin e porosisë, përbërjen e tij, disa SKU dhe barkode, etj. Kur mallrat të mbërrijnë në magazinë, dërgesa do të jetë në gjendje të marrë statuset, vulat kohore dhe gjithçka që nevojitet. Por më pas duam të marrim përditësime për këto të dhëna në BOB. Ne kemi një proces të kundërt të marrjes së të dhënave nga dorëzimi. A është kjo e njëjta ngjarje? Apo është ky një shkëmbim i veçantë që meriton temën e vet?

Me shumë mundësi, ato do të jenë shumë të ngjashme, dhe tundimi për të bërë një temë nuk është i pabazuar, sepse një temë e veçantë do të thotë konsumatorë të veçantë, konfigurime të veçanta, një gjenerim i veçantë i gjithë kësaj. Por jo një fakt.

Fushë e re apo ngjarje e re?

Por nëse përdorni të njëjtat ngjarje, atëherë lind një problem tjetër. Për shembull, jo të gjitha sistemet e shpërndarjes mund të gjenerojnë atë lloj DTO që mund të gjenerojë BOB. Ne u dërgojmë ID-në, por ata nuk e ruajnë sepse nuk kanë nevojë dhe nga pikëpamja e fillimit të procesit event-bus, kjo fushë kërkohet.

Nëse prezantojmë një rregull për autobusin e ngjarjeve që kjo fushë kërkohet, atëherë ne jemi të detyruar të vendosim rregulla shtesë të vlefshmërisë në BOB ose në mbajtësin e ngjarjeve fillestare. Vleresimi fillon të përhapet në të gjithë shërbimin - kjo nuk është shumë e përshtatshme.

Një problem tjetër është tundimi për zhvillim në rritje. Na thonë se ngjarjes duhet t'i shtohet diçka dhe ndoshta, nëse e mendojmë, duhet të ishte një ngjarje më vete. Por në skemën tonë, një ngjarje më vete është një temë më vete. Një temë më vete është i gjithë procesi që përshkrova më lart. Zhvilluesi tundohet të shtojë një fushë tjetër në skemën JSON dhe ta rigjenerojë atë.

Në rastin e rimbursimeve, ne arritëm në ngjarjen e ngjarjeve në gjysmë viti. Ne kishim një meta-ngjarje të quajtur përditësim i rimbursimit, i cili kishte një fushë tipi që përshkruante se çfarë ishte në të vërtetë ky përditësim. Për shkak të kësaj, ne patëm ndërprerës "të mrekullueshëm" me verifikuesit që na thanë se si ta vërtetonim këtë ngjarje me këtë lloj.

Versionimi i ngjarjes

Për të vërtetuar mesazhet në Kafka mund të përdorni Avro, por ishte e nevojshme që menjëherë të shtrihej mbi të dhe të përdorej Confluent. Në rastin tonë, duhet të jemi të kujdesshëm me versionin. Nuk do të jetë gjithmonë e mundur të rilexohen mesazhet nga regjistri i replikimit sepse modeli është "larguar". Në thelb, rezulton të ndërtohen versione në mënyrë që modeli të jetë i përputhshëm: për shembull, bëni një fushë përkohësisht opsionale. Nëse dallimet janë shumë të forta, ne fillojmë të shkruajmë në një temë të re dhe transferojmë klientët kur të mbarojnë së lexuari të vjetër.

Rendi i garantuar i leximit të ndarjeve

Temat brenda Kafkës ndahen në ndarje. Kjo nuk është shumë e rëndësishme ndërsa ne jemi duke projektuar entitete dhe shkëmbime, por është e rëndësishme kur vendosim se si ta konsumojmë dhe shkallëzojmë atë.

Në rastin e zakonshëm, ju shkruani një temë në Kafka. Si parazgjedhje, përdoret një ndarje dhe të gjitha mesazhet në këtë temë shkojnë tek ajo. Dhe konsumatori rrjedhimisht i lexon këto mesazhe në mënyrë sekuenciale. Le të themi se tani duhet të zgjerojmë sistemin në mënyrë që mesazhet të lexohen nga dy konsumatorë të ndryshëm. Nëse, për shembull, po dërgoni SMS, atëherë mund t'i thoni Kafkës të bëjë një ndarje shtesë dhe Kafka do të fillojë t'i ndajë mesazhet në dy pjesë - gjysma këtu, gjysma këtu.

Si i ndan Kafka? Çdo mesazh ka një trup (në të cilin ne ruajmë JSON) dhe një çelës. Ju mund t'i bashkëngjitni një funksion hash këtij çelësi, i cili do të përcaktojë se në cilën ndarje do të shkojë mesazhi.

Në rastin tonë me rimbursimet, kjo është e rëndësishme, nëse marrim dy ndarje, atëherë ekziston mundësia që një konsumator paralel të përpunojë ngjarjen e dytë përpara të parës dhe të ketë probleme. Funksioni hash siguron që mesazhet me të njëjtin çelës të përfundojnë në të njëjtën ndarje.

Ngjarjet vs komandat

Ky është një problem tjetër që kemi hasur. Ngjarja është një ngjarje e caktuar: themi se diçka ka ndodhur diku (diçka_ka ndodhur), për shembull, një artikull është anuluar ose ka ndodhur një rimbursim. Nëse dikush i dëgjon këto ngjarje, atëherë sipas "artikull i anuluar", do të krijohet entiteti i rimbursimit dhe "ka ndodhur rimbursimi" do të shkruhet diku në konfigurimet.

Por zakonisht, kur hartoni ngjarje, nuk doni t'i shkruani ato kot - ju mbështeteni në faktin se dikush do t'i lexojë ato. Ekziston një tundim i madh për të shkruar jo diçka_ka ndodhur (artikull_anuluar, rimbursim_rimbursuar), por diçka_duhet_bëhet. Për shembull, artikulli është gati për t'u kthyer.

Nga njëra anë, sugjeron se si do të përdoret ngjarja. Nga ana tjetër, tingëllon shumë më pak si një emër i zakonshëm i ngjarjes. Për më tepër, nuk është larg nga këtu te komanda do_something. Por ju nuk keni garanci që dikush ta lexojë këtë ngjarje; dhe nëse e lexon, atëherë e lexon me sukses; dhe nëse e lexon me sukses, atëherë ke bërë diçka dhe ajo diçka ka qenë e suksesshme. Në momentin që një ngjarje bëhet do_something, reagimi bëhet i nevojshëm dhe ky është një problem.

Përvojë në zhvillimin e shërbimit të mjetit të rimbursimit me një API asinkron në Kafka

Në shkëmbimin asinkron në RabbitMQ, kur lexoni mesazhin, shkoni në http, keni një përgjigje - të paktën që mesazhi është marrë. Kur i shkruan Kafkës, ka një mesazh që i ke shkruar Kafkës, por nuk di asgjë se si është përpunuar.

Prandaj, në rastin tonë, ne duhej të fusnim një ngjarje përgjigjeje dhe të vendosnim monitorimin, në mënyrë që nëse dërgoheshin kaq shumë ngjarje, pas një kohe të tillë të mbërrinte i njëjti numër ngjarjesh përgjigjeje. Nëse kjo nuk ndodh, atëherë duket se diçka nuk ka shkuar keq. Për shembull, nëse kemi dërguar ngjarjen "item_ready_to_refund", presim që të krijohet një rimbursim, paratë do t'i kthehen klientit dhe ngjarja "money_refunded" do të na dërgohet. Por kjo nuk është e sigurt, ndaj nevojitet monitorim.

nuanca

Ekziston një problem mjaft i dukshëm: nëse lexoni nga një temë në mënyrë sekuenciale dhe keni një mesazh të keq, konsumatori do të bjerë dhe ju nuk do të shkoni më tej. Ju duhet ndaloni të gjithë konsumatorët, angazhohu më tej për të vazhduar leximin.

Ne e dinim për këtë, ne llogaritëm në të, por megjithatë ndodhi. Dhe kjo ndodhi sepse ngjarja ishte e vlefshme nga pikëpamja e ngjarjeve-bus, ngjarja ishte e vlefshme nga pikëpamja e validatorit të aplikacionit, por nuk ishte e vlefshme nga pikëpamja e PostgreSQL, sepse në sistemin tonë MySQL me INT UNISIGNED sistemi kishte PostgreSQL vetëm me INT. Madhësia e tij është pak më e vogël dhe ID-ja nuk përshtatej. Symfony vdiq me një përjashtim. Ne, natyrisht, e kapëm përjashtimin sepse u mbështetëm në të dhe do ta kryenim këtë kompensim, por para kësaj donim të rrisnim numëruesin e problemit, pasi mesazhi u përpunua pa sukses. Numëruesit në këtë projekt janë gjithashtu në bazën e të dhënave, dhe Symfony tashmë ka mbyllur komunikimin me bazën e të dhënave, dhe përjashtimi i dytë vrau të gjithë procesin pa pasur mundësi për të kryer kompensimin.

Shërbimi u shtri për ca kohë - për fat të mirë, me Kafkën kjo nuk është aq e keqe, sepse mesazhet mbeten. Kur puna të rikthehet, mund të përfundoni leximin e tyre. Është komode.

Kafka ka aftësinë të vendosë një kompensim arbitrar përmes veglave. Por për ta bërë këtë, ju duhet të ndaloni të gjithë konsumatorët - në rastin tonë, përgatitni një version të veçantë në të cilin nuk do të ketë konsumatorë, rishpërndarje. Pastaj në Kafka mund të zhvendosni kompensimin përmes veglave, dhe mesazhi do të kalojë.

Një nuancë tjetër - replikimi log vs rdkafka.so - lidhet me specifikat e projektit tonë. Ne përdorim PHP, dhe në PHP, si rregull, të gjitha bibliotekat komunikojnë me Kafkën përmes depove rdkafka.so, dhe më pas ka një lloj mbështjellësi. Ndoshta këto janë vështirësitë tona personale, por doli që thjesht të rilexosh një pjesë të asaj që kishim lexuar tashmë nuk është aq e lehtë. Në përgjithësi, kishte probleme me softuerin.

Duke iu rikthyer specifikave të punës me ndarje, shkruhet pikërisht në dokumentacion konsumatorët >= ndarje teme. Por e mora vesh këtë shumë më vonë se sa do të kisha dashur. Nëse dëshironi të shkallëzoni dhe të keni dy konsumatorë, ju nevojiten të paktën dy ndarje. Kjo do të thotë, nëse keni pasur një ndarje në të cilën janë grumbulluar 20 mijë mesazhe dhe keni bërë një të re, numri i mesazheve nuk do të barazohet së shpejti. Prandaj, për të pasur dy konsumatorë paralelë, duhet të merreni me ndarje.

Monitorimi

Mendoj se mënyra se si ne e monitorojmë do të jetë edhe më e qartë se çfarë problemesh ka në qasjen ekzistuese.

Për shembull, ne llogarisim se sa produkte në bazën e të dhënave kanë ndryshuar kohët e fundit statusin e tyre dhe, në përputhje me rrethanat, ngjarjet duhet të kishin ndodhur në bazë të këtyre ndryshimeve, dhe ne e dërgojmë këtë numër në sistemin tonë të monitorimit. Pastaj nga Kafka marrim numrin e dytë, sa ngjarje janë regjistruar në të vërtetë. Natyrisht, ndryshimi midis këtyre dy numrave duhet të jetë gjithmonë zero.

Përvojë në zhvillimin e shërbimit të mjetit të rimbursimit me një API asinkron në Kafka

Përveç kësaj, ju duhet të monitoroni se si po vepron prodhuesi, nëse ngjarjet-autobusi ka marrë mesazhe dhe si po bën konsumatori. Për shembull, në grafikët e mëposhtëm, Mjeti i Rimbursimit po funksionon mirë, por BOB ka qartë disa probleme (majat blu).

Përvojë në zhvillimin e shërbimit të mjetit të rimbursimit me një API asinkron në Kafka

Unë përmenda tashmë vonesën e grupit të konsumatorëve. Përafërsisht, ky është numri i mesazheve të palexuara. Në përgjithësi, konsumatorët tanë punojnë shpejt, kështu që vonesa është zakonisht 0, por ndonjëherë mund të ketë një kulm afatshkurtër. Kafka mund ta bëjë këtë jashtë kutisë, por ju duhet të vendosni një interval të caktuar.

Ka një projekt strofulle cila do t'ju japë më shumë informacion mbi Kafkën. Ai thjesht përdor API-në e grupit të konsumatorëve për të dhënë statusin se si po funksionon ky grup. Përveç "OK" dhe "Dështoi", ekziston një paralajmërim dhe mund të zbuloni se konsumatorët tuaj nuk mund të përballojnë ritmin e prodhimit - ata nuk kanë kohë të korrigjojnë atë që është shkruar. Sistemi është mjaft i zgjuar dhe i lehtë për t'u përdorur.

Përvojë në zhvillimin e shërbimit të mjetit të rimbursimit me një API asinkron në Kafka

Kështu duket përgjigja e API-së. Këtu është grupi bob-live-fifa, particioni refund.update.v1, statusi OK, vonesa 0 - kompensimi i fundit përfundimtar i tillë dhe i tillë.

Përvojë në zhvillimin e shërbimit të mjetit të rimbursimit me një API asinkron në Kafka

Monitorimi updated_at SLA (i mbërthyer) E përmenda tashmë. Për shembull, produkti ka ndryshuar në statusin se është gati për kthim. Ne instalojmë Cron, i cili thotë se nëse brenda 5 minutash ky objekt nuk ka shkuar për rimbursim (ne i kthejmë paratë përmes sistemeve të pagesave shumë shpejt), atëherë patjetër që diçka nuk shkoi mirë, dhe kjo është padyshim një rast për mbështetje. Prandaj, ne thjesht marrim Cron, i cili lexon gjëra të tilla, dhe nëse ato janë më të mëdha se 0, atëherë ai dërgon një alarm.

Për ta përmbledhur, përdorimi i ngjarjeve është i përshtatshëm kur:

  • informacioni është i nevojshëm nga disa sisteme;
  • rezultati i përpunimit nuk është i rëndësishëm;
  • ka pak ngjarje apo ngjarje të vogla.

Duket se artikulli ka një temë shumë specifike - API asinkron në Kafka, por në lidhje me të do të doja të rekomandoja shumë gjëra menjëherë.
Së pari, më pas Ngarkesë e lartë ++ duhet të presim deri në nëntor; në prill do të ketë një version të Shën Petersburgut, dhe në qershor do të flasim për ngarkesa të larta në Novosibirsk.
Së dyti, autori i raportit, Sergei Zaika, është anëtar i Komitetit të Programit të konferencës sonë të re për menaxhimin e njohurive NjohuriKonf. Konferenca është njëditore, do të zhvillohet më 26 prill, por programi i saj është shumë intensiv.
Dhe do të jetë në maj PHP Rusia и RIT++ (me DevOpsConf të përfshirë) - gjithashtu mund të propozoni temën tuaj atje, të flisni për përvojën tuaj dhe të ankoheni për konet tuaja të mbushura.

Burimi: www.habr.com

Shto një koment