Erfaring med at udvikle Refund Tool-tjenesten med en asynkron API på Kafka

Hvad kunne tvinge en så stor virksomhed som Lamoda, med en strømlinet proces og snesevis af sammenkoblede tjenester, til markant at ændre sin tilgang? Motivation kan være helt anderledes: fra lovgivningsmæssig til ønsket om at eksperimentere, der er iboende i alle programmører.

Men det betyder ikke, at du ikke kan regne med yderligere fordele. Sergey Zaika vil fortælle dig præcis, hvad du kan vinde, hvis du implementerer den begivenhedsdrevne API på Kafka (fewald). Der vil helt sikkert også blive talt om store skud og interessante opdagelser – eksperimentet kan ikke undvære dem.

Erfaring med at udvikle Refund Tool-tjenesten med en asynkron API på Kafka

Ansvarsfraskrivelse: Denne artikel er baseret på materialer fra et møde, som Sergey holdt i november 2018 på HighLoad++. Lamodas live-oplevelse af arbejdet med Kafka tiltrak lyttere ikke mindre end andre rapporter på skemaet. Vi synes, det er et glimrende eksempel på, at man altid kan og bør finde ligesindede, og arrangørerne af HighLoad++ vil fortsat forsøge at skabe en atmosfære, der befordrer dette.

Om processen

Lamoda er en stor e-handelsplatform, der har sit eget kontaktcenter, leveringsservice (og mange tilknyttede selskaber), et fotostudie, et kæmpe lager, og alt dette kører på sin egen software. Der er snesevis af betalingsmetoder, b2b-partnere, der kan bruge nogle eller alle disse tjenester og ønsker at vide opdateret information om deres produkter. Derudover opererer Lamoda i tre lande udover Den Russiske Føderation, og alt er lidt anderledes der. I alt er der formentlig mere end hundrede måder at konfigurere en ny ordre på, som skal behandles på sin egen måde. Alt dette fungerer ved hjælp af snesevis af tjenester, der nogle gange kommunikerer på ikke-oplagte måder. Der er også et centralt system, hvis hovedansvar er ordrestatus. Vi kalder hende BIR, jeg arbejder sammen med hende.

Refusionsværktøj med begivenhedsdrevet API

Ordet hændelsesdrevet er ret forhastet; lidt længere vil vi definere mere detaljeret, hvad der menes med dette. Jeg starter med den kontekst, hvor vi besluttede at prøve den begivenhedsdrevne API-tilgang i Kafka.

Erfaring med at udvikle Refund Tool-tjenesten med en asynkron API på Kafka

I enhver butik er der, udover ordrer, som kunderne betaler for, tidspunkter, hvor butikken er forpligtet til at returnere penge, fordi produktet ikke passede kunden. Dette er en forholdsvis kort proces: Vi afklarer oplysningerne, hvis det er nødvendigt, og overfører pengene.

Men afkastet blev mere kompliceret på grund af ændringer i lovgivningen, og vi var nødt til at implementere en separat mikroservice til det.

Erfaring med at udvikle Refund Tool-tjenesten med en asynkron API på Kafka

Vores motivation:

  1. Lov FZ-54 - kort sagt kræver loven at rapportere til skattekontoret om enhver pengetransaktion, det være sig en retur eller en kvittering, inden for en ret kort SLA på få minutter. Vi som e-handelsvirksomhed udfører en del operationer. Teknisk betyder det nyt ansvar (og derfor en ny service) og forbedringer i alle involverede systemer.
  2. BIR split er et internt projekt i virksomheden for at fritage BOB fra en lang række ikke-kerneansvar og reducere dens samlede kompleksitet.

Erfaring med at udvikle Refund Tool-tjenesten med en asynkron API på Kafka

Dette diagram viser de vigtigste Lamoda-systemer. Nu er de fleste af dem flere en konstellation af 5-10 mikrotjenester omkring en skrumpende monolit. De vokser langsomt, men vi forsøger at gøre dem mindre, for det er skræmmende at placere det fragment, der er valgt i midten - vi kan ikke tillade det at falde. Vi er tvunget til at reservere alle udvekslinger (pile) og tage højde for, at nogen af ​​dem kan vise sig at være utilgængelige.

BOB har også en del udvekslinger: betalingssystemer, leveringssystemer, notifikationssystemer mv.

Teknisk BIR er:

  • ~150k linjer kode + ~100k linjer med test;
  • php7.2 + Zend 1 & Symfony Components 3;
  • >100 API'er & ~50 udgående integrationer;
  • 4 lande med deres egen forretningslogik.

At implementere BOB er dyrt og smertefuldt, mængden af ​​kode og problemer, det løser, er sådan, at ingen kan sætte det hele ind i hovedet. Generelt er der mange grunde til at forenkle det.

Returproces

I første omgang er to systemer involveret i processen: BIR og Betaling. Nu dukker to mere op:

  • Fiscalization Service, som skal tage sig af problemer med fiskalisering og kommunikation med eksterne tjenester.
  • Refund Tool, som blot indeholder nye udvekslinger for ikke at puste BOB op.

Nu ser processen sådan ud:

Erfaring med at udvikle Refund Tool-tjenesten med en asynkron API på Kafka

  1. BIR modtager en anmodning om refusion.
  2. BOB taler om dette tilbagebetalingsværktøj.
  3. Refusionsværktøjet fortæller Betaling: "Tilbagefør pengene."
  4. Betaling returnerer pengene.
  5. Refund Tool og BOB synkroniserer status med hinanden, for indtil videre har de begge brug for det. Vi er endnu ikke klar til helt at skifte til Refund Tool, da BOB har en UI, rapporter til regnskab og generelt en masse data, der ikke kan overføres så nemt. Du skal sidde på to stole.
  6. Anmodningen om beskatning forsvinder.

Det resulterede i, at vi lavede en slags eventbus på Kafka – event-bus, hvor alting startede. Hurra, nu har vi et enkelt point of failure (sarkasme).

Erfaring med at udvikle Refund Tool-tjenesten med en asynkron API på Kafka

Fordele og ulemper er ret indlysende. Vi lavede en bus, hvilket betyder, at nu er alle tjenester afhængige af den. Dette forenkler designet, men introducerer et enkelt fejlpunkt i systemet. Kafka vil gå ned, processen vil stoppe.

Hvad er en begivenhedsdrevet API

Et godt svar på dette spørgsmål er i rapporten af ​​Martin Fowler (GOTO 2017) "De mange betydninger af begivenhedsdrevet arkitektur".

Kort hvad vi gjorde:

  1. Afslut alle asynkrone udvekslinger via opbevaring af begivenheder. I stedet for at informere enhver interesseret forbruger om en statusændring over netværket, skriver vi en begivenhed om en statusændring til et centraliseret lager, og forbrugere, der er interesserede i emnet, læser alt, hvad der fremgår derfra.
  2. Hændelsen i dette tilfælde er en meddelelse (meddelelser) at noget har ændret sig et sted. For eksempel er ordrestatus ændret. En forbruger, der er interesseret i nogle data, der følger med statusændringen, og som ikke er inkluderet i meddelelsen, kan selv finde ud af dens status.
  3. Den maksimale mulighed er fuldgyldig event sourcing, statsoverførsel, i hvilket tilfælde indeholder alle de oplysninger, der er nødvendige for behandlingen: hvor de kom fra, og hvilken status den gik til, hvordan præcist blev dataene ændret osv. Spørgsmålet er blot gennemførligheden og mængden af ​​information, som du har råd til at gemme.

Som en del af lanceringen af ​​refusionsværktøjet brugte vi den tredje mulighed. Denne forenklede hændelsesbehandling, da der ikke var behov for at udtrække detaljerede oplysninger, plus det eliminerede scenariet, hvor hver ny hændelse genererer en byge af opklarende få-anmodninger fra forbrugere.

Refusionsværktøjsservice ikke indlæst, så Kafka er der mere en smag af pennen end en nødvendighed. Jeg tror ikke, at hvis tilbagebetalingstjenesten blev et højbelastningsprojekt, ville virksomheden være glad.

Asynkron udveksling SOM DEN ER

Til asynkrone udvekslinger bruger PHP-afdelingen normalt RabbitMQ. Vi indsamlede dataene til anmodningen, satte dem i en kø, og forbrugeren af ​​den samme tjeneste læste den og sendte den (eller sendte den ikke). Til selve API'en bruger Lamoda aktivt Swagger. Vi designer en API, beskriver den i Swagger og genererer klient- og serverkode. Vi bruger også en lidt forbedret JSON RPC 2.0.

Nogle steder bruges ESB-busser, nogle lever på activeMQ, men generelt, RabbitMQ - standard.

Asynkron udveksling TO BE

Ved udformning af udveksling via events-bus kan en analogi spores. Vi beskriver på samme måde fremtidig dataudveksling gennem begivenhedsstrukturbeskrivelser. Yaml-formatet, vi skulle selv lave kodegenereringen, generatoren opretter DTO'er i henhold til specifikationen og lærer klienter og servere at arbejde med dem. Generation går ind på to sprog - golang og php. Dette hjælper med at holde bibliotekerne konsekvente. Generatoren er skrevet i golang, hvorfor den fik navnet gogi.

Event-sourcing på Kafka er en typisk ting. Der er en løsning fra den primære virksomhedsversion af Kafka Confluent, der er nakadi, en løsning fra vores domænebrødre Zalando. Vores motivation til at starte med vanilje Kafka - det betyder, at vi skal lade løsningen stå fri, indtil vi endelig beslutter os for, om vi vil bruge den overalt, og også give os selv råderum og forbedringer: vi vil have støtte til vores JSON RPC 2.0, generatorer til to sprog, og lad os se hvad der ellers.

Det er ironisk, at selv i et så lykkeligt tilfælde, hvor der er en nogenlunde ens forretning, Zalando, som lavede en nogenlunde ens løsning, kan vi ikke bruge den effektivt.

Det arkitektoniske mønster ved lanceringen er som følger: vi læser direkte fra Kafka, men skriver kun gennem events-bus. Der er meget klar til læsning i Kafka: mæglere, balancerer, og det er mere eller mindre klar til horisontal skalering, det ville jeg beholde. Vi ønskede at fuldføre optagelsen gennem én Gateway aka Events-bus, og her er hvorfor.

Events-bus

Eller en eventbus. Dette er simpelthen en statsløs http-gateway, som påtager sig flere vigtige roller:

  • Producerer validering — vi kontrollerer, at arrangementerne lever op til vores specifikationer.
  • Event master system, det vil sige, at dette er det vigtigste og eneste system i virksomheden, der besvarer spørgsmålet om, hvilke begivenheder med hvilke strukturer der anses for gyldige. Validering involverer blot datatyper og opgørelser for at nøje specificere indhold.
  • Hash funktion til sharding - Kafka-meddelelsesstrukturen er nøgleværdi, og ved hjælp af hash af nøgle beregnes det, hvor den skal placeres.

Hvorfor

Vi arbejder i en stor virksomhed med en strømlinet proces. Hvorfor ændre noget? Dette er et eksperiment, og vi forventer at høste flere fordele.

1:n+1 udvekslinger (én til mange)

Kafka gør det meget nemt at forbinde nye forbrugere til API'et.

Lad os sige, at du har en mappe, som du skal holde ajour i flere systemer på én gang (og i nogle nye). Tidligere opfandt vi et bundt, der implementerede set-API, og mastersystemet blev informeret om forbrugeradresser. Nu sender mastersystemet opdateringer til emnet, og alle interesserede læser det. Et nyt system er dukket op - vi har tilmeldt det emnet. Ja, også bundt, men enklere.

I tilfælde af refusionsværktøj, som er et stykke BIR, er det praktisk for os at holde dem synkroniseret gennem Kafka. Betaling siger, at pengene blev returneret: BOB, RT fandt ud af dette, ændrede deres status, Fiscalization Service fandt ud af dette og udstedte en check.

Erfaring med at udvikle Refund Tool-tjenesten med en asynkron API på Kafka

Vi har planer om at skabe en samlet underretningstjeneste, der vil underrette kunden om nyheder vedrørende hans ordre/retur. Nu er dette ansvar spredt ud mellem systemer. Det vil være nok for os at lære meddelelsestjenesten at fange relevant information fra Kafka og svare på den (og deaktivere disse meddelelser i andre systemer). Der kræves ingen nye direkte udvekslinger.

Datadrevet

Information mellem systemer bliver gennemsigtig - uanset hvilken "bloody enterprise" du har, og uanset hvor fyldig dit efterslæb er. Lamoda har en Data Analytics-afdeling, der indsamler data fra systemer og sætter dem i en genanvendelig form, både til erhvervslivet og til intelligente systemer. Kafka giver dig mulighed for hurtigt at give dem en masse data og holde informationsstrømmen opdateret.

Replikeringslog

Beskeder forsvinder ikke efter at være læst, som i RabbitMQ. Når en hændelse indeholder nok information til behandling, har vi en historik over de seneste ændringer af objektet og, hvis det ønskes, muligheden for at anvende disse ændringer.

Lagringsperioden for replikeringsloggen afhænger af intensiteten af ​​at skrive til dette emne; Kafka giver dig mulighed for fleksibelt at sætte grænser for lagringstid og datavolumen. Ved intensive emner er det vigtigt, at alle forbrugere har tid til at læse informationen, før den forsvinder, selv i tilfælde af kortvarig ubrugelighed. Det er normalt muligt at gemme data for enheder af dage, hvilket er ganske nok til støtte.

Erfaring med at udvikle Refund Tool-tjenesten med en asynkron API på Kafka

Dernæst en lille genfortælling af dokumentationen, for dem der ikke er bekendt med Kafka (billedet er også fra dokumentationen)

AMQP har køer: vi skriver beskeder til en kø for forbrugeren. Typisk behandles én kø af ét system med samme forretningslogik. Hvis du har brug for at underrette flere systemer, kan du lære applikationen at skrive til flere køer eller konfigurere udveksling med fanout-mekanismen, som selv kloner dem.

Kafka har en lignende abstraktion emne, hvor du skriver beskeder, men de forsvinder ikke efter læsning. Som standard, når du opretter forbindelse til Kafka, modtager du alle beskeder og har mulighed for at gemme, hvor du slap. Det vil sige, at du læser sekventielt, du må ikke markere beskeden som læst, men gemme id'et, hvorfra du så kan læse videre. Det Id du slog dig på kaldes offset, og mekanismen er commit offset.

Derfor kan forskellig logik implementeres. For eksempel har vi BIR i 4 tilfælde for forskellige lande - Lamoda er i Rusland, Kasakhstan, Ukraine, Hviderusland. Da de er installeret separat, har de lidt forskellige konfigurationer og deres egen forretningslogik. Vi angiver i meddelelsen, hvilket land det refererer til. Hver BOB-forbruger i hvert land læser med et forskelligt groupId, og hvis beskeden ikke gælder for dem, springer de det over, dvs. begår straks offset +1. Hvis det samme emne læses af vores betalingsservice, så gør det det med en separat gruppe, og forskydninger krydser derfor ikke hinanden.

Begivenhedskrav:

  • Data fuldstændighed. Jeg vil gerne have, at arrangementet har nok data, så det kan behandles.

  • Integritet. Vi uddelegerer til Events-bus verifikationen af, at begivenheden er konsistent, og den kan behandle den.
  • Rækkefølgen er vigtig. I tilfælde af en tilbagevenden er vi tvunget til at arbejde med historien. Med notifikationer er ordren ikke vigtig, hvis der er tale om homogene notifikationer, vil mailen være den samme uanset hvilken ordre der kom først. Ved refusion er der en klar proces, hvis vi ændrer ordren, vil der opstå undtagelser, refusionen bliver ikke oprettet eller behandlet - vi ender i en anden status.
  • Konsistens. Vi har en butik, og nu laver vi events i stedet for en API. Vi har brug for en måde til hurtigt og billigt at overføre information om nye begivenheder og ændringer til eksisterende til vores tjenester. Dette opnås gennem en fælles specifikation i et separat git-lager og kodegeneratorer. Derfor er klienter og servere i forskellige tjenester koordineret.

Kafka i Lamoda

Vi har tre Kafka installationer:

  1. Logfiler;
  2. R&D;
  3. Events-bus.

I dag taler vi kun om det sidste punkt. Hos events-bus har vi ikke særlig store installationer - 3 mæglere (servere) og kun 27 emner. Som regel er ét emne én proces. Men dette er en subtil pointe, og vi vil berøre det nu.

Erfaring med at udvikle Refund Tool-tjenesten med en asynkron API på Kafka

Ovenfor er rps-grafen. Refusionsprocessen er markeret med en turkis linje (ja, den på X-aksen), og den lyserøde linje er indholdsopdateringsprocessen.

Lamoda-kataloget indeholder millioner af produkter, og dataene opdateres hele tiden. Nogle kollektioner går af mode, nye frigives for at erstatte dem, og nye modeller dukker konstant op i kataloget. Vi forsøger at forudsige, hvad der vil være interessant for vores kunder i morgen, så vi køber hele tiden nye ting, fotograferer dem og opdaterer montren.

Pink tinder er produktopdateringer, det vil sige ændringer i produkter. Det kan ses, at fyrene tog billeder, tog billeder og så igen! — indlæst en pakke med begivenheder.

Lamoda Events use cases

Vi bruger den konstruerede arkitektur til følgende operationer:

  • Returstatussporing: call-to-action og statussporing fra alle involverede systemer. Betaling, statusser, fiskalisering, meddelelser. Her testede vi tilgangen, lavede værktøjer, samlede alle fejlene, skrev dokumentation og fortalte vores kollegaer, hvordan de skulle bruge det.
  • Opdatering af produktkort: konfiguration, metadata, karakteristika. Et system læser (som vises), og flere skriver.
  • E-mail, push og sms: ordren er afhentet, ordren er ankommet, returneringen er accepteret osv., dem er der rigtig mange af.
  • Lager, lagerfornyelse — kvantitativ opdatering af varer, kun tal: ankomst på lageret, retur. Det er nødvendigt, at alle systemer i forbindelse med varereservation fungerer med de mest aktuelle data. I øjeblikket er aktieopdateringssystemet ret komplekst; Kafka vil forenkle det.
  • Dataanalyse (R&D afdeling), ML værktøjer, analyser, statistik. Vi ønsker, at information skal være gennemsigtig - Kafka er velegnet til dette.

Nu den mere interessante del om de store bump og interessante opdagelser, der er sket i løbet af de sidste seks måneder.

Design problemer

Lad os sige, at vi vil gøre en ny ting – for eksempel overføre hele leveringsprocessen til Kafka. Nu er en del af processen implementeret i Ordrebehandling i BOB. Der er en statusmodel bag overførsel af en ordre til leveringstjenesten, flytning til et mellemlager og så videre. Der er en hel monolit, endda to, plus en masse API'er dedikeret til levering. De ved meget mere om levering.

Det ser ud til at være lignende områder, men ordrebehandlingen i BOB og forsendelsessystemet har forskellige statusser. For eksempel sender nogle kurertjenester ikke mellemstatusser, men kun de endelige: "leveret" eller "tabt". Andre rapporterer tværtimod meget detaljeret om varebevægelser. Alle har deres egne valideringsregler: For nogle er e-mailen gyldig, hvilket betyder, at den vil blive behandlet; for andre er det ikke gyldigt, men ordren vil stadig blive behandlet, fordi der er et telefonnummer til kontakt, og nogen vil sige, at en sådan ordre slet ikke vil blive behandlet.

Datastrøm

I Kafkas tilfælde opstår spørgsmålet om at organisere datastrømmen. Denne opgave involverer at vælge en strategi baseret på flere punkter; lad os gennemgå dem alle.

I ét emne eller i forskellige?

Vi har en begivenhedsspecifikation. I BOB skriver vi, at en sådan og en ordre skal leveres, og angiver: ordrenummeret, dets sammensætning, nogle SKU'er og stregkoder mv. Når varerne ankommer til lageret, vil leverancen kunne modtage status, tidsstempler og alt hvad der skal til. Men så vil vi gerne modtage opdateringer på disse data i BIR. Vi har en omvendt proces med at modtage data fra levering. Er dette den samme begivenhed? Eller er dette en separat udveksling, der fortjener sit eget emne?

Mest sandsynligt vil de være meget ens, og fristelsen til at lave et emne er ikke ubegrundet, fordi et separat emne betyder separate forbrugere, separate konfigurationer, en separat generation af alt dette. Men ikke et faktum.

Nyt felt eller ny begivenhed?

Men hvis du bruger de samme hændelser, så opstår der et andet problem. For eksempel kan ikke alle leveringssystemer generere den type DTO, som BOB kan generere. Vi sender dem id'et, men de gemmer det ikke, fordi de ikke har brug for det, og med henblik på at starte event-bus-processen er dette felt påkrævet.

Hvis vi indfører en regel for event-bus om, at dette felt er påkrævet, så er vi tvunget til at sætte yderligere valideringsregler i BOB eller i starthændelseshandleren. Validering begynder at spredes i hele tjenesten - dette er ikke særlig praktisk.

Et andet problem er fristelsen til gradvis udvikling. Vi får at vide, at der skal tilføjes noget til arrangementet, og måske, hvis vi tænker over det, skulle det have været et særskilt arrangement. Men i vores ordning er et særskilt arrangement et særskilt emne. Et separat emne er hele processen, som jeg beskrev ovenfor. Udvikleren er fristet til blot at tilføje et andet felt til JSON-skemaet og genskabe det.

I tilfælde af refusion ankom vi til begivenheder i løbet af et halvt år. Vi havde en meta-begivenhed kaldet refusionsopdatering, som havde et typefelt, der beskrev, hvad denne opdatering faktisk var. På grund af dette havde vi "vidunderlige" kontakter med validatorer, der fortalte os, hvordan vi validerer denne begivenhed med denne type.

Begivenhedsversionering

For at validere beskeder i Kafka kan du bruge Avro, men det var nødvendigt straks at lægge på det og bruge Confluent. I vores tilfælde skal vi være forsigtige med versionering. Det vil ikke altid være muligt at genlæse beskeder fra replikeringsloggen, fordi modellen har "forladt". Grundlæggende viser det sig at bygge versioner, så modellen er bagudkompatibel: Gør for eksempel et felt midlertidigt valgfrit. Hvis forskellene er for stærke, begynder vi at skrive i et nyt emne, og overfører klienter, når de er færdige med at læse det gamle.

Garanteret læserækkefølge af partitioner

Emner inde i Kafka er opdelt i partitioner. Dette er ikke særlig vigtigt, mens vi designer enheder og børser, men det er vigtigt, når vi beslutter, hvordan det skal forbruges og skaleres.

I det sædvanlige tilfælde skriver du ét emne i Kafka. Som standard bruges én partition, og alle meddelelser i dette emne går til den. Og forbrugeren læser derfor disse beskeder sekventielt. Lad os sige, at vi nu skal udvide systemet, så beskeder læses af to forskellige forbrugere. Sender du for eksempel SMS, så kan du bede Kafka om at lave en ekstra partition, og Kafka vil begynde at opdele beskederne i to dele - halvdelen her, halvdelen her.

Hvordan deler Kafka dem op? Hver besked har en krop (hvori vi gemmer JSON) og en nøgle. Du kan vedhæfte en hash-funktion til denne nøgle, som bestemmer, hvilken partition meddelelsen vil gå ind i.

I vores tilfælde med refusion er dette vigtigt, hvis vi tager to skillevægge, så er der en chance for, at en parallelforbruger vil behandle den anden begivenhed før den første, og der vil være ballade. Hash-funktionen sikrer, at beskeder med samme nøgle havner i samme partition.

Hændelser vs kommandoer

Dette er et andet problem, vi stødte på. Begivenhed er en bestemt hændelse: vi siger, at der skete noget et eller andet sted (der skete noget), for eksempel, en vare blev annulleret, eller en refusion fandt sted. Hvis nogen lytter til disse begivenheder, vil refusionsenheden blive oprettet i henhold til "element annulleret", og "refusion skete" vil blive skrevet et sted i opsætningerne.

Men normalt, når du designer begivenheder, ønsker du ikke at skrive dem forgæves - du stoler på, at nogen vil læse dem. Der er en høj fristelse til at skrive ikke noget_der skete (vare_annulleret, refunderet_refunderet), men noget_skal_gøres. For eksempel er varen klar til at blive returneret.

På den ene side antyder det, hvordan arrangementet vil blive brugt. På den anden side lyder det meget mindre som et normalt begivenhedsnavn. Desuden er der ikke langt herfra til do_something-kommandoen. Men du har ingen garanti for, at nogen læser denne begivenhed; og hvis du læser det, så læser du det med succes; og hvis du læste det med succes, så gjorde du noget, og det noget var vellykket. I det øjeblik en begivenhed bliver do_noget, bliver feedback nødvendig, og det er et problem.

Erfaring med at udvikle Refund Tool-tjenesten med en asynkron API på Kafka

I asynkron udveksling i RabbitMQ, når du læser beskeden, skal du gå til http, du har et svar - i hvert fald at beskeden er modtaget. Når du skriver til Kafka, er der en besked, som du har skrevet til Kafka, men du ved ikke noget om, hvordan den blev behandlet.

Derfor var vi i vores tilfælde nødt til at indføre en responsbegivenhed og sætte overvågning op, så hvis der blev sendt så mange hændelser, så skulle der efter sådan og et tidspunkt komme det samme antal svarhændelser. Hvis dette ikke sker, så ser noget ud til at være gået galt. For eksempel, hvis vi sendte begivenheden "item_ready_to_refund", forventer vi, at en refusion vil blive oprettet, pengene vil blive returneret til kunden, og "money_refunded" begivenheden vil blive sendt til os. Men det er ikke sikkert, så overvågning er nødvendig.

nuancer

Der er et ret åbenlyst problem: Hvis du læser fra et emne sekventielt, og du har et dårligt budskab, falder forbrugeren, og du kommer ikke længere. Du mangler stoppe alle forbrugere, commit offset yderligere for at fortsætte med at læse.

Vi vidste om det, vi regnede med det, og alligevel skete det. Og dette skete, fordi begivenheden var gyldig ud fra et begivenheds-bus-synspunkt, begivenheden var gyldig fra applikationsvalidatorens synspunkt, men den var ikke gyldig fra PostgreSQL-synspunktet, fordi i vores ene system MySQL med UNSIGNED INT, og i det nyskrevne havde systemet PostgreSQL bare med INT. Hans størrelse er lidt mindre, og ID'et passede ikke. Symfony døde med en undtagelse. Vi fangede selvfølgelig undtagelsen, fordi vi stolede på den og ville begå denne offset, men før det ønskede vi at øge problemtælleren, da meddelelsen blev behandlet uden held. Tællerne i dette projekt er også i databasen, og Symfony har allerede lukket kommunikationen med databasen, og den anden undtagelse dræbte hele processen uden en chance for at begå offset.

Tjenesten lå i et stykke tid - heldigvis er det ikke så slemt med Kafka, for beskederne forbliver. Når arbejdet er genoprettet, kan du læse dem færdigt. Det er behageligt.

Kafka har evnen til at indstille en vilkårlig offset gennem værktøj. Men for at gøre dette skal du stoppe alle forbrugere - i vores tilfælde skal du forberede en separat udgivelse, hvor der ikke vil være nogen forbrugere, omfordelinger. Så i Kafka kan du flytte offset gennem værktøj, og beskeden vil gå igennem.

En anden nuance - replikeringslog vs rdkafka.so - er relateret til detaljerne i vores projekt. Vi bruger PHP, og i PHP kommunikerer som regel alle biblioteker med Kafka gennem rdkafka.so repository, og så er der en form for wrapper. Måske er det vores personlige vanskeligheder, men det viste sig, at det ikke er så nemt at genlæse et stykke af det, vi allerede havde læst. Generelt var der softwareproblemer.

For at vende tilbage til detaljerne ved at arbejde med partitioner, er det skrevet direkte i dokumentationen forbrugere >= emnepartitioner. Men jeg fandt ud af det meget senere, end jeg ville have ønsket. Hvis du vil skalere og have to forbrugere, skal du have mindst to partitioner. Det vil sige, at hvis du havde en partition, hvor der var samlet 20 tusinde beskeder, og du lavede en ny, vil antallet af beskeder ikke blive udlignet snart. Derfor, for at have to parallelle forbrugere, skal du beskæftige dig med skillevægge.

overvågning

Jeg tror, ​​at den måde, vi overvåger det, vil være endnu tydeligere, hvilke problemer der er i den eksisterende tilgang.

For eksempel beregner vi, hvor mange produkter i databasen, der for nylig har ændret deres status, og der skulle derfor være sket hændelser baseret på disse ændringer, og vi sender dette nummer til vores overvågningssystem. Så fra Kafka får vi det andet tal, hvor mange begivenheder der faktisk blev optaget. Det er klart, at forskellen mellem disse to tal altid skal være nul.

Erfaring med at udvikle Refund Tool-tjenesten med en asynkron API på Kafka

Derudover skal du overvåge, hvordan producenten klarer sig, om events-bus modtog beskeder, og hvordan forbrugeren har det. For eksempel, i skemaerne nedenfor, klarer Refund Tool sig godt, men BIR har tydeligvis nogle problemer (blå toppe).

Erfaring med at udvikle Refund Tool-tjenesten med en asynkron API på Kafka

Jeg har allerede nævnt forbrugergruppeforsinkelse. Groft sagt er dette antallet af ulæste beskeder. Generelt arbejder vores forbrugere hurtigt, så forsinkelsen er normalt 0, men nogle gange kan der være en kortvarig top. Kafka kan gøre dette ud af boksen, men du skal indstille et bestemt interval.

Der er et projekt Burrowsom vil give dig mere information om Kafka. Den bruger simpelthen forbrugergruppe-API'en til at give status for, hvordan denne gruppe klarer sig. Udover OK og Mislykket er der en advarsel, og du kan finde ud af, at dine forbrugere ikke kan klare produktionstempoet – de har ikke tid til at læse korrektur på, hvad der står. Systemet er ret smart og nemt at bruge.

Erfaring med at udvikle Refund Tool-tjenesten med en asynkron API på Kafka

Sådan ser API-svaret ud. Her er gruppen bob-live-fifa, partition refund.update.v1, status OK, lag 0 - den sidste endelige offset sådan og sådan.

Erfaring med at udvikle Refund Tool-tjenesten med en asynkron API på Kafka

overvågning updated_at SLA (fast) Jeg har allerede nævnt. For eksempel er produktet ændret til status, at det er klar til returnering. Vi installerer Cron, som siger, at hvis dette objekt ikke er gået tilbage til tilbagebetaling om 5 minutter (vi returnerer penge gennem betalingssystemer meget hurtigt), så gik der helt sikkert noget galt, og dette er bestemt en sag for support. Derfor tager vi simpelthen Cron, som læser sådanne ting, og hvis de er større end 0, så sender den en advarsel.

For at opsummere er det praktisk at bruge begivenheder hvornår:

  • information er nødvendig for flere systemer;
  • resultatet af behandlingen er ikke vigtigt;
  • der er få arrangementer eller små begivenheder.

Det ser ud til, at artiklen har et meget specifikt emne - asynkron API på Kafka, men i forbindelse med det vil jeg gerne anbefale en masse ting på én gang.
Først, næste HighLoad ++ vi skal vente til november, i april kommer der en version af St. Petersborg, og i juni taler vi om høje belastninger i Novosibirsk.
For det andet er forfatteren af ​​rapporten, Sergei Zaika, medlem af programudvalget for vores nye konference om videnledelse KnowledgeConf. Konferencen er en-dags, finder sted den 26. april, men dens program er meget intenst.
Og det bliver i maj PHP Rusland и RIT++ (med DevOpsConf inkluderet) - du kan også foreslå dit emne der, tale om din oplevelse og klage over dine fyldte kegler.

Kilde: www.habr.com

Tilføj en kommentar