{"id":30778,"date":"2019-10-31T21:37:23","date_gmt":"2019-10-31T18:37:23","guid":{"rendered":"https:\/\/prohoster.info\/blog\/opyt-razrabotki-servisa-refund-tool-s-asinhronnym-api-na-kafka\/"},"modified":"2019-10-31T21:37:23","modified_gmt":"2019-10-31T18:37:23","slug":"opyt-razrabotki-servisa-refund-tool-s-asinhronnym-api-na-kafka","status":"publish","type":"post","link":"https:\/\/prohoster.info\/da\/blog\/administrirovanie\/opyt-razrabotki-servisa-refund-tool-s-asinhronnym-api-na-kafka","title":{"rendered":"Erfaring med udviklingen af Refund Tool-servicen med asynkront API baseret p\u00e5 Kafka","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>Hvad kan f\u00e5 en s\u00e5 stor virksomhed som Lamoda med et veludviklet system og adskillige sammenkoblede tjenester til at \u00e6ndre tilgang? Motivationerne kan v\u00e6re helt forskellige: fra lovgivningsm\u00e6ssige til den iboende trang til at eksperimentere, som alle programm\u00f8rer har.<\/p>\n<p>Men det betyder bestemt ikke, at man ikke kan forvente yderligere fordele. Hvad man konkret kan vinde ved at implementere events-driven API p\u00e5 Kafka, vil Sergey Zaika fort\u00e6lle om (<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/users\/fewald\/\" class=\"user_link\">fewald<\/a><\/noindex>). Der vil ogs\u00e5 v\u00e6re omtale af de erfaringer og interessante opdagelser, som n\u00f8dvendigvis f\u00f8lger med eksperimentet.<\/p>\n<p><img decoding=\"async\" alt=\"Erfaring med udviklingen af Refund Tool-servicen med asynkront API baseret p\u00e5 Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/7ab959ab45ec5c6565b35b18b361c0ea.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\n<em>Disclaimer: Denne artikel er baseret p\u00e5 materialer fra et meetup, som Sergey afholdt i november 2018 p\u00e5 HighLoad++. Den praktiske erfaring fra Lamodas arbejde med Kafka tiltrak ikke mindre interesse fra deltagerne end de andre opl\u00e6g i programmet. Vi mener, det er et fremragende eksempel p\u00e5, at man altid kan og b\u00f8r finde ligesindede, og at arrang\u00f8rerne af HighLoad++ vil forts\u00e6tte med at skabe en atmosf\u00e6re, der muligg\u00f8r dette.<\/em><br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2>Om processen<\/h2>\n<p>\nLamoda er en stor e-handelsplatform, der har sit eget kontaktcenter, leveringsservice (og mange partnerleverand\u00f8rer), fotoservice, et enormt lager, og alt dette k\u00f8rer p\u00e5 sin egen software. Der er adskillige betalingsmetoder, og B2B-partnere kan benytte dele eller alle disse tjenester og \u00f8nsker at kende den aktuelle information om deres varer. Desuden opererer Lamoda i tre lande ud over Rusland, hvor tingene fungerer lidt anderledes. I alt findes der sandsynligvis over hundrede m\u00e5der at konfigurere en ny ordre p\u00e5, der skal behandles p\u00e5 en bestemt m\u00e5de. Alt dette fungerer gennem adskillige tjenester, der nogle gange kommunikerer p\u00e5 en ikke-\u00e5benlys m\u00e5de. Der er ogs\u00e5 et centralt system, hvis hovedansvar er at holde styr p\u00e5 ordrestatusser. Vi kalder det BOB, og jeg arbejder med det.<\/p>\n<h2>Refund Tool med events-driven API <\/h2>\n<p>\nOrdet events-driven er ret udvandet, men vi vil senere definere, hvad det konkret indeb\u00e6rer. Jeg vil starte med konteksten, hvori vi besluttede at afpr\u00f8ve events-driven API-tilgangen i Kafka. <\/p>\n<p><img decoding=\"async\" alt=\"Erfaring med udviklingen af Refund Tool-servicen med asynkront API baseret p\u00e5 Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/3bfdce47dd8420fc63d84645e76de647.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nI enhver butik, udover de ordrer, som kunderne betaler for, opst\u00e5r der situationer, hvor butikken skal tilbagebetale penge, fordi kunden ikke har kunne bruge produktet. Denne relativt korte proces involverer at bekr\u00e6fte oplysningerne, hvis det er n\u00f8dvendigt, og videref\u00f8re pengene. <\/p>\n<p>Men returprocessen er blevet kompliceret p\u00e5 grund af \u00e6ndringer i lovgivningen, og vi har v\u00e6ret n\u00f8dt til at implementere en separat mikrotjeneste til det.<\/p>\n<p><img decoding=\"async\" alt=\"Erfaring med udviklingen af Refund Tool-servicen med asynkront API baseret p\u00e5 Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/0e358df5476e7448976e0f1147a103fb.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nVores motivation:<\/p>\n<ol>\n<li><strong>Lov FZ-54<\/strong>\u00a0\u2014 kort sagt kr\u00e6ver loven, at man rapporterer hver pengeoverf\u00f8rsel til skattemyndighederne, hvad enten det er retur eller indbetaling, inden for en ret kort SLA p\u00e5 nogle f\u00e5 minutter. Som e-handel udf\u00f8rer vi en del operationer. Teknisk set betyder det et nyt ansvar (og dermed en ny tjeneste) samt justeringer i alle involverede systemer.<\/li>\n<li><strong>BOB split<\/strong>\u00a0\u2014 et internt selskabsprojekt til at befri BOB fra et stort antal ikke-kerneopgaver og reducere dets kompleksitet.<\/li>\n<\/ol>\n<p>\n<img decoding=\"async\" alt=\"Erfaring med udviklingen af Refund Tool-servicen med asynkront API baseret p\u00e5 Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/d3ecf9961bdb372fc5f84ee9389f73ef.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nP\u00e5 dette diagram er de vigtigste systemer i Lamoda illustreret. I \u00f8jeblikket repr\u00e6senterer de fleste af dem n\u00e6rmere <strong>et konstellation af 5-10 mikrotjenester omkring en faldende monolit.<\/strong>De vokser stille og roligt, men vi pr\u00f8ver at g\u00f8re dem mindre, fordi det er skr\u00e6mmende at deployere et dedikeret midtpunkt \u2013 vi kan ikke tillade det at falde. Alle udvekslinger (pile) er vi tvunget til at reservere og antage, at enhver af dem kan blive utilg\u00e6ngelig.<\/p>\n<p>I BOB er der ogs\u00e5 en del udvekslinger: betalingssystemer, levering, notifikationer osv. <\/p>\n<p>Teknisk set er BOB:<\/p>\n<ul>\n<li>~150k linjer kode + ~100k linjer tests;<\/li>\n<li>php7.2 + Zend 1 &amp; Symfony Components 3;<\/li>\n<li>&gt;100 API &amp; ~50 udg\u00e5ende integrationer;<\/li>\n<li>4 lande med deres egen forretningslogik. <\/li>\n<\/ul>\n<p>\nAt deployere BOB er dyrt og smertefuldt, antallet af kodelinjer og de opgaver, den l\u00f8ser, er s\u00e5dan, at ingen kan beholde det i hovedet som helhed. Generelt er der mange grunde til at simplificere det.<\/p>\n<h2>Returprocessen<\/h2>\n<p>\nOprindeligt involverer processen to systemer: BOB og Payment. Nu tilf\u00f8jes der endnu to:<\/p>\n<ul>\n<li>Fiscalization Service, som vil tage sig af problemerne med fiscalization og kommunikationen med eksterne tjenester.<\/li>\n<li>Refund Tool, hvor nye udvekslinger simpelthen flyttes hen, s\u00e5 BOB ikke bliver overbelastet.<\/li>\n<\/ul>\n<p>\nNu ser processen s\u00e5ledes ud:<\/p>\n<p><img decoding=\"async\" alt=\"Erfaring med udviklingen af Refund Tool-servicen med asynkront API baseret p\u00e5 Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/13c02975881ad35c61304053c604cda3.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<\/p>\n<ol>\n<li>BOB modtager en anmodning om pengene tilbage.<\/li>\n<li>BOB informerer Refund Tool om dette.<\/li>\n<li>Refund Tool siger til Payment: 'Returner pengene.'.<\/li>\n<li>Payment returnerer pengene.<\/li>\n<li>Refund Tool og BOB synkroniserer statusser, fordi de begge har behov for det i \u00f8jeblikket. Vi er ikke helt klar til at skifte fuldt til Refund Tool, da der er en UI i BOB, rapporter til regnskabsafdelingen og generelt mange data, der ikke s\u00e5 nemt kan overf\u00f8res. Vi m\u00e5 sidde p\u00e5 to stole.<\/li>\n<li>En anmodning om fiscalization sendes.<\/li>\n<\/ol>\n<p>\nI sidste ende har vi lavet en form for event-bus p\u00e5 Kafka, som alt er knyttet til. Hurra, nu har vi et enkelt fejlfunktionspunkt (sarcasme).<\/p>\n<p><img decoding=\"async\" alt=\"Erfaring med udviklingen af Refund Tool-servicen med asynkront API baseret p\u00e5 Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/674edd7972998b4985071f5250612c7e.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nFordele og ulemper er ret \u00e5benlyse. Vi har lavet en bus, hvilket betyder, at alle tjenester nu er afh\u00e6ngige af den. Det forenkler designet, men introducerer et enkelt fejlfunktionspunkt i systemet. Hvis Kafka g\u00e5r ned, stopper processen.<\/p>\n<h2>Hvad er et events-drevet API <\/h2>\n<p>\nEt godt svar p\u00e5 dette sp\u00f8rgsm\u00e5l findes i Martin Fowlers pr\u00e6sentation (GOTO 2017) <noindex><a rel=\"nofollow\" href=\"https:\/\/youtu.be\/STKCRSUsyPO\">\u00abThe Many Meanings of Event-Driven Architecture\u00bb<\/a><\/noindex>. <\/p>\n<p>Kort sagt, hvad vi har gjort:<\/p>\n<ol>\n<li>Vi har indkapslet al asynkron kommunikation gennem <strong>events storage<\/strong>. I stedet for at kommunikere over netv\u00e6rket til hver interesseret forbruger om status\u00e6ndring, skriver vi en h\u00e6ndelse om status\u00e6ndring i et centraliseret lager, og de interesserede forbrugere l\u00e6ser alt, der dukker op derfra.<\/li>\n<li>En h\u00e6ndelse (event) i dette tilf\u00e6lde er en notifikation (<strong>notifications<\/strong>) om, at noget et sted er \u00e6ndret. For eksempel, status for en ordre er \u00e6ndret. En forbruger, som har brug for nogle ledsagende oplysninger om status\u00e6ndringen, som ikke er i notifikationen, kan selv finde deres tilstand.<\/li>\n<li>Den maksimale l\u00f8sning er fuld event sourcing, <strong>state transfer<\/strong>, hvor h\u00e6ndelsen indeholder al den information, der er n\u00f8dvendig for behandling: hvorfra og til hvilken status det er g\u00e5et, hvordan dataene er \u00e6ndret osv. Sp\u00f8rgsm\u00e5let er kun om hensigtsm\u00e6ssighed og m\u00e6ngden af information, som du kan tillade dig at opbevare.<\/li>\n<\/ol>\n<p>\nI forbindelse med lanceringen af Refund Tool brugte vi den tredje mulighed. Det forenklede behandling af h\u00e6ndelser, da vi ikke beh\u00f8vede at udvinde detaljerede oplysninger, og eliminerede scenariet, hvor hver ny h\u00e6ndelse medf\u00f8rer en b\u00f8lge af opklarende get-anmodninger fra forbrugerne.<\/p>\n<p>Tjenesten Refund Tool <strong>er ikke belastet<\/strong>, s\u00e5 Kafka der er mere en pr\u00f8ve end en n\u00f8dvendighed. Jeg tror ikke, at hvis tilbagebetalingsservicen blev et high-load projekt, ville forretningen v\u00e6re glad.<\/p>\n<h4>Async exchange AS IS<\/h4>\n<p>\nTil asynkrone udvekslinger bruger PHP-afdelingen som regel RabbitMQ. Vi samlede data til anmodningen, lagde det i k\u00f8en, og forbrugeren fra den samme tjeneste l\u00e6ste det og sendte det (eller ikke sendte det). For selve API'et bruger Lamoda aktivt Swagger. Vi designer API'et, beskriver det i Swagger, genererer klient- og serverkode. Vi bruger ogs\u00e5 en lidt udvidet version af JSON RPC 2.0. <\/p>\n<p>Nogle steder bruges esb-busser, nogle lever p\u00e5 activeMQ, men generelt, <strong>RabbitMQ \u2014 standard<\/strong>.<\/p>\n<h4>Async exchange TO BE<\/h4>\n<p>\nN\u00e5r vi designer en udveksling gennem events-bus, ses der en parallel. Vi beskriver p\u00e5 lignende m\u00e5de den fremtidige datoudveksling gennem beskrivelser af event-strukturen. YAML-formatet og kodegenereringen m\u00e5tte vi selv lave; generatoren efter specifikationen skaber DTO'er og l\u00e6rer klienter og servere at arbejde med dem. Genereringen foreg\u00e5r p\u00e5 to sprog \u2014 <strong>golang og php<\/strong>. Det g\u00f8r det muligt at holde bibliotekerne konsistente. Generatoren er skrevet i golang, hvorfor den fik navnet gogi.<\/p>\n<p>Event sourcing p\u00e5 Kafka er en typisk ting. Der findes l\u00f8sninger fra den prim\u00e6re enterprise-version af Kafka Confluent, og der er <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/zalando\/nakadi\">nakadi<\/a><\/noindex>, en l\u00f8sning fra vores \"br\u00f8dre\" i dom\u00e6neomr\u00e5det Zalando. Vores <strong>motivation for at starte med vanilla Kafka<\/strong>\u00a0er at holde l\u00f8sningen gratis, indtil vi beslutter, om vi vil bruge det i stor skala, samt for at bevare rum til man\u00f8vrering og forbedringer: vi vil have st\u00f8tte til vores <strong>JSON RPC 2.0<\/strong>, generatorer til to sprog, og vi ser p\u00e5 hvad mere der kan laves. <\/p>\n<p>Ironisk nok, selv i s\u00e5 lykkelig en situation, hvor der er en nogenlunde tilsvarende virksomhed som Zalando, som har lavet en lignende l\u00f8sning, kan vi ikke bruge den effektivt. <\/p>\n<p>Arkitektonisk set er m\u00f8nsteret ved opstart s\u00e5dan: vi l\u00e6ser direkte fra Kafka, men skriver kun gennem events-bus. Til l\u00e6sning fra Kafka er der meget f\u00e6rdigt: m\u00e6glere, belastningsfordelere, og det er mere eller mindre klar til horisontal skalerbarhed, noget vi \u00f8nskede at bevare. For skrivning \u00f8nskede vi at pakke det gennem \u00e9n gateway, ogs\u00e5 kendt som Events-bus, og det er derfor.<\/p>\n<h3>Events-bus<\/h3>\n<p>\nEller events-bussen. Det er blot en stateless HTTP-gateway, som tager flere vigtige roller:<\/p>\n<ul>\n<li><strong>Validering af produktion<\/strong>\u00a0\u2014 vi kontrollerer, at begivenhederne lever op til vores specifikation.<\/li>\n<li><strong>Hovedsystem for begivenheder<\/strong>, det vil sige det er det prim\u00e6re og eneste system i virksomheden, som svarer p\u00e5 sp\u00f8rgsm\u00e5let om, hvilke events med hvilke strukturer der betragtes som valide. Validering inkluderer blot datatyper og enums for h\u00e5rd specifikation af indholdet. <\/li>\n<li><strong>Hash-funktion<\/strong> til sharding \u2014 strukturen af meddelelsen i Kafka er key-value, og hash fra key beregner, hvor det skal placeres.<\/li>\n<\/ul>\n<p><\/p>\n<h3>Hvorfor<\/h3>\n<p>\nVi arbejder i en stor virksomhed med en veludviklet proces. Hvorfor \u00e6ndre noget? <strong>Det er et eksperiment<\/strong>, og vi forventer at opn\u00e5 nogle fordele.<\/p>\n<h4>1:n+1 udvekslinger (\u00e9n til mange)<\/h4>\n<p>\nDet er meget nemt at tilslutte nye forbrugere til API'et med Kafka. <\/p>\n<p>Lad os antage, at du har et katalog, som skal holdes ajour i flere systemer samtidig (og m\u00e5ske ogs\u00e5 i nogle nye). Tidligere opfandt vi et bundle, der implementerede et set-API, og vi informerede master-systemet om adressen p\u00e5 forbrugerne. Nu sender master-systemet opdateringer til et emne, og alle, der er interesseret, l\u00e6ser dem. Der er kommet et nyt system - vi har tilmeldt det emnet. Ja, stadig et bundle, men lidt enklere.<\/p>\n<p>I tilf\u00e6lde af refund-tool, som i bund og grund er et stykke BOB, er det praktisk for os at holde dem synkroniseret via Kafka. Betalingen erkl\u00e6rer, at pengene er blevet tilbagef\u00f8rt: BOB, RT l\u00e6rte om det, \u00e6ndrede deres status, og Fiscalization Service fik besked og udskrev en kvittering.<\/p>\n<p><img decoding=\"async\" alt=\"Erfaring med udviklingen af Refund Tool-servicen med asynkront API baseret p\u00e5 Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/b01b22a333b58e87aeef0c52d40e6960.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nVi har planer om at lave en samlet Notifications Service, der ville underrette kunden om nyheder vedr\u00f8rende deres ordre\/returneringer. I \u00f8jeblikket er dette ansvar fordelt mellem systemerne. Det vil v\u00e6re tilstr\u00e6kkeligt at l\u00e6re Notifications Service at fange relevant information fra Kafka og reagere p\u00e5 det (og deaktivere disse meddelelser i de andre systemer). Der kr\u00e6ves ingen nye direkte udvekslinger.<\/p>\n<h4>Data-drevet<\/h4>\n<p>\nInformationen mellem systemerne bliver gennemsigtig - uanset hvor \"blodig enterprise\" du har, og hvor omfattende din backlog m\u00e5tte v\u00e6re. I Lamoda er der en afdeling for Data Analytics, som indsamler data fra systemerne og bringer dem i genanvendelig form, b\u00e5de for forretningen og for intelligente systemer. Kafka giver mulighed for hurtigt at give dem mange data og holde denne informationsstr\u00f8m aktuel.<\/p>\n<h4>Replication log<\/h4>\n<p>\nBeskeder forsvinder ikke efter l\u00e6sning, som i RabbitMQ. N\u00e5r et event indeholder tilstr\u00e6kkelig information til behandling, f\u00e5r vi en historik over de seneste \u00e6ndringer af objektet, og, hvis \u00f8nskes, muligheden for at anvende disse \u00e6ndringer.<\/p>\n<p>Opbevaringsperioden for replication log afh\u00e6nger af intensiteten af skrivninger til dette emne, Kafka g\u00f8r det muligt at konfigurere gr\u00e6nserne for opbevaringsperiode og datavolumen fleksibelt. For intensive emner er det vigtigt, at alle forbrugere har tid til at l\u00e6se informationen, f\u00f8r den forsvinder, selv i tilf\u00e6lde af kortvarige nedbrud. Det er normalt muligt at opbevare data i\u00a0<strong>et antal dage<\/strong>, hvilket er helt tilstr\u00e6kkeligt til support. <\/p>\n<p><img decoding=\"async\" alt=\"Erfaring med udviklingen af Refund Tool-servicen med asynkront API baseret p\u00e5 Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/0e08dd384155289123ebee96430c2370.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nN\u00e6ste er lidt om dokumentationen for dem, der ikke er bekendt med Kafka (billedet er ogs\u00e5 fra dokumentationen)<\/p>\n<p>I AMQP er der k\u00f8er: vi skriver beskeder i en k\u00f8 til forbrugeren. Som regel behandles \u00e9n k\u00f8 af \u00e9t system med den samme forretningslogik. Hvis der er behov for at informere flere systemer, kan man l\u00e6re applikationen at skrive til flere k\u00f8er eller konfigurere en exchange med en fanout-mekanisme, som selv kloner dem.<\/p>\n<p>I Kafka er der en lignende abstraktion <em>emne<\/em>, hvor du skriver beskeder, men de forsvinder ikke efter at v\u00e6re blevet l\u00e6st. Som standard, n\u00e5r du forbinder til Kafka, modtager du alle beskeder, og der er samtidig mulighed for at huske det sted, hvor du stoppede. Det vil sige, at du l\u00e6ser r\u00e6kkef\u00f8lge, kan v\u00e6lge ikke at markere en besked som l\u00e6st, men gemme id'et, s\u00e5 du senere kan forts\u00e6tte l\u00e6sningen. Id'et, hvor du stoppede, kaldes offset, og mekanismen er commit offset. <\/p>\n<p>Derfor kan man implementere forskellig logik. For eksempel eksisterer vi BOB i 4 instanser for forskellige lande \u2013 Lamoda findes i Rusland, Kasakhstan, Ukraine og Hviderusland. Da de deployeres separat, har de lidt forskellige konfigurationer og deres egen forretningslogik. Vi angiver i beskeden, hvilket land den tilh\u00f8rer. Hver BOB-forbruger i hvert land l\u00e6ser med forskellige groupId'er, og hvis beskeden ikke vedr\u00f8rer dem, springer de den over, d.v.s. de committer straks offset +1. Hvis den samme emne l\u00e6ses af vores Payment Service, g\u00f8r den det med en separat gruppe, og derfor krydser offset ikke hinanden.<\/p>\n<p><b>Krav til begivenheder:<\/b><\/p>\n<ul>\n<li><strong>Datakomplethed. <\/strong>Det ville v\u00e6re \u00f8nskeligt, at begivenheden havde tilstr\u00e6kkelige data til, at den kunne blive behandlet. <\/li>\n<\/ul>\n<p><\/p>\n<ul>\n<li><strong>Integritet. <\/strong>Vi delegerer Events-bus til at kontrollere, at begivenheden er konsistent, og den kan behandle den.<\/li>\n<li><strong>R\u00e6kkef\u00f8lge er vigtigt. <\/strong>I tilf\u00e6lde af returnering er vi n\u00f8dt til at arbejde med historikken. Med notifikationer er r\u00e6kkef\u00f8lgen ikke vigtig, hvis det er homogene notifikationer, vil e-mailen v\u00e6re den samme uanset hvilken ordre der kom f\u00f8rst. I tilf\u00e6lde af returnering er der en klar proces, og hvis r\u00e6kkef\u00f8lgen \u00e6ndres, opst\u00e5r der undtagelser, refunderingen vil ikke blive oprettet eller behandlet \u2013 vi vil ende i en anden status.<\/li>\n<li><strong>Konsistens. <\/strong>Vi har et lager, og nu laver vi begivenheder i stedet for en API. Vi har brug for en m\u00e5de at hurtigt og billigt sende oplysninger om nye begivenheder og \u00e6ndringer i eksisterende til vores tjenester. Dette opn\u00e5s gennem en f\u00e6lles specifikation i et separat git-repositorium og kodegenerators. S\u00e5 derfor er vores klienter og servere i forskellige tjenester koordineret.<\/li>\n<\/ul>\n<p><\/p>\n<h2>Kafka i Lamoda<\/h2>\n<p>\nVi har tre installationer af Kafka: <\/p>\n<ol>\n<li>Logs;<\/li>\n<li>F&amp;U;<\/li>\n<li>Events-bus.<\/li>\n<\/ol>\n<p>\nI dag taler vi kun om det sidste punkt. I events-bussen har vi ikke s\u00e5 store installationer - 3 broker (servere) og kun 27 emner. Som regel er et emne en proces. Men det er et subtilt punkt, og det skal vi ogs\u00e5 ber\u00f8re.<\/p>\n<p><img decoding=\"async\" alt=\"Erfaring med udviklingen af Refund Tool-servicen med asynkront API baseret p\u00e5 Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/f398852689b31429cc97b4cbffcabab5.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\n\u00d8verst et diagram over rps. Refunds-processen er markeret med en turkis linje (ja, ja, den, der ligger p\u00e5 X-aksen), og den lyser\u00f8de markerer indholdsopdateringsprocessen. <\/p>\n<p>Kataloget Lamoda indeholder millioner af varer, og dataene opdateres konstant. Nogle kollektioner g\u00e5r af mode, og nye udgives som erstatning, der kommer hele tiden nye modeller til kataloget. Vi fors\u00f8ger at forudsige, hvad der vil interessere vores kunder i morgen, derfor k\u00f8ber vi konstant nye ting, tager billeder af dem og opdaterer udstillingen. <\/p>\n<p>De lyser\u00f8de toppe er produktopdateringer, det vil sige \u00e6ndringer ved varerne. Det er tydeligt, at drengene har taget billeder, taget billeder, og s\u00e5 pludselig! - de uploadede en bunke begivenheder.<\/p>\n<h2>Lamoda Events brugssager<\/h2>\n<p>\nDen opbyggede arkitektur bruger vi til s\u00e5danne operationer:<\/p>\n<ul>\n<li><strong>Sporing af returneringsstatusser<\/strong>: call-to-action og tracking af statusser fra alle involverede systemer. Betaling, statusser, skattem\u00e6ssig registrering, notifikationer. Her har vi pr\u00f8vet en metode, lavet v\u00e6rkt\u00f8jer, samlet alle fejl, skrevet dokumentation og fortalt kollegerne, hvordan man bruger det.<\/li>\n<li><strong>Opdatering af produktkort: <\/strong>konfiguration, metadata, specifikationer. \u00c9n system l\u00e6ser (den, der viser), og flere skriver.<\/li>\n<li><strong>Email, push og sms<\/strong>: ordren er samlet, ordren er ankommet, returneringen er accepteret osv., der er mange af dem. <\/li>\n<li><strong>Lager, opdatering af lageret<\/strong>\u00a0\u2014 kvantitativ opdatering af navne, bare tal: modtagelse p\u00e5 lageret, returnering. Det er n\u00f8dvendigt, at alle systemer, der er relateret til reservation af varer, opererer med de mest aktuelle data. Lige nu er lageropdateringssystemet ret komplekst, Kafka vil g\u00f8re det muligt at forenkle det.<\/li>\n<li><strong>Dataanalyse<\/strong> (R&amp;D-afdeling), ML-v\u00e6rkt\u00f8jer, analyse, statistik. Vi \u00f8nsker, at oplysningerne skal v\u00e6re gennemsigtige - til dette form\u00e5l er Kafka velegnet.<\/li>\n<\/ul>\n<p>\nNu til den mere interessante del om de bumps, vi har m\u00f8dt, og de sp\u00e6ndende opdagelser, der er sket over de sidste seks m\u00e5neder.<\/p>\n<h2>Designproblemer<\/h2>\n<p>\nLad os sige, at vi vil lave en ny ting - for eksempel overf\u00f8re hele leveringsprocessen til Kafka. Lige nu er en del af processen implementeret i Order Processing i BOB. Bag ordenes overf\u00f8rsel til leveringsservicen, bev\u00e6gelsen til et midlertidigt lager og andet st\u00e5r der en statusmodel. Der er en hel monolith, faktisk to, plus en masse API'er, der er dedikeret til levering. De ved meget mere om leveringen. <\/p>\n<p>Det ser ud til at v\u00e6re besl\u00e6gtede omr\u00e5der, men for Order Processing i BOB og for leveringssystemet er statuserne forskellige. For eksempel sender nogle kurertjenester ikke interim-statusser, men kun de endelige: \"leveret\" eller \"mistet\". Andre oplyser derimod meget detaljeret om bev\u00e6gelsen af varer. Alle har deres egne valideringsregler: for nogen er en e-mail gyldig, s\u00e5 den bliver behandlet; for andre er den ikke gyldig, men ordren vil stadig blive behandlet, fordi der er et telefonnummer til kontakt, og nogen vil sige, at en s\u00e5dan ordre ikke vil blive behandlet overhovedet.<\/p>\n<h3>Datastream<\/h3>\n<p>\nI tilf\u00e6lde af Kafka opst\u00e5r sp\u00f8rgsm\u00e5let om organisering af datastreamen. Denne opgave er relateret til valget af strategi p\u00e5 flere punkter, lad os kigge p\u00e5 dem alle.<\/p>\n<h4>I \u00e9n topic eller i forskellige?<\/h4>\n<p>\nVi har en begivenhedsspecifikation. I BOB skriver vi, at en bestemt ordre skal leveres, og vi angiver: ordrenummer, dens indhold, nogle SKU'er og stregkoder osv. N\u00e5r varerne ankommer til lageret, vil leveringen kunne f\u00e5 statusser, tidsstempler og alt, hvad der er n\u00f8dvendigt. Men derefter \u00f8nsker vi at f\u00e5 opdateringer om disse data i BOB. Der opst\u00e5r en omvendt proces for at hente data fra levering. Er det den samme h\u00e6ndelse? Eller er det en separat udveksling, der fortjener en separat topic?<\/p>\n<p>Det er sandsynligvis, at de vil v\u00e6re meget ens, og fristelsen til at lave \u00e9n topic er ikke ubegrundet, fordi en separat topic betyder separate forbrugere, separate konfigurationer, en separate generation af alt dette. Men det er ikke sikkert.<\/p>\n<h4>Nyt felt eller ny h\u00e6ndelse?<\/h4>\n<p>\nMen hvis vi bruger de samme begivenheder, opst\u00e5r der et andet problem. For eksempel kan ikke alle leveringssystemer generere et s\u00e5dant DTO, som BOB kan generere. Vi sender dem id, og de gemmer dem ikke, fordi de ikke har brug for dem, og set fra event-bus'ens startperspektiv er dette felt p\u00e5kr\u00e6vet. <\/p>\n<p>Hvis vi indf\u00f8rer en regel for event-bus, hvor dette felt er p\u00e5kr\u00e6vet, er vi n\u00f8dt til at indf\u00f8re yderligere valideringsregler i BOB eller i behandlingen af startbegivenheden. Valideringen begynder at sprede sig i tjenesten - hvilket ikke er s\u00e6rlig praktisk.<\/p>\n<p>Et andet problem er fristelsen til inkrementel udvikling. Vi f\u00e5r at vide, at vi skal tilf\u00f8je noget til begivenheden, og, m\u00e5ske, hvis man t\u00e6nker godt over det, burde det have v\u00e6ret en separat begivenhed. Men i vores skema er en separat begivenhed et separat emne. Et separat emne er hele den proces, som jeg har beskrevet ovenfor. Udvikleren bliver fristet til bare at tilf\u00f8je endnu et felt til JSON-skemaet og regenerere.<\/p>\n<p>I tilf\u00e6lde af refunds n\u00e5ede vi p\u00e5 seks m\u00e5neder til begivenheden for begivenheder. Vi havde \u00e9n meta-begivenhed, der hedder refund update, som havde et felt type, der beskrev, hvad selve denne opdatering gik ud p\u00e5. Derfra havde vi \"fantastiske\" switches med validere, der sagde, hvordan man skulle validere denne begivenhed med denne type.<\/p>\n<h4>Versionering af begivenheder<\/h4>\n<p>\nTil validering af beskeder i Kafka kan man bruge <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.confluent.io\/current\/schema-registry\/docs\/index.html\">Avro<\/a><\/noindex>, men det var n\u00f8dvendigt at planl\u00e6gge dette fra starten og bruge Confluent. I vores tilf\u00e6lde med versionering er vi n\u00f8dt til at v\u00e6re forsigtige. Det vil ikke altid v\u00e6re muligt at l\u00e6se beskeder fra replikationsloggen, fordi modellen \"er k\u00f8rt v\u00e6k\". Hovedsageligt ender vi med at bygge versioner, s\u00e5 modellen er bagudkompatibel: for eksempel ved at g\u00f8re feltet midlertidigt ikke-obligatorisk. Hvis forskellene er for store, begynder vi at skrive til et nyt emne, og vi flytter kunderne, n\u00e5r de er f\u00e6rdige med at l\u00e6se det gamle.<\/p>\n<h4>Garanteret l\u00e6seorden for partitioner<\/h4>\n<p>\nEmnerne inden for Kafka er opdelt i partitioner. Det er ikke s\u00e5 vigtigt, mens vi designer entiteter og udvekslinger, men vigtigt, n\u00e5r vi beslutter, hvordan vi skal forbruge det og skalere.<\/p>\n<p>I de fleste tilf\u00e6lde skriver du til Kafka et enkelt emne. Som standard bruges en partition, og alle meddelelser til dette emne havner i den. En consumer l\u00e6ser s\u00e5ledes sekventielt disse meddelelser. Antag nu, at systemet skal udvides, s\u00e5 meddelelserne kan l\u00e6ses af to forskellige consumere. Hvis du for eksempel sender en SMS, kan du f\u00e5 Kafka til at oprette en ekstra partition, og Kafka begynder at fordele meddelelserne p\u00e5 to dele - halvdelen der, halvdelen her. <\/p>\n<p>Hvordan deler Kafka dem? Hver meddelelse har et indhold (hvor vi gemmer JSON) og en n\u00f8gle. Til denne n\u00f8gle kan der anvendes en hash-funktion, der bestemmer, hvilken partition meddelelsen havner i.<\/p>\n<p>I vores tilf\u00e6lde med refunds er dette vigtigt; hvis vi bruger to partitioner, er der en chance for, at en parallel consumer behandler den anden begivenhed f\u00f8r den f\u00f8rste, hvilket kan skabe problemer. Hash-funktionen garanterer, at meddelelser med den samme n\u00f8gle havner i den samme partition. <\/p>\n<h4>Begivenheder vs kommandoer<\/h4>\n<p>\nDette er endnu et problem, vi st\u00f8dte p\u00e5. En begivenhed er en form for h\u00e6ndelse: vi siger, at noget er sket (something_happened), for eksempel at en vare er blevet annulleret eller en refund er sket. Hvis nogen lytter til disse begivenheder, vil en refund-entitet blive oprettet ved 'varen blev annulleret', mens 'en refund skete' registreres et sted i ops\u00e6tningen.<\/p>\n<p>Men normalt, n\u00e5r du designer begivenheder, vil du ikke skrive dem for ingenting - du regner med, at nogen vil l\u00e6se dem. Der er en stor fristelse til at skrive ikke something_happened (item_canceled, refund_refunded), men something_should_be_done. For eksempel, varen er klar til returnering.<\/p>\n<p>P\u00e5 den ene side antyder det, hvordan begivenheden vil blive brugt. P\u00e5 den anden side ligner det langt mindre et normalt navn til en begivenhed. Desuden er det ikke langt herfra til kommandon do_something. Men du har ingen garanti for, at nogen har l\u00e6st denne begivenhed; og hvis den er blevet l\u00e6st, er der ingen garanti for, at det er sket med succes; og hvis det er sket med succes, har det gjort noget, og det noget er kommet igennem med succes. I det \u00f8jeblik, hvor en begivenhed bliver do_something, bliver der behov for feedback, og det er et problem.<\/p>\n<p><img decoding=\"async\" alt=\"Erfaring med udviklingen af Refund Tool-servicen med asynkront API baseret p\u00e5 Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/b755d91208092bd9791a41ce4633fb48.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nI asynkron udveksling i RabbitMQ, n\u00e5r du har l\u00e6st en meddelelse, har du v\u00e6ret inde og tjekke via http, og du f\u00e5r en respons - i det mindste at meddelelsen blev modtaget. N\u00e5r du har skrevet i Kafka, er der en meddelelse om, at du har skrevet i Kafka, men hvad der skete med det, ved du ikke noget om. <\/p>\n<p>Derfor m\u00e5tte vi i vores tilf\u00e6lde indf\u00f8re en responsbegivenhed og indstille overv\u00e5gning p\u00e5, at hvis der opstod et vist antal h\u00e6ndelser inden for en given tid, s\u00e5 skulle der komme et tilsvarende antal responsbegivenheder. Hvis dette ikke skete, ser det ud til, at noget gik galt. For eksempel, hvis vi har sendt h\u00e6ndelsen \"item_ready_to_refund\", forventer vi, at refund vil blive oprettet, pengene vil blive tilbagef\u00f8rt til kunden, og vi f\u00e5r h\u00e6ndelsen \"money_refunded\". Men det er ikke sikkert, derfor er overv\u00e5gning n\u00f8dvendig.<\/p>\n<h3>Nuancer<\/h3>\n<p>\nDer er et ret \u00e5benlyst problem: hvis du l\u00e6ser fra emnet sekventielt, og du har en d\u00e5rlig besked, falder forbrugeren, og s\u00e5 kan du ikke komme videre. Du skal <strong>stoppe alle forbrugere<\/strong>, committe offset'en fremad for at forts\u00e6tte med at l\u00e6se.<\/p>\n<p>Vi vidste om dette, vi havde taget h\u00f8jde for det, og det skete alligevel. Og dette skete, fordi h\u00e6ndelsen var gyldig i forhold til events-bus, h\u00e6ndelsen var gyldig i forhold til applikationsvalidereren, men den var ikke gyldig i forhold til PostgreSQL, fordi vi i et system havde MySQL med UNSIGNED INT, mens det nyskrivne system havde PostgreSQL bare med INT. Dens st\u00f8rrelse er lidt mindre, og Id kunne ikke v\u00e6re plads. Symfony d\u00f8de med en undtagelse. Vi fangede selvf\u00f8lgelig undtagelsen, fordi vi havde taget h\u00f8jde for den, og vi havde planlagt at committe denne offset, men f\u00f8r det ville vi gerne inkrementere problemmeteret, da beskeden blev behandlet med fejl. T\u00e6llerne i dette projekt ligger ogs\u00e5 i databasen, men Symfony havde allerede afsluttet kommunikationen med databasen, og den anden undtagelse dr\u00e6bte hele processen uden chancer for at committe offset.<\/p>\n<p>I et stykke tid l\u00e5 servicen stille \u2014 heldigvis er det ikke s\u00e5 alvorligt med Kafka, fordi beskederne forbliver. N\u00e5r arbejdet genoptages, kan de l\u00e6ses igen. Det er praktisk.<\/p>\n<p>Kafka har muligheden gennem tooling at s\u00e6tte et vilk\u00e5rligt offset. Men for at g\u00f8re dette skal alle forbrugere stoppes \u2014 i vores tilf\u00e6lde forberede en separat udgivelse, hvor der ikke vil v\u00e6re forbrugere, redeployments. S\u00e5 kan offset'en justeres i Kafka gennem tooling, og beskeden vil g\u00e5 igennem.<\/p>\n<p>En anden nuance \u2014 <strong>replication log vs rdkafka.so<\/strong>\u00a0\u2014 relateret til vores projekts specifikationer. Vi bruger PHP, og i PHP kommunikerer alle biblioteker typisk med Kafka gennem rdkafka.so-repositoriet, og derefter er der en form for indpakning. M\u00e5ske er det vores personlige udfordringer, men det viste sig, at det ikke er s\u00e5 let at genl\u00e6se en sektion, der allerede er l\u00e6st. Generelt havde vi softwareproblemer.<\/p>\n<p>N\u00e5r man ser p\u00e5 s\u00e6regenhederne ved arbejdet med partitions, st\u00e5r der direkte i dokumentationen <strong>consumers &gt;= topic partitions<\/strong>. Men jeg fandt f\u00f8rst ud af dette meget senere, end jeg gerne ville. Hvis du \u00f8nsker at skalere og have to forbrugere, skal du have mindst to partitions. Det vil sige, hvis du havde \u00e9n partition, hvor der var samlet 20.000 beskeder, og du lavede en ny, vil antallet af beskeder ikke blive udlignet med det samme. Derfor, for at have to parallelle forbrugere, skal du arbejde med partitions.<\/p>\n<h2>Overv\u00e5gning<\/h2>\n<p>\nJeg t\u00e6nker, baseret p\u00e5 hvordan vi overv\u00e5ger, vil det blive endnu klarere, hvilke problemer der findes med den nuv\u00e6rende tilgang.<\/p>\n<p>For eksempel t\u00e6ller vi, hvor mange varer der for nylig har \u00e6ndret status i databasen, og i overensstemmelse med disse \u00e6ndringer burde der v\u00e6re sket begivenheder, og vi sender dette antal til vores overv\u00e5gningssystem. Derefter f\u00e5r vi et andet tal fra Kafka, hvor mange begivenheder der faktisk blev registreret. Det er tydeligt, at forskellen mellem disse to tal altid skal v\u00e6re nul.<\/p>\n<p><img decoding=\"async\" alt=\"Erfaring med udviklingen af Refund Tool-servicen med asynkront API baseret p\u00e5 Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/07d8ed08514f2fb97d9019466b96342c.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nDerudover skal vi overv\u00e5ge, hvordan det g\u00e5r med producenten, om events-bus har modtaget beskeder, og hvordan det g\u00e5r med forbrugeren. For eksempel ser det godt ud for Refund Tool, men BOB har tydeligvis nogle problemer (de bl\u00e5 toppe).<\/p>\n<p><img decoding=\"async\" alt=\"Erfaring med udviklingen af Refund Tool-servicen med asynkront API baseret p\u00e5 Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/57112dbe70d2b388c53f19c34cca6f63.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nJeg har allerede n\u00e6vnt consumer-group lag. Groft sagt er det antallet af ul\u00e6ste beskeder. Generelt fungerer vores forbrugere hurtigt, derfor er laget normalt 0, men der kan undertiden v\u00e6re en kortvarig spids. Kafka kan h\u00e5ndtere dette ud af boksen, men der skal angives et interval. <\/p>\n<p>Der er et projekt <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/linkedin\/Burrow\">Burrow<\/a><\/noindex>, som giver dig mere information om Kafka. Det returnerer blot status via API for consumer-group og viser, hvordan det g\u00e5r med denne gruppe. Derudover er der OK og Failed, men ogs\u00e5 warning, s\u00e5 du kan finde ud af, at dine forbrugere ikke kan f\u00f8lge med tempoet for produktionen \u2014 de f\u00e5r ikke l\u00e6st det, der skrives. Systemet er ret smart og nemt at bruge. <\/p>\n<p><img decoding=\"async\" alt=\"Erfaring med udviklingen af Refund Tool-servicen med asynkront API baseret p\u00e5 Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/cece8495801e187b155487a802e1a35a.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nS\u00e5dan ser svaret fra API ud. Her er gruppen bob-live-fifa, partition refund.update.v1, status OK, lag 0 \u2014 den seneste endelige offset er s\u00e5dan.<\/p>\n<p><img decoding=\"async\" alt=\"Erfaring med udviklingen af Refund Tool-servicen med asynkront API baseret p\u00e5 Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/1538b2ccc390e9b83075f54566f1039b.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nOverv\u00e5gning <strong>updated_at SLA (stuck)<\/strong> Jeg har allerede n\u00e6vnt det. For eksempel er varen g\u00e5et ind i status, at den er klar til returnering. Vi s\u00e6tter en Cron, der siger, at hvis denne genstand ikke er g\u00e5et til refund inden for 5 minutter (vi refunderer pengene gennem betalingssystemerne meget hurtigt), s\u00e5 er der helt sikkert noget, der er g\u00e5et galt, og det er definitivt en sag for supporten. Derfor tager vi bare Cron, som l\u00e6ser disse ting, og hvis de er over 0, sender den en alert.<\/p>\n<p><b>For at opsummere er det praktisk at bruge h\u00e6ndelser, n\u00e5r<\/b>:<\/p>\n<ul>\n<li>informationen er n\u00f8dvendig for flere systemer;<\/li>\n<li>resultatet af behandlingen ikke er vigtigt;<\/li>\n<li>der er f\u00e5 h\u00e6ndelser, eller h\u00e6ndelserne er sm\u00e5. <\/li>\n<\/ul>\n<blockquote><p>Det kan synes, at artiklen har et ret konkret emne - asynkron API p\u00e5 Kafka, men i den forbindelse vil jeg straks anbefale mange ting.<br \/>\nFor det f\u00f8rste er den n\u00e6ste <noindex><a rel=\"nofollow\" href=\"https:\/\/www.highload.ru\/\">HighLoad++<\/a><\/noindex> vi skal ikke vente til november, allerede i april vil der v\u00e6re en version i Sankt Petersborg, og i juni vil vi tale om h\u00f8je belastninger i Novosibirsk.<br \/>\nFor det andet indg\u00e5r taleren Sergey Zayka i det programkomit\u00e9 for vores nye konference om vidensstyring <noindex><a rel=\"nofollow\" href=\"https:\/\/knowledgeconf.ru\/2019\">KnowledgeConf<\/a><\/noindex>. Konferencen er en-dags, finder sted den 26. april, men programmet er meget fyldt.<br \/>\nOg ogs\u00e5 i maj vil der v\u00e6re <noindex><a rel=\"nofollow\" href=\"https:\/\/phprussia.ru\/2019\">PHP Russia<\/a><\/noindex> og\u00a0<noindex><a rel=\"nofollow\" href=\"https:\/\/ritfest.ru\/2019\">RIT++<\/a><\/noindex> (med DevOpsConf inkluderet) - der kan du stadig foresl\u00e5 dit emne, dele din erfaring og klage over dine opn\u00e5ede skader.<\/p><\/blockquote>\n<p>Kilde: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/oleg-bunin\/blog\/445424\/\">habr.com<\/a><\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u0427\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0442\u0430\u043a\u0443\u044e \u0431\u043e\u043b\u044c\u0448\u0443\u044e \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u044e \u043a\u0430\u043a Lamoda \u0441\u00a0\u043e\u0442\u043b\u0430\u0436\u0435\u043d\u043d\u044b\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u043c \u0438\u00a0\u0434\u0435\u0441\u044f\u0442\u043a\u0430\u043c\u0438 \u0432\u0437\u0430\u0438\u043c\u043e\u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043c\u0435\u043d\u044f\u0442\u044c \u043f\u043e\u0434\u0445\u043e\u0434? \u041c\u043e\u0442\u0438\u0432\u0430\u0446\u0438\u044f \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0441\u043e\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u043e \u0440\u0430\u0437\u043d\u0430\u044f: \u043e\u0442\u00a0\u0437\u0430\u043a\u043e\u043d\u043e\u0434\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u0434\u043e\u00a0\u043f\u0440\u0438\u0441\u0443\u0449\u0435\u0433\u043e \u0432\u0441\u0435\u043c \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0438\u0441\u0442\u0430\u043c \u0436\u0435\u043b\u0430\u043d\u0438\u044f \u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c. \u041d\u043e\u00a0\u044d\u0442\u043e \u0432\u043e\u0432\u0441\u0435 \u043d\u0435\u00a0\u0437\u043d\u0430\u0447\u0438\u0442, \u0447\u0442\u043e \u043d\u0435\u043b\u044c\u0437\u044f \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u044b\u0432\u0430\u0442\u044c \u043d\u0430\u00a0\u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u0443\u044e \u0432\u044b\u0433\u043e\u0434\u0443. \u0412\u00a0\u0447\u0435\u043c \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e \u043c\u043e\u0436\u043d\u043e \u0432\u044b\u0438\u0433\u0440\u0430\u0442\u044c, \u0435\u0441\u043b\u0438 \u0432\u043d\u0435\u0434\u0440\u0438\u0442\u044c events-driven API \u043d\u0430\u00a0Kafka, \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0435\u0442 \u0421\u0435\u0440\u0433\u0435\u0439 \u0417\u0430\u0438\u043a\u0430 (fewald). \u041f\u0440\u043e \u043d\u0430\u0431\u0438\u0442\u044b\u0435 \u0448\u0438\u0448\u043a\u0438 \u0438\u00a0\u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u044b\u0435 \u043e\u0442\u043a\u0440\u044b\u0442\u0438\u044f \u0442\u043e\u0436\u0435 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":22763,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-30778","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.3 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u0427\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0442\u0430\u043a\u0443\u044e \u0431\u043e\u043b\u044c\u0448\u0443\u044e \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u044e \u043a\u0430\u043a Lamoda \u0441 \u043e\u0442\u043b\u0430\u0436\u0435\u043d\u043d\u044b\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u043c \u0438 \u0434\u0435\u0441\u044f\u0442\u043a\u0430\u043c\u0438 \u0432\u0437\u0430\u0438\u043c\u043e\u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043c\u0435\u043d\u044f\u0442\u044c \u043f\u043e\u0434\u0445\u043e\u0434?\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/da\/blog\/administrirovanie\/opyt-razrabotki-servisa-refund-tool-s-asinhronnym-api-na-kafka\" \/>\n\t\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.3\" \/>\n\t\t<meta property=\"og:locale\" content=\"da_DK\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47\u041e\u043f\u044b\u0442 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0441\u0435\u0440\u0432\u0438\u0441\u0430 Refund Tool \u0441 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u043c API \u043d\u0430 Kafka | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u0427\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0442\u0430\u043a\u0443\u044e \u0431\u043e\u043b\u044c\u0448\u0443\u044e \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u044e \u043a\u0430\u043a Lamoda \u0441 \u043e\u0442\u043b\u0430\u0436\u0435\u043d\u043d\u044b\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u043c \u0438 \u0434\u0435\u0441\u044f\u0442\u043a\u0430\u043c\u0438 \u0432\u0437\u0430\u0438\u043c\u043e\u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043c\u0435\u043d\u044f\u0442\u044c \u043f\u043e\u0434\u0445\u043e\u0434?\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/da\/blog\/administrirovanie\/opyt-razrabotki-servisa-refund-tool-s-asinhronnym-api-na-kafka\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2019-10-31T18:37:23+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2019-10-31T18:37:23+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47Udviklingserfaring med refunderingstjenesten med asynkron API p\u00e5 Kafka | ProHoster","description":"Hvad kan f\u00e5 en s\u00e5 stor virksomhed som Lamoda med en str\u00f8mlinet proces og dusinvis af indbyrdes forbundne tjenester til at \u00e6ndre sin tilgang v\u00e6sentligt?","canonical_url":"https:\/\/prohoster.info\/da\/blog\/administrirovanie\/opyt-razrabotki-servisa-refund-tool-s-asinhronnym-api-na-kafka","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"da_DK","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47\u041e\u043f\u044b\u0442 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0441\u0435\u0440\u0432\u0438\u0441\u0430 Refund Tool \u0441 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u043c API \u043d\u0430 Kafka | ProHoster","og:description":"\u0427\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0442\u0430\u043a\u0443\u044e \u0431\u043e\u043b\u044c\u0448\u0443\u044e \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u044e \u043a\u0430\u043a Lamoda \u0441 \u043e\u0442\u043b\u0430\u0436\u0435\u043d\u043d\u044b\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u043c \u0438 \u0434\u0435\u0441\u044f\u0442\u043a\u0430\u043c\u0438 \u0432\u0437\u0430\u0438\u043c\u043e\u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043c\u0435\u043d\u044f\u0442\u044c \u043f\u043e\u0434\u0445\u043e\u0434?","og:url":"https:\/\/prohoster.info\/da\/blog\/administrirovanie\/opyt-razrabotki-servisa-refund-tool-s-asinhronnym-api-na-kafka","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2019-10-31T18:37:23+00:00","article:modified_time":"2019-10-31T18:37:23+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"30778","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":"2026-01-21 02:58:19","breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-03-01 03:29:56","updated":"2026-01-21 02:58:19","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/posts\/30778","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/comments?post=30778"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/posts\/30778\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/media\/22763"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/media?parent=30778"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/categories?post=30778"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/tags?post=30778"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}