Erfarenhet av att utveckla tjÀnsten Refund Tool med asynkront API pÄ Kafka

Vad kan fÄ ett sÄ stort företag som Lamoda med en vÀl fungerande process och dussintals sammanlÀnkade tjÀnster att verkligen Àndra sitt tillvÀgagÄngssÀtt? Motivation kan vara helt olika: allt frÄn lagkrav till det som alla programmerare har en inneboende lust att experimentera.

Men det betyder inte att man inte kan förvĂ€nta sig extra vinster. Hur man kan vinna konkret om man implementerar ett events-driven API pĂ„ Kafka kommer Sergey Zaika att berĂ€tta (fewald). Det kommer ocksĂ„ att finnas berĂ€ttelser om lĂ€rdomar och intressanta upptĂ€ckter – ett experiment kan inte undvika sĂ„dana.

Erfarenhet av att utveckla tjÀnsten Refund Tool med asynkront API pÄ Kafka

Disclaimer: Denna artikel Àr baserad pÄ material frÄn en meetup som Sergey höll i november 2018 pÄ HighLoad++. Den verkliga erfarenheten av Lamodas arbete med Kafka lockade lyssnare lika mycket som andra presentationer pÄ schemat. Vi tycker att detta Àr ett utmÀrkt exempel pÄ att man alltid kan och bör hitta likasinnade, och att arrangörerna av HighLoad++ kommer att fortsÀtta att skapa en atmosfÀr som uppmuntrar till detta.

Om processen

Lamoda Àr en stor e-handelplattform som har sitt eget kontaktcenter, leveranstjÀnst (och mÄnga partners), fotostudio, en stor lager och allt detta fungerar pÄ egen mjukvara. Det finns dussintals betalningsmetoder, b2b-partners som kan anvÀnda en del eller alla dessa tjÀnster och vill veta aktuell information om sina produkter. Dessutom verkar Lamoda i tre lÀnder utöver Ryssland, och dÀr fungerar allt lite annorlunda. Totalt finns det nog mer Àn hundra sÀtt att konfigurera en ny bestÀllning som mÄste bearbetas pÄ sitt eget sÀtt. Allt detta fungerar med hjÀlp av dussintals tjÀnster som ibland kommunicerar pÄ ett otydligt sÀtt. Det finns ocksÄ ett centralt system vars huvudansvar Àr orderstatusar. Vi kallar det BOB, jag arbetar med det.

Refund Tool with events-driven API

Ordet events-driven Àr ganska slitet, lÀngre fram kommer vi att definiera mer specifikt vad som menas med detta. Jag börjar med kontexten dÀr vi beslutade att testa tillvÀgagÄngssÀttet för events-driven API i Kafka.

Erfarenhet av att utveckla tjÀnsten Refund Tool med asynkront API pÄ Kafka

I varje butik, utöver bestÀllningar som kunder betalar för, finns det tillfÀllen dÄ butiken förvÀntas Äterbetala pengar, eftersom varan inte passade kunden. Denna relativt korta process innebÀr att vi klargör information, om det Àr nödvÀndigt, och överför pengarna.

Men Äterbetalningsprocessen blev mer komplicerad pÄ grund av förÀndringar i lagstiftningen, och vi var tvungna att implementera en separat mikrotjÀnst för detta.

Erfarenhet av att utveckla tjÀnsten Refund Tool med asynkront API pÄ Kafka

VÄr motivation:

  1. Lag FZ-54 — kort sagt krĂ€ver lagen att varje penningtransaktion, oavsett om det Ă€r en Ă„terbetalning eller en inbetalning, rapporteras till skatteverket inom en ganska kort SLA pĂ„ nĂ„gra minuter. Vi, som e-handel, utför ganska mĂ„nga transaktioner. Tekniskt sett innebĂ€r det ett nytt ansvar (och dĂ€rmed en ny tjĂ€nst) och förĂ€ndringar i alla berörda system.
  2. BOB split — ett internt projekt för att avlasta BOB frĂ„n ett stort antal orelaterade ansvar och minska dess övergripande komplexitet.

Erfarenhet av att utveckla tjÀnsten Refund Tool med asynkront API pÄ Kafka

I detta diagram ritas de grundlĂ€ggande systemen i Lamoda. För nĂ€rvarande bestĂ„r de flesta av dessa snarare av ett stjĂ€rnsystem av 5-10 mikrotjĂ€nster runt en minskande monolit. De vĂ€xer sakta, men vi försöker göra dem mindre, eftersom det Ă€r skrĂ€mmande att deploya en dedikerad del i mitten — vi fĂ„r inte lĂ„ta den krascha. Alla utbyten (pilarna) mĂ„ste vi reservera och förbereda oss för att nĂ„gon av dem kan bli otillgĂ€nglig.

Det finns ocksÄ ganska mÄnga utbyten i BOB: betalningssystem, transport, notifieringar, etc.

Tekniskt Àr BOB:

  • ~150k rader kod + ~100k rader tester;
  • php7.2 + Zend 1 & Symfony Components 3;
  • >100 API & ~50 utgĂ„ende integrationer;
  • 4 lĂ€nder med sin egen affĂ€rslogik.

Att deploya BOB Àr kostsamt och smÀrtsamt; mÀngden kod och de uppgifter som hanteras Àr sÄdana att ingen kan hÄlla allt i huvudet pÄ en gÄng. Sammanfattningsvis finns det mÄnga anledningar att förenkla det.

Återbetalningsprocessen

I processen Àr tvÄ system inblandade: BOB och Payment. Nu tillkommer ytterligare tvÄ:

  • Fiscalization Service, som tar hand om problemen med fiscalisering och kommunikation med externa tjĂ€nster.
  • Refund Tool, dĂ€r nya utbyten hanteras för att inte öka storleken pĂ„ BOB.

Nu ser processen ut sÄ hÀr:

Erfarenhet av att utveckla tjÀnsten Refund Tool med asynkront API pÄ Kafka

  1. En begÀran om Äterbetalning kommer till BOB.
  2. BOB informerar Refund Tool om detta.
  3. Refund Tool meddelar Payment: "Återbetala pengarna".
  4. Payment Äterbetalar pengarna.
  5. Refund Tool och BOB synkroniserar statusar med varandra, eftersom bÄda behöver det för tillfÀllet. Vi Àr dock inte redo att helt byta till Refund Tool, eftersom BOB har UI, rapporter för bokföring och generellt mycket data som inte kan flyttas sÄ enkelt. Vi sitter fortfarande pÄ tvÄ stolar.
  6. En begÀran om fiscalisering skickas.

Som vi har gjort har vi skapat en event-bus baserad pÄ Kafka, dÀr allt Àr kopplat. Hurra, nu har vi en enda felpunkt (sarkasm).

Erfarenhet av att utveckla tjÀnsten Refund Tool med asynkront API pÄ Kafka

Fördelar och nackdelar Àr ganska uppenbara. Vi har skapat en bus, sÄ nu Àr alla tjÀnster beroende av den. Detta förenklar designen, men inför en enda felpunkt i systemet. Om Kafka faller, stannar processen.

Vad Àr ett event-driven API

Ett bra svar pÄ denna frÄga finns i Martin Fowlers föredrag (GOTO 2017) «The Many Meanings of Event-Driven Architecture».

Kort sagt, vad vi har gjort:

  1. Vi har kapslat in alla asynkrona utbyten genom events storage. IstÀllet för att informera varje intresserad konsument om statusÀndringar via nÀtverket, skriver vi ett evenemang om statusÀndringen till ett centraliserat lagringsutrymme, och intresserade konsumenter lÀser dÀrifrÄn allt som dyker upp.
  2. Ett evenemang (event) i det hÀr fallet Àr en notifikation (notifications) om att nÄgot har förÀndrats nÄgonstans. Till exempel, en bestÀllningsstatus har Àndrats. Konsumenten, som Àr intresserad av vissa tillÀggsdata relaterade till statusÀndringen, och som inte finns i notifikationen, kan sjÀlv ta reda pÄ deras tillstÄnd.
  3. Det maximala alternativet Àr fullskalig event sourcing, state transfer, dÀr evenemanget innehÄller all information som behövs för bearbetning: vart vi kom ifrÄn, vilken status vi övergick till, hur data förÀndrades, och sÄ vidare. FrÄgan kvarstÄr dock om det Àr rimligt och hur mycket information du kan tillÄta dig att lagra.

Inom ramen för lanseringen av Refund Tool anvÀnde vi det tredje alternativet. Detta förenklade hÀndelsebehandlingen eftersom det inte behövdes samla in detaljerad information, och det uteslöt scenariot dÀr varje nytt evenemang ger upphov till en vÄg av uppföljande get-förfrÄgningar frÄn konsumenterna.

TjÀnsten Refund Tool Àr inte belastad, sÄ Kafka dÀr Àr mer en provövning Àn ett krav. Jag tror inte att om tjÀnsten för Äterbetalning blev ett highload-projekt, skulle företaget vara glatt.

Async exchange AS IS

För asynkrona utbyten anvÀnder PHP-avdelningen vanligtvis RabbitMQ. Vi samlade in data för förfrÄgningen, la det i kön, och konsumenten av samma tjÀnst lÀste och skickade det (eller inte). För API:et anvÀnder Lamoda aktivt Swagger. Vi designar API:et, beskriver det i Swagger, och genererar klient- och serverkod. Vi anvÀnder Àven en nÄgot utvidgad JSON RPC 2.0.

ESB-bussar anvÀnds hÀr och dÀr, vissa lever pÄ ActiveMQ, men i stort sett, RabbitMQ - standard.

Asynk vÀxla TO BE

NÀr vi designar överföring via events-bus, kan vi se en parallell. Vi beskriver pÄ liknande sÀtt den framtida datatransfereringen genom att beskriva strukturen av eventet. YAML-formatet, kodgenereringen fick vi göra sjÀlva; generatorn efter specifikationen skapar DTO och lÀr klienter och servrar att arbeta med dem. Genereringen sker pÄ tvÄ sprÄk - golang och php. Detta sÀkerstÀller att biblioteken Àr konsekventa. Generatorn Àr skriven i golang, vilket gav den namnet gogi.

Event-sourcing pÄ Kafka Àr typiskt. Det finns en lösning frÄn den huvudsakliga företagsversionen Kafka Confluent, och det finns nakadi, en lösning frÄn vÄra "bröder" inom domÀnomrÄdet Zalando. VÄr motivation att börja med vanilla Kafka Àr att hÄlla lösningen gratis tills vi definitivt beslutar om vi kommer att anvÀnda den allmÀnt, samt att behÄlla utrymme för manövrering och förbÀttringar: vi vill ha stöd för vÄr JSON RPC 2.0, generatorer för tvÄ sprÄk och vi fÄr se vad mer.

Ironiskt nog, Àven i det lyckliga fallet nÀr det finns ett ungefÀr liknande företag som Zalando som har gjort en ungefÀr liknande lösning, kan vi inte anvÀnda den effektivt.

Arkitektoniskt, vid starten, Àr mönstret att lÀsa direkt frÄn Kafka, men skriva endast genom events-bus. För att lÀsa frÄn Kafka finns det mycket fÀrdigt: mÀklare, lastbalanserare och den Àr mer eller mindre klar för horisontell skalning, vilket vi ville behÄlla. Skriften ville vi dock linda in genom en Gateway aka Events-bus, och det Àr dÀrför.

Events-bus

eller hÀndelsebussen. Det Àr bara en stateless http-gateway som tar pÄ sig flera viktiga roller:

  • Validering av producering – vi kontrollerar att hĂ€ndelserna uppfyller vĂ„r specifikation.
  • Master-systemet för hĂ€ndelser, det vill sĂ€ga det huvudsakliga och enda systemet i företaget som svarar pĂ„ frĂ„gan om vilka hĂ€ndelser med vilka strukturer som anses giltiga. Valideringen inkluderar bara datatyper och enums för en strikt specifikation av innehĂ„llet.
  • Hash-funktion för sharding – Kafka-meddelandets struktur Ă€r key-value och hĂ€r berĂ€knas var det ska lĂ€ggas utifrĂ„n hash av key.

Varför

Vi arbetar i ett stort företag med en vÀletablerad process. Varför Àndra nÄgot? Det hÀr Àr ett experiment, och vi förvÀntar oss att fÄ flera fördelar.

1:n+1 överföringar (en till mÄnga)

Med Kafka Àr det mycket enkelt att koppla in nya konsumenter till API:t.

Anta att du har en katalog som du behöver hĂ„lla uppdaterad i flera system samtidigt (och kanske i nya ocksĂ„). Tidigare utvecklade vi ett bundle som implementerade set-API, medan master-systemet informerade om konsumenternas adresser. Nu skickar master-systemet uppdateringar till en topic, och alla som Ă€r intresserade lĂ€ser dem. Det har kommit ett nytt system – vi har prenumererat pĂ„ topicen. Ja, det Ă€r ocksĂ„ ett bundle, men enklare.

I fallet med refund-tool, som i grunden Àr en del av BOB, Àr det praktiskt för oss att hÄlla dem synkroniserade via Kafka. Payment meddelar att pengarna har Äterbetalats: BOB och RT fÄr veta det, Àndrar sina statusar, och Fiscalization Service fÄr veta det och skriver ut kvittot.

Erfarenhet av att utveckla tjÀnsten Refund Tool med asynkront API pÄ Kafka

Vi planerar att skapa en enhetlig Notifications Service som skulle informera kunder om nyheter kring deras bestÀllningar/returer. Just nu Àr det ansvaret spritt mellan systemen. Det kommer att rÀcka med att lÀra Notifications Service att fÄnga relevant information frÄn Kafka och reagera pÄ den (och stÀnga av dessa meddelanden i de andra systemen). Inga nya direkta utbyten krÀvs.

Data-driven

Informationen mellan systemen blir transparent – oavsett vilket "blodigt enterprise" ni har och hur omfattande er backlog Ă€r. Lamoda har en avdelning för Data Analytics som samlar in data frĂ„n systemen och omvandlar dem till ett Ă„teranvĂ€ndbart format, bĂ„de för affĂ€rsĂ€ndamĂ„l och för intelligenta system. Kafka gör det möjligt att snabbt ge dem mycket data och hĂ„lla denna informationsström aktuell.

Replication log

Meddelanden försvinner inte efter lÀsning, som i RabbitMQ. NÀr en hÀndelse innehÄller tillrÀckligt med information för bearbetning, fÄr vi en historia av de senaste Àndringarna för objektet och, om vi vill, möjlighet att tillÀmpa dessa Àndringar.

Lagringstiden för replication log beror pÄ hur intensivt data skrivs till denna topic; Kafka tillÄter att vi flexibelt kan stÀlla in lagringsgrÀnser bÄde vad gÀller tidsperiod och datavolym. För intensiva topics Àr det viktigt att alla konsumenter hinner lÀsa informationen innan den försvinner, Àven vid kortvariga driftstopp. Vanligtvis kan vi lagra data under ett par dagar, vilket Àr fullt tillrÀckligt för support.

Erfarenhet av att utveckla tjÀnsten Refund Tool med asynkront API pÄ Kafka

HÀr följer lite sammanfattning av dokumentationen för dem som inte Àr bekanta med Kafka (bilden Àr ocksÄ frÄn dokumentationen)

I AMQP finns det köer: vi skriver meddelanden till en kö för konsumenten. Vanligtvis behandlar ett system med en och samma affÀrslogik en kö. Om flera system behöver meddelas kan vi lÀra appliceringen att skriva till flera köer eller konfigurera en exchange med en fanout-mekanism som klonar dem.

I Kafka finns en liknande abstraktion topic, dit du skriver meddelanden, men de försvinner inte efter lÀsning. Som standard, nÀr du ansluter till Kafka, fÄr du alla meddelanden, och du har möjlighet att spara platsen dÀr du stannade. Det vill sÀga att du lÀser sekventiellt, kan vÀlja att inte markera meddelandet som lÀst men spara id dÀr du sedan fortsÀtter lÀsa. Id, dÀr du stannade, kallas offset, och mekanismen Àr commit offset.

DĂ€rför kan olika logik implementeras. Till exempel, vĂ„r BOB finns i 4 instanser för olika lĂ€nder — Lamoda finns i Ryssland, Kazakstan, Ukraina, Vitryssland. Eftersom de distribueras separat har de lite egna konfigurationer och sin egen affĂ€rslogik. Vi anger i meddelandet vilket land det hör till. Varje BOB-konsument i varje land lĂ€ser med olika groupId, och om meddelandet inte gĂ€ller dem, hoppar de över det, det vill sĂ€ga de kommittar offset +1 direkt. Om samma Ă€mne lĂ€ses av vĂ„r Payment Service gör den detta med en separat grupp, och dĂ€rför överlappar inte offset.

Krav pÄ hÀndelser:

  • FullstĂ€ndighet av data. Vi skulle vilja att hĂ€ndelsen innehöll tillrĂ€ckligt med data för att kunna bearbeta den.

  • Integritet. Vi delegerar Events-bus att kontrollera att hĂ€ndelsen Ă€r konsekvent och att den kan bearbetas.
  • Ordning Ă€r viktig. I fallet med returer mĂ„ste vi arbeta med historik. Vid notifieringar Ă€r ordningen inte viktig, om det handlar om homogena notifieringar, blir e-postmeddelandet detsamma oavsett vilken bestĂ€llning som kom först. I fallet med retur finns det en tydlig process; om ordningen Ă€ndras kan det uppstĂ„ undantag, Ă„terbetalningen kan inte skapas eller bearbetas — vi kommer att hamna i ett annat status.
  • Konsistens. Vi har ett lagringsutrymme, och nu skapar vi events istĂ€llet för API. Vi behöver ett sĂ€tt att snabbt och billigt överföra information om nya events och om förĂ€ndringar i redan existerande till vĂ„ra tjĂ€nster. Detta uppnĂ„s genom en gemensam specifikation i ett separat git-repository och kodgeneratorer. DĂ€rför Ă€r kunder och servrar i olika tjĂ€nster synkroniserade.

Kafka i Lamoda

Vi har tre installationer av Kafka:

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

Idag pratar vi bara om den senaste punkten. I events-busen har vi inte sĂ„ stora installationer – 3 mĂ€klare (servrar) och totalt 27 Ă€mnen. Som regel Ă€r ett Ă€mne en process. Men detta Ă€r en delikat frĂ„ga, och vi kommer att beröra den snart.

Erfarenhet av att utveckla tjÀnsten Refund Tool med asynkront API pÄ Kafka

Ovan Ă€r en graf över rps. Processen för Ă„terbetalningar Ă€r markerad med en turkos linje (ja, ja, den som ligger pĂ„ X-axeln), och med en rosa linje – processen för att uppdatera innehĂ„ll.

Katalogen Lamoda innehÄller miljontals produkter, och data uppdateras hela tiden. Vissa kollektioner gÄr ur modet, nya slÀpps istÀllet, och nya modeller dyker stÀndigt upp i katalogen. Vi försöker förutsÀga vad som kommer att intressera vÄra kunder i morgon, sÄ vi köper stÀndigt nya saker, fotograferar dem och uppdaterar skyltfönstret.

De rosa topparna representerar produktuppdateringar, det vill sĂ€ga förĂ€ndringar av produkter. Det Ă€r tydligt att teamet fotograferade, fotograferade, och sedan plötsligt! – laddade upp en mĂ€ngd events.

Lamoda Events anvÀndningsfall

Den byggda arkitekturen anvÀnder vi för följande operationer:

  • Övervakning av returstatusar: call-to-action och spĂ„rning av statusar frĂ„n alla involverade system. Betalning, statusar, fackalisering, notifieringar. HĂ€r prövade vi en metod, skapade verktyg, samlade alla buggar, skrev dokumentation och berĂ€ttade för kollegor hur man anvĂ€nder det.
  • Uppdatering av produktkort: konfiguration, metadata, specifikationer. En system lĂ€ser (som visar), medan flera skriver.
  • Email, push och sms: bestĂ€llningen har samlats, bestĂ€llningen har kommit fram, returen har accepterats etc., mĂ„nga av dem.
  • Lager, uppdatering av lager – kvantitativ uppdatering av artiklar, bara siffror: ankomst till lagret, retur. Det Ă€r viktigt att alla system som Ă€r kopplade till varureserviingen hanterar sĂ„ aktuella data som möjligt. För nĂ€rvarande Ă€r lagersystemet ganska komplext, Kafka kommer att förenkla det.
  • Dataanalys (F&U-avdelning), ML-verktyg, analys, statistik. Vi vill att informationen ska vara transparent - för detta passar Kafka bra.

Nu kommer den mer intressanta delen om de misstag som gjorts och de intressanta upptÀckter som har skett under det senaste halvÄret.

Designproblem

Anta att vi vill skapa nÄgot nytt - till exempel, överföra hela leveransprocessen till Kafka. Just nu implementeras en del av processen i Order Processing i BOB. Bakom överföringen av bestÀllningen till fraktjÀnsten, flyttning till ett mellanlager och annat ligger en statusmodell. Det finns en hel monolit, till och med tvÄ, plus massor av API:er relaterade till leverans. De vet mycket mer om leveransen.

Det verkar som om dessa omrÄden Àr lika, men för Order Processing i BOB och för leveranssystemet Àr statusarna olika. Vissa budfirmor skickar till exempel inte mellanliggande statusar, utan bara de slutgiltiga: "levererad" eller "förlorad". Andra, tvÀrtom, rapporterar vÀldigt detaljerat om varans rörelse. Var och en har sina egna valideringsregler: för vissa Àr en e-postadress giltig, sÄ den behandlas; för andra Àr den ogiltig, men bestÀllningen kommer fortfarande att behandlas eftersom det finns ett telefonnummer för kontakt, medan nÄgon annan sÀger att en sÄdan bestÀllning överhuvudtaget inte kommer att behandlas.

Datastream

I fallet med Kafka uppstÄr frÄgan om organisationen av datastreamen. Denna uppgift Àr kopplad till valet av strategi pÄ flera punkter, lÄt oss gÄ igenom dem alla.

I en topic eller i flera?

Vi har en specifikation för hĂ€ndelsen. I BOB skriver vi att en viss bestĂ€llning ska levereras, och vi anger: bestĂ€llningsnummer, dess innehĂ„ll, nĂ„gra SKU och streckkoder etc. NĂ€r varan anlĂ€nder till lagret kommer leveransen att kunna fĂ„ statusar, tidsstĂ€mplar och allt som behövs. Men vi vill sedan i BOB fĂ„ uppdateringar baserat pĂ„ dessa data. En omvĂ€nd process för att hĂ€mta data frĂ„n leveransen uppstĂ„r. Är det samma hĂ€ndelse? Eller Ă€r det en separat utbyte som förtjĂ€nar en egen topic?

Förmodligen kommer de att likna varandra starkt, och frestelsen att skapa en topic Àr inte ogrundad, eftersom en separat topic innebÀr separata konsumenter, separata konfigurationer, separat generering av allt detta. Men det Àr inte sÀkert.

Nytt fÀlt eller ny hÀndelse?

Men om man anvÀnder samma hÀndelser, uppstÄr ett annat problem. Till exempel kan inte alla distributionssystem generera ett sÄdant DTO som BOB kan generera. Vi skickar dem id, men de sparar inte dessa eftersom de inte behövs, och sett ur hÀndelse-hubbens startpunkt Àr det hÀr fÀltet obligatoriskt.

Om vi sĂ€tter en regel för hĂ€ndelse-hubben att detta fĂ€lt Ă€r obligatoriskt, tvingas vi införa ytterligare valideringsregler i BOB eller i hanteraren för start-hĂ€ndelsen. Valideringen börjar sprida sig över tjĂ€nsten – det Ă€r inte sĂ€rskilt praktiskt.

Ett annat problem Àr frestelsen till inkrementell utveckling. Man sÀger till oss att vi behöver lÀgga till nÄgot i hÀndelsen, och kanske, om man verkligen tÀnker efter, borde det ha varit en separat hÀndelse. Men i vÄr struktur Àr en separat hÀndelse ett separat Àmne. Ett separat Àmne innebÀr hela den process jag beskrev ovan. Utvecklaren frestas att helt enkelt lÀgga till ett annat fÀlt i JSON-schemat och regenerera.

För refunds kom vi pÄ ett halvÄr till hÀndelse av hÀndelser. Vi hade en meta-hÀndelse som kallas refund update, som hade ett fÀlt av typen som beskrev vad den hÀr uppdateringen egentligen handlar om. FrÄn detta hade vi "fantastiska" switchar med validerare som sa hur man skulle valida denna hÀndelse med denna typ.

Versionering av hÀndelser

För validering av meddelanden i Kafka kan man anvÀnda Avro, men man behövde planera för detta direkt och anvÀnda Confluent. I vÄrt fall med versioneringen mÄste vi vara försiktiga. Det kommer inte alltid att vara möjligt att lÀsa om meddelandena frÄn repliceringsloggen, eftersom modellen har "Äkt ivÀg". I huvudsak fÄr vi bygga versioner sÄ att modellen Àr bakÄtkompatibel: till exempel göra fÀltet tillfÀlligt icke-obligatoriskt. Om skillnaderna Àr för stora börjar vi skriva i ett nytt Àmne, och klienterna byter över nÀr de har lÀst klart det gamla.

Garanti för lÀsningsordning av partitioner

Ämnen i Kafka Ă€r uppdelade i partitioner. Detta Ă€r inte sĂ€rskilt viktigt medan vi designar entiteter och utbyten, men viktigt nĂ€r vi beslutar hur vi ska konsumera och skala.

I normala fall skriver du till Kafka en topic. Som standard anvĂ€nds en partition, och alla meddelanden i denna topic hamnar dĂ€r. Consumer lĂ€ser dĂ€refter dessa meddelanden sekventiellt. Anta nu att du behöver utöka systemet sĂ„ att meddelandena kan lĂ€sas av tvĂ„ olika consumrar. Om du till exempel skickar ett SMS kan du be Kafka att skapa en extra partition, och Kafka börjar fördela meddelandena mellan tvĂ„ delar — hĂ€lften dit, hĂ€lften hit.

Hur delar Kafka dem? Varje meddelande har en kropp (dÀr vi lagrar JSON) och en nyckel. Till denna nyckel kan en hash-funktion kopplas, som kommer att bestÀmma vilken partition meddelandet hamnar i.

I vÄrt fall med refunds Àr detta viktigt. Om vi tar tvÄ partitioner finns risken att en parallell consumer hanterar den andra hÀndelsen tidigare Àn den första, vilket kan leda till problem. Hash-funktionen garanterar att meddelanden med samma nyckel hamnar i samma partition.

Events vs kommandon

Detta Àr ett annat problem som vi stötte pÄ. Event Àr en hÀndelse: vi sÀger att nÄgot har hÀnt (something_happened), till exempel att ett objekt har avbrutits eller en Äterbetalning har skett. Om nÄgon lyssnar pÄ dessa hÀndelser, kommer en entitet refund att skapas vid "objektet avbröts", och "Äterbetalning har skett" kommer att registreras nÄgonstans i instÀllningarna.

Men vanligtvis, nĂ€r du designar hĂ€ndelser, vill du inte bara skriva dem för sakens skull — du förvĂ€ntar dig att de ska lĂ€sas av nĂ„gon. Det finns en stark frestelse att skriva nĂ„got som inte Ă€r something_happened (item_canceled, refund_refunded), utan nĂ„got som bör göras, till exempel item klar för retur.

Å ena sidan indikerar detta hur hĂ€ndelsen kommer att anvĂ€ndas. Å andra sidan liknar det mycket mindre ett normalt hĂ€ndelse namn. Dessutom ligger det nĂ€ra till hands att ge en kommando do_something. Men du har ingen garanti för att detta event har blivit lĂ€st; och om det har lĂ€sts, sĂ„ har det lĂ€sts framgĂ„ngsrikt; och om det har lĂ€sts framgĂ„ngsrikt, sĂ„ gjordes nĂ„got, och det nĂ„got gick igenom framgĂ„ngsrikt. I det ögonblick hĂ€ndelsen blir do_something, blir Ă„terkoppling nödvĂ€ndig, och det Ă€r ett problem.

Erfarenhet av att utveckla tjÀnsten Refund Tool med asynkront API pÄ Kafka

Vid asynkron kommunikation i RabbitMQ, nĂ€r du har lĂ€st ett meddelande, gĂ„tt vidare till http, har du en respons — Ă„tminstone att meddelandet har accepterats. NĂ€r du skriver i Kafka har du ett meddelande om att du har skrivit i Kafka, men du vet ingenting om hur det har behandlats.

DÀrför var vi tvungna att införa en svarshÀndelse i vÄrt fall och konfigurera övervakning för att om det intrÀffade ett visst antal hÀndelser pÄ en viss tid, sÄ skulle lika mÄnga svarshÀndelser komma in. Om detta inte hÀnde, verkar det som om nÄgot gick fel. Till exempel, om vi skickade hÀndelsen "item_ready_to_refund", förvÀntar vi oss att en Äterbetalning ska skapas, att kunden ska fÄ tillbaka sina pengar och att vi ska fÄ hÀndelsen "money_refunded". Men det Àr inte sÀkert, dÀrför behövs övervakning.

Detaljer

Det finns ett ganska uppenbart problem: om du lÀser frÄn Àmnet sekventiellt och du har nÄgot meddelande som Àr dÄligt, kraschar konsumenten och du kommer inte vidare. Du behöver stoppa alla konsumenter, begÄ offset lÀngre fram för att kunna fortsÀtta lÀsa.

Vi var medvetna om detta, vi hade förberett oss för det, men det hÀnde ÀndÄ. Detta intrÀffade eftersom hÀndelsen var giltig ur events-bus perspektiv, hÀndelsen var giltig ur applikationsvaliderarens perspektiv, men den var inte giltig ur PostgreSQL:s perspektiv, eftersom vi i ett system har MySQL med UNSIGNED INT, medan det nyss skrivna systemet hade PostgreSQL med bara INT. Dess storlek Àr nÄgot mindre och Id fick inte plats. Symfony dog med ett undantag. SjÀlvklart fÄngade vi undantaget eftersom vi hade förberett oss för det och planerade att begÄ detta offset, men innan dess ville vi inkrementera problemrÀknaren, eftersom meddelandet hanterades misslyckat. RÀknarna i detta projekt ligger ocksÄ i databasen, och Symfony hade redan avslutat kommunikationen med databasen, och det andra undantaget dödade hela processen utan chans att begÄ offset.

Servicen lÄg nere ett tag - lyckligtvis Àr det inte sÄ farligt med Kafka, för meddelandena ligger kvar. NÀr arbetet Äterupptas kan de lÀsas igen. Det Àr praktiskt.

Kafka har möjlighet att via verktyg sÀtta en godtycklig offset. Men för att göra detta behöver du stoppa alla konsumenter - i vÄrt fall förbereda en separat release dÀr det inte finns nÄgra konsumenter, inga omlokaliseringar. DÄ kan du via verktygen i Kafka justera offset, och meddelandet kommer att gÄ igenom.

En annan detalj - replikationslogg vs rdkafka.so — Ă€r kopplad till specifikationen av vĂ„rt projekt. Vi anvĂ€nder PHP, och i PHP kommunicerar ofta alla bibliotek med Kafka via rdkafka.so-repositoriet, och sedan finns det nĂ„got slags omslag. Kanske Ă€r det vĂ„ra personliga utmaningar, men det visade sig att det helt enkelt inte Ă€r sĂ„ lĂ€tt att lĂ€sa om nĂ„got som man redan har lĂ€st. Sammanfattningsvis hade vi programvaruproblem.

Tillbaka till specifikationerna för hur man arbetar med partitions, stÄr det skrivet direkt i dokumentationen consumers >= topic partitions. Men jag fick veta detta mycket senare Àn jag hade velat. Om du vill skala upp och ha tvÄ konsumenter behöver du minst tvÄ partitions. Det innebÀr att om du har haft en partition dÀr 20 000 meddelanden har samlats, och du har skapat en ny, kommer antalet meddelanden att jÀmnas ut först efter en tid. SÄ för att ha tvÄ parallella konsumenter mÄste man förstÄ sig pÄ partitions.

Övervakning

Jag tror att det kommer att bli Ànnu tydligare, baserat pÄ hur vi övervakar, vilka problem som finns i den nuvarande metoden.

Till exempel rÀknar vi hur mÄnga produkter som nyligen har Àndrat status i databasen, och i enlighet med dessa förÀndringar borde det ha intrÀffat hÀndelser, och vi skickar detta nummer till vÄrt övervakningssystem. Sedan fÄr vi frÄn Kafka det andra numret, det som faktiskt har registrerats av hÀndelser. Uppenbarligen ska skillnaden mellan dessa tvÄ siffror alltid vara noll.

Erfarenhet av att utveckla tjÀnsten Refund Tool med asynkront API pÄ Kafka

Dessutom mÄste vi övervaka hur det gÄr för producenterna, om events-bussen har tagit emot meddelanden, och hur det gÄr för konsumenten. Till exempel Àr allt bra med Refund Tool pÄ graferna nedan, men BOB har uppenbarligen nÄgra problem (blÄ toppar).

Erfarenhet av att utveckla tjÀnsten Refund Tool med asynkront API pÄ Kafka

Jag har redan nÀmnt consumer-group lag. Grovt sagt Àr det antalet olÀsta meddelanden. Generellt arbetar konsumenterna snabbt, sÄ lag Àr vanligtvis 0, men ibland kan det uppstÄ kortvariga toppar. Kafka hanterar detta ur lÄdan, men man mÄste ange en viss tidsram.

Det finns ett projekt Burrow, som ger dig mer information om Kafka. Det ger helt enkelt via API:n statusen för consumer-group och hur det gĂ„r för denna grupp. Förutom OK och Failed finns det ocksĂ„ warning, och du kan fĂ„ veta att dina konsumenter inte klarar av tempot i producerandet – att de inte hinner lĂ€sa det som skrivs. Systemet Ă€r ganska smart, det Ă€r praktiskt att anvĂ€nda.

Erfarenhet av att utveckla tjÀnsten Refund Tool med asynkront API pÄ Kafka

SĂ„ hĂ€r ser svaret ut via API:n. HĂ€r Ă€r gruppen bob-live-fifa, partition refund.update.v1, status OK, lag 0 – den senaste slutliga offset Ă€r sĂ„ hĂ€r.

Erfarenhet av att utveckla tjÀnsten Refund Tool med asynkront API pÄ Kafka

Övervakning updated_at SLA (stuck) Jag har redan nĂ€mnt det. Till exempel har produkten övergĂ„tt till statusen "redo för Ă„terbetalning". Vi sĂ€tter upp en Cron som sĂ€ger att om den hĂ€r objektet inte har övergĂ„tt till Ă„terbetalning inom 5 minuter (vi Ă„terbetalar pengar snabbt via betalningssystem), sĂ„ har nĂ„got definitivt gĂ„tt fel, och det Ă€r en klar fall för supporten. SĂ„ vi anvĂ€nder helt enkelt en Cron som lĂ€ser sĂ„dana saker, och om de Ă€r fler Ă€n 0, sĂ„ skickar den en alert.

Sammanfattningsvis Àr det bekvÀmt att anvÀnda hÀndelser nÀr:

  • information behövs av flera system;
  • resultatet av bearbetningen inte betyder nĂ„got;
  • det finns fĂ„ hĂ€ndelser eller hĂ€ndelserna Ă€r smĂ„.

Det kan tyckas som att artikeln har ett helt konkret Ă€mne – asynkront API pĂ„ Kafka, men jag har mycket att rekommendera i samband med det.
För det första, nÀsta HighLoad++ behöver vi vÀnta till november, men redan i april kommer den lokala versionen att hÄllas i Sankt Petersburg, och i juni pratar vi om hög belastning i Novosibirsk.
För det andra, rapportförfattaren Sergey Zaika Àr medlem av det programkommitté för vÄr nya konferens om kunskapsstyrning. KnowledgeConfKonferensen Àr en endags och Àger rum den 26 april, men programmet Àr mycket intensivt.
Och dessutom kommer det i maj PHP Russia och RIT++ (med DevOpsConf som del) – dit kan du Ă€ven foreslĂ„ ditt Ă€mne, berĂ€tta om din erfarenhet och klaga pĂ„ dina inlĂ€rda lĂ€xor.

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster