{"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\/pl\/blog\/administrirovanie\/opyt-razrabotki-servisa-refund-tool-s-asinhronnym-api-na-kafka","title":{"rendered":"Do\u015bwiadczenie rozwoju us\u0142ugi Refund Tool z asynchronicznym API na Kafka","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>Co mo\u017ce sk\u0142oni\u0107 tak du\u017c\u0105 firm\u0119 jak Lamoda z ustabilizowanym procesem i dziesi\u0105tkami powi\u0105zanych us\u0142ug do istotnej zmiany podej\u015bcia? Motywacja mo\u017ce by\u0107 bardzo r\u00f3\u017cna: od legislacyjnej po przyrodzon\u0105 wszystkim programistom ch\u0119\u0107 eksperymentowania.<\/p>\n<p>Jednak to nie znaczy, \u017ce nie mo\u017cna liczy\u0107 na dodatkowe korzy\u015bci. O tym, co konkretnie mo\u017cna zyska\u0107, wdra\u017caj\u0105c API oparte na zdarzeniach w Kafce, opowie Sergey Zaika (<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/users\/fewald\/\" class=\"user_link\">fewald<\/a><\/noindex>). O nabitych szczo\u0142ach i interesuj\u0105cych odkryciach te\u017c na pewno b\u0119dzie \u2014 nie mo\u017ce si\u0119 obej\u015b\u0107 bez eksperyment\u00f3w.<\/p>\n<p><img decoding=\"async\" alt=\"Do\u015bwiadczenie rozwoju us\u0142ugi Refund Tool z asynchronicznym API na Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/7ab959ab45ec5c6565b35b18b361c0ea.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\n<em>Disclaimer: Ten artyku\u0142 oparty jest na materia\u0142ach z meetup'a, kt\u00f3ry Sergey przeprowadzi\u0142 w listopadzie 2018 roku na HighLoad++. \u017bywe do\u015bwiadczenia Lamody z Kafk\u0105 przyci\u0105gn\u0119\u0142y s\u0142uchaczy nie mniej ni\u017c inne wyk\u0142ady w harmonogramie. Uwa\u017camy, \u017ce to doskona\u0142y przyk\u0142ad tego, \u017ce zawsze mo\u017cna i nale\u017cy szuka\u0107 podobnie my\u015bl\u0105cych ludzi, a organizatorzy HighLoad++ b\u0119d\u0105 dalej stara\u0107 si\u0119 tworzy\u0107 atmosfer\u0119 do tego sprzyjaj\u0105c\u0105.<\/em><br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2>O procesie<\/h2>\n<p>\nLamoda to du\u017ca platforma e-commerce, kt\u00f3ra posiada w\u0142asne centrum kontaktowe, us\u0142ug\u0119 dostawy (i wiele partner\u00f3w), studio fotograficzne, ogromny magazyn i wszystko to dzia\u0142a na w\u0142asnym oprogramowaniu. Istniej\u0105 dziesi\u0105tki metod p\u0142atno\u015bci, partnerzy b2b, kt\u00f3rzy mog\u0105 korzysta\u0107 z cz\u0119\u015bci lub wszystkich tych us\u0142ug i chc\u0105 zna\u0107 aktualne informacje o swoich produktach. Dodatkowo, Lamoda dzia\u0142a w trzech krajach opr\u00f3cz Rosji i wsz\u0119dzie jest troch\u0119 inaczej. \u0141\u0105cznie mo\u017ce by\u0107 ponad sto sposob\u00f3w skonfigurowania nowego zam\u00f3wienia, kt\u00f3re musi by\u0107 przetwarzane na sw\u00f3j spos\u00f3b. Wszystko to dzia\u0142a dzi\u0119ki dziesi\u0105tkom us\u0142ug, kt\u00f3re komunikuj\u0105 si\u0119 czasami w spos\u00f3b nieoczywisty. Jest te\u017c centralny system, kt\u00f3rego g\u0142\u00f3wn\u0105 odpowiedzialno\u015bci\u0105 s\u0105 statusy zam\u00f3wie\u0144. Nazywamy go BOB, ja pracuj\u0119 z tym systemem.<\/p>\n<h2>Refund Tool with events-driven API <\/h2>\n<p>\nTermin events-driven jest do\u015b\u0107 wy\u015bwiechtany, troch\u0119 p\u00f3\u017aniej dok\u0142adniej okre\u015blimy, co przez to rozumiemy. Zaczn\u0119 od kontekstu, w kt\u00f3rym postanowili\u015bmy wypr\u00f3bowa\u0107 podej\u015bcie API oparte na zdarzeniach w Kafce. <\/p>\n<p><img decoding=\"async\" alt=\"Do\u015bwiadczenie rozwoju us\u0142ugi Refund Tool z asynchronicznym API na Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/3bfdce47dd8420fc63d84645e76de647.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nW ka\u017cdym sklepie, opr\u00f3cz zam\u00f3wie\u0144, za kt\u00f3re klienci p\u0142ac\u0105, s\u0105 momenty, kiedy od sklepu wymaga si\u0119 zwrotu pieni\u0119dzy, poniewa\u017c towar nie odpowiada\u0142 klientowi. Ten stosunkowo kr\u00f3tki proces: w razie potrzeby ustalamy informacje i przekazujemy pieni\u0105dze. <\/p>\n<p>Jednak zwrot sta\u0142 si\u0119 bardziej skomplikowany z powodu zmian w przepisach, wi\u0119c musieli\u015bmy wdro\u017cy\u0107 oddzielny mikroserwis do jego obs\u0142ugi.<\/p>\n<p><img decoding=\"async\" alt=\"Do\u015bwiadczenie rozwoju us\u0142ugi Refund Tool z asynchronicznym API na Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/0e358df5476e7448976e0f1147a103fb.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nNasza motywacja:<\/p>\n<ol>\n<li><strong>Ustawa FZ-54<\/strong>\u00a0\u2014 w skr\u00f3cie, ustawa wymaga zg\u0142aszania do urz\u0119d\u00f3w skarbowych ka\u017cdej transakcji pieni\u0119\u017cnej, niezale\u017cnie czy to zwrot, czy wp\u0142ata, w do\u015b\u0107 kr\u00f3tkim czasie SLA wynosz\u0105cym kilka minut. My, jako e-commerce, przeprowadzamy sporo operacji. Technicznie oznacza to now\u0105 odpowiedzialno\u015b\u0107 (a wi\u0119c nowy serwis) oraz modyfikacje w wszystkich zaanga\u017cowanych systemach.<\/li>\n<li><strong>BOB split<\/strong>\u00a0\u2014 wewn\u0119trzny projekt firmy maj\u0105cy na celu uwolnienie BOB od du\u017cej liczby zb\u0119dnych odpowiedzialno\u015bci i zmniejszenie jego z\u0142o\u017cono\u015bci.<\/li>\n<\/ol>\n<p>\n<img decoding=\"async\" alt=\"Do\u015bwiadczenie rozwoju us\u0142ugi Refund Tool z asynchronicznym API na Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/d3ecf9961bdb372fc5f84ee9389f73ef.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nNa tym schemacie przedstawione s\u0105 g\u0142\u00f3wne systemy Lamoda. Obecnie wi\u0119kszo\u015b\u0107 z nich przypomina raczej <strong>konstelacj\u0119 5-10 mikroserwis\u00f3w wok\u00f3\u0142 kurcz\u0105cego si\u0119 monolitu.<\/strong>Rosn\u0105 one powoli, ale staramy si\u0119 je zmniejsza\u0107, poniewa\u017c wdra\u017canie wyodr\u0119bnionego fragmentu w \u015brodku budzi obawy \u2013 nie mo\u017cna dopu\u015bci\u0107 do jego awarii. Wszystkie po\u0142\u0105czenia (strza\u0142ki) musimy rezerwowa\u0107, zak\u0142adaj\u0105c, \u017ce ka\u017cdy z nich mo\u017ce by\u0107 niedost\u0119pny.<\/p>\n<p>W BOB r\u00f3wnie\u017c jest sporo po\u0142\u0105cze\u0144: systemy p\u0142atno\u015bci, dostawy, powiadomie\u0144 itd. <\/p>\n<p>Technicznie BOB to:<\/p>\n<ul>\n<li>~150k linijek kodu + ~100k linijek test\u00f3w;<\/li>\n<li>php7.2 + Zend 1 &amp; Symfony Components 3;<\/li>\n<li>&gt;100 API &amp; ~50 integracji zewn\u0119trznych;<\/li>\n<li>4 kraje z w\u0142asn\u0105 logik\u0105 biznesow\u0105. <\/li>\n<\/ul>\n<p>\nWdra\u017canie BOB jest kosztowne i bolesne, ilo\u015b\u0107 kodu i rozwi\u0105zywanych przez niego zada\u0144 jest taka, \u017ce nikt nie mo\u017ce go w ca\u0142o\u015bci zrozumie\u0107. Og\u00f3lnie rzecz bior\u0105c, jest wiele powod\u00f3w, aby go upro\u015bci\u0107.<\/p>\n<h2>Proces zwrotu<\/h2>\n<p>\nPocz\u0105tkowo w proces zaanga\u017cowane s\u0105 dwa systemy: BOB i Payment. Teraz do\u0142\u0105czaj\u0105 jeszcze dwa:<\/p>\n<ul>\n<li>Us\u0142uga Fiskalizacji, kt\u00f3ra zajmie si\u0119 problemami z fiskalizacj\u0105 oraz komunikacj\u0105 z zewn\u0119trznymi serwisami.<\/li>\n<li>Narz\u0119dzie Zwrot\u00f3w, do kt\u00f3rego po prostu przenoszone s\u0105 nowe po\u0142\u0105czenia, aby nie rozbudowywa\u0107 BOB.<\/li>\n<\/ul>\n<p>\nTeraz proces wygl\u0105da tak:<\/p>\n<p><img decoding=\"async\" alt=\"Do\u015bwiadczenie rozwoju us\u0142ugi Refund Tool z asynchronicznym API na Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/13c02975881ad35c61304053c604cda3.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<\/p>\n<ol>\n<li>Do BOB przychodzi \u017c\u0105danie zwrotu pieni\u0119dzy.<\/li>\n<li>BOB informuje o tym Narz\u0119dzie Zwrot\u00f3w.<\/li>\n<li>Narz\u0119dzie Zwrot\u00f3w m\u00f3wi Payment: \u201eZwr\u00f3\u0107 pieni\u0105dze\u201d.<\/li>\n<li>Payment zwraca pieni\u0105dze.<\/li>\n<li>Narz\u0119dzie Zwrot\u00f3w i BOB synchronizuj\u0105 si\u0119 nawzajem z statusami, poniewa\u017c obecnie obie strony tego potrzebuj\u0105. Na razie nie jeste\u015bmy gotowi na ca\u0142kowite prze\u0142\u0105czenie si\u0119 na Narz\u0119dzie Zwrot\u00f3w, poniewa\u017c w BOB s\u0105 UI, raporty dla ksi\u0119gowo\u015bci oraz wiele danych, kt\u00f3re tak \u0142atwo nie przeniesiesz. Musimy pozosta\u0107 na dw\u00f3ch krzes\u0142ach.<\/li>\n<li>Wysy\u0142ane jest \u017c\u0105danie fiskalizacji.<\/li>\n<\/ol>\n<p>\nW rezultacie stworzyli\u015bmy na Kafka pewnego rodzaju szyn\u0119 zdarze\u0144 \u2013 event-bus, na kt\u00f3rej wszystko si\u0119 opiera. Hurra, teraz mamy jeden punkt awarii (sarkazm).<\/p>\n<p><img decoding=\"async\" alt=\"Do\u015bwiadczenie rozwoju us\u0142ugi Refund Tool z asynchronicznym API na Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/674edd7972998b4985071f5250612c7e.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nPlusy i minusy s\u0105 do\u015b\u0107 oczywiste. Zrobili\u015bmy szyn\u0119, co oznacza, \u017ce teraz wszystkie serwisy od niej zale\u017c\u0105. To upraszcza projektowanie, ale wprowadza w systemie jeden punkt awarii. Je\u015bli Kafka padnie, proces stanie.<\/p>\n<h2>Czym jest API oparte na zdarzeniach <\/h2>\n<p>\nDobr\u0105 odpowied\u017a na to pytanie mo\u017cna znale\u017a\u0107 w wyk\u0142adzie Martina Fowlera (GOTO 2017) <noindex><a rel=\"nofollow\" href=\"https:\/\/youtu.be\/STKCRSUsyPO\">\u201eWiele znacze\u0144 architektury opartej na zdarzeniach\u201d<\/a><\/noindex>. <\/p>\n<p>Kr\u00f3tko m\u00f3wi\u0105c, co zrobili\u015bmy:<\/p>\n<ol>\n<li>Obudowali\u015bmy wszystkie asynchroniczne wymiany przez <strong>przechowywanie zdarze\u0144<\/strong>. Zamiast informowa\u0107 przez sie\u0107 ka\u017cdego zainteresowanego konsumenta o zmianie statusu, zapisujemy w zcentralizowanym magazynie zdarzenie o zmianie stanu, a zainteresowani w temacie konsumenci odczytuj\u0105 wszystko, co si\u0119 pojawia.<\/li>\n<li>Zdarzenie (event) w tym przypadku to powiadomienie (<strong>notifications<\/strong>) o tym, \u017ce co\u015b gdzie\u015b si\u0119 zmieni\u0142o. Na przyk\u0142ad zmieni\u0142 si\u0119 status zam\u00f3wienia. Konsument, kt\u00f3remu wa\u017cne s\u0105 pewne dodatkowe dane zwi\u0105zane z zmian\u0105 statusu, a kt\u00f3rych nie ma w powiadomieniu, mo\u017ce sam pozna\u0107 ich stan.<\/li>\n<li>Maksymalna wersja to pe\u0142ne \u017ar\u00f3d\u0142o zdarze\u0144, <strong>przesy\u0142anie stanu<\/strong>, w kt\u00f3rym zdarzenie zawiera wszystkie informacje potrzebne do przetworzenia: sk\u0105d i w jaki status przeszli, jak dok\u0142adnie zmieni\u0142y si\u0119 dane itp. Pytanie tylko o celowo\u015b\u0107 i obj\u0119to\u015b\u0107 informacji, kt\u00f3r\u0105 mo\u017cesz sobie pozwoli\u0107 przechowywa\u0107.<\/li>\n<\/ol>\n<p>\nW ramach uruchomienia Refund Tool u\u017cyli\u015bmy trzeciej opcji. U\u0142atwi\u0142o to przetwarzanie zdarze\u0144, poniewa\u017c nie trzeba pozyskiwa\u0107 szczeg\u00f3\u0142owych informacji, a ponadto wykluczy\u0142o scenariusz, w kt\u00f3rym ka\u017cde nowe zdarzenie wywo\u0142uje lawin\u0119 uzupe\u0142niaj\u0105cych zapyta\u0144 GET od konsument\u00f3w.<\/p>\n<p>Us\u0142uga Refund Tool <strong>nie jest obci\u0105\u017cona<\/strong>, dlatego Kafka jest tam raczej pr\u00f3b\u0105 swoich si\u0142, ni\u017c konieczno\u015bci\u0105. Nie s\u0105dz\u0119, \u017ce gdyby serwis zwrotu \u015brodk\u00f3w sta\u0142 si\u0119 projektem o du\u017cym obci\u0105\u017ceniu, biznes by\u0142by z tego powodu zadowolony.<\/p>\n<h4>Asynchroniczna wymiana AS IS<\/h4>\n<p>\nDla asynchronicznych wymian, dzia\u0142 PHP zazwyczaj u\u017cywa RabbitMQ. Zbieramy dane do zapytania, wk\u0142adamy je do kolejki, a konsument tej samej us\u0142ugi je odczytuje i wysy\u0142a (lub nie wysy\u0142a). Dla samego API Lamoda aktywnie wykorzystuje Swagger. Projektujemy API, opisujemy je w Swaggerze, generujemy kod kliencki i serwerowy. U\u017cywamy r\u00f3wnie\u017c nieco rozszerzonego JSON RPC 2.0. <\/p>\n<p>Gdzie\u015b u\u017cywane s\u0105 szyny esb, kto\u015b korzysta z activeMQ, ale og\u00f3lnie, <strong>RabbitMQ \u2014 standard<\/strong>.<\/p>\n<h4>Async exchange DO<\/h4>\n<p>\nProjektuj\u0105c wymian\u0119 przez events-bus, zauwa\u017camy analogi\u0119. W podobny spos\u00f3b opisujemy przysz\u0142\u0105 wymian\u0119 danych poprzez opisy struktury eventu. Format yaml, kodogenaracja musieli\u015bmy zrobi\u0107 sami, generator wed\u0142ug specyfikacji tworzy DTO i uczy klient\u00f3w oraz serwery ich u\u017cywa\u0107. Generacja odbywa si\u0119 w dw\u00f3ch j\u0119zykach \u2014 <strong>golang i php<\/strong>. To pozwala utrzymywa\u0107 biblioteki w zgodno\u015bci. Generator jest napisany w golang, st\u0105d jego nazwa gogi.<\/p>\n<p>Event-sourcing w Kafka \u2014 rzecz typowa. Jest rozwi\u0105zanie od g\u0142\u00f3wnej wersji enterprise Kafka Confluent, jest <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/zalando\/nakadi\">nakadi<\/a><\/noindex>, rozwi\u0105zanie od naszych \u201ebraci\u201d z dziedziny Zalando. Nasza <strong>motywacja, by zacz\u0105\u0107 od vanilla Kafka<\/strong>\u00a0\u2014 to pozostawienie rozwi\u0105zania darmowym, a\u017c do momentu, gdy zdecydujemy, czy b\u0119dziemy je powszechnie stosowa\u0107, a tak\u017ce aby mie\u0107 przestrze\u0144 do manewru i poprawek: chcemy wsparcia dla naszego <strong>JSON RPC 2.0<\/strong>, generator\u00f3w pod dwa j\u0119zyki i zobaczymy, co jeszcze. <\/p>\n<p>Ironia polega na tym, \u017ce nawet w takim szcz\u0119\u015bliwym przypadku, kiedy istnieje mniej wi\u0119cej podobny biznes jak Zalando, kt\u00f3ry stworzy\u0142 podobne rozwi\u0105zanie, nie mo\u017cemy go efektywnie wykorzysta\u0107. <\/p>\n<p>Architektonicznie na pocz\u0105tku mamy taki wzorzec: czytamy bezpo\u015brednio z Kafka, ale zapisujemy tylko przez events-bus. Do czytania z Kafka jest wiele gotowego: brokerzy, load balancery, i jest w miar\u0119 gotowe do poziomego skalowania, co chcieli\u015bmy zachowa\u0107. Zapis z kolei zdecydowali\u015bmy si\u0119 zawin\u0105\u0107 przez jeden Gateway aka Events-bus, i oto dlaczego.<\/p>\n<h3>Events-bus<\/h3>\n<p>\nCzyli autobus wydarze\u0144. To po prostu stateless http gateway, kt\u00f3ry przejmuje na siebie kilka wa\u017cnych r\u00f3l:<\/p>\n<ul>\n<li><strong>Walidacja produkcji<\/strong>\u00a0\u2014 sprawdzamy, czy wydarzenia odpowiadaj\u0105 naszej specyfikacji.<\/li>\n<li><strong>System nadrz\u0119dny do wydarze\u0144<\/strong>, czyli to g\u0142\u00f3wny i jedyny system w firmie, kt\u00f3ry odpowiada na pytanie, kt\u00f3re wydarzenia z jakimi strukturami s\u0105 uznawane za wa\u017cne. W walidacji zawarte s\u0105 po prostu typy danych i enumy dla \u015bcis\u0142ej specyfikacji zawarto\u015bci. <\/li>\n<li><strong>Funkcja haszuj\u0105ca<\/strong> do sharding \u2014 struktura wiadomo\u015bci Kafka to key-value i wed\u0142ug hasha z key oblicza si\u0119, gdzie to umie\u015bci\u0107.<\/li>\n<\/ul>\n<p><\/p>\n<h3>Dlaczego<\/h3>\n<p>\nPracujemy w du\u017cej firmie z ustalonym procesem. Po co co\u015b zmienia\u0107? <strong>To eksperyment<\/strong>, i spodziewamy si\u0119 uzyska\u0107 kilka korzy\u015bci.<\/p>\n<h4>1:n+1 wymiany (jeden do wielu)<\/h4>\n<p>\nPrzy Kafka bardzo \u0142atwo pod\u0142\u0105czy\u0107 nowych konsument\u00f3w do API. <\/p>\n<p>Za\u0142\u00f3\u017cmy, \u017ce masz poradnik, kt\u00f3ry trzeba utrzymywa\u0107 aktualnym w kilku systemach naraz (i w jakich\u015b nowych). Kiedy\u015b wymy\u015blili\u015bmy pakiet, kt\u00f3ry realizowa\u0142 set-API, a g\u0142\u00f3wny system informowa\u0142 o adresach konsument\u00f3w. Teraz g\u0142\u00f3wny system wysy\u0142a aktualizacje do tematu, a wszyscy, kt\u00f3rzy s\u0105 zainteresowani, je czytaj\u0105. Pojawi\u0142 si\u0119 nowy system \u2014 pod\u0142\u0105czony do tematu. Tak, r\u00f3wnie\u017c pakiet, ale prostszy.<\/p>\n<p>W przypadku narz\u0119dzia zwrot\u00f3w, kt\u00f3re jest cz\u0119\u015bci\u0105 BOB, wygodnie jest nam zachowa\u0107 je synchronizowane przez Kafka. Payment informuje, \u017ce pieni\u0105dze zosta\u0142y zwr\u00f3cone: BOB, RT dowiaduj\u0105 si\u0119 o tym, zmieniaj\u0105 swoje statusy, a Fiscalization Service tak\u017ce o tym wie i wystawia paragon.<\/p>\n<p><img decoding=\"async\" alt=\"Do\u015bwiadczenie rozwoju us\u0142ugi Refund Tool z asynchronicznym API na Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/b01b22a333b58e87aeef0c52d40e6960.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nMamy plany stworzenia jednego Serwisu Powiadomie\u0144, kt\u00f3ry mia\u0142by informowa\u0107 klienta o nowo\u015bciach zwi\u0105zanych z jego zam\u00f3wieniem\/zwrotami. Teraz ta odpowiedzialno\u015b\u0107 jest rozproszona pomi\u0119dzy systemy. Wystarczy, \u017ce nauczymy Serwis Powiadomie\u0144 wy\u0142apywa\u0107 z Kafka odpowiednie informacje i na nie reagowa\u0107 (i wy\u0142\u0105czy\u0107 te powiadomienia w innych systemach). Nie b\u0119d\u0105 potrzebne \u017cadne nowe bezpo\u015brednie wymiany.<\/p>\n<h4>Zarz\u0105dzane danymi<\/h4>\n<p>\nInformacje mi\u0119dzy systemami staj\u0105 si\u0119 przejrzyste \u2014 niezale\u017cnie od tego, jaki \u201ekrwawy enterprise\u201d masz, i jak obszerny jest tw\u00f3j backlog. W Lamoda jest dzia\u0142 analizy danych, kt\u00f3ry zbiera dane z system\u00f3w i przekszta\u0142ca je w form\u0119 do ponownego wykorzystania, zar\u00f3wno dla biznesu, jak i dla system\u00f3w inteligentnych. Kafka pozwala szybko dostarczy\u0107 im wiele danych i utrzymywa\u0107 ten przep\u0142yw informacji aktualnym.<\/p>\n<h4>Dziennik replikacji<\/h4>\n<p>\nWiadomo\u015bci nie znikaj\u0105 po przeczytaniu, jak w RabbitMQ. Kiedy zdarzenie zawiera wystarczaj\u0105co du\u017co informacji do przetwarzania, mamy histori\u0119 ostatnich zmian obiektu, a je\u015bli chcemy, mo\u017cliwo\u015b\u0107 zastosowania tych zmian.<\/p>\n<p>Czas przechowywania dziennika replikacji zale\u017cy od intensywno\u015bci zapisu w tym temacie, Kafka pozwala elastycznie ustawi\u0107 limity zar\u00f3wno pod wzgl\u0119dem czasu przechowywania, jak i obj\u0119to\u015bci danych. W przypadku intensywnych temat\u00f3w wa\u017cne jest, aby wszyscy konsumenci zd\u0105\u017cyli przeczyta\u0107 informacje przed ich znikni\u0119ciem, nawet w przypadku kr\u00f3tkotrwa\u0142ej awarii. Zazwyczaj udaje si\u0119 przechowywa\u0107 dane przez\u00a0<strong>jednostki dni<\/strong>, co jest wystarczaj\u0105ce dla wsparcia. <\/p>\n<p><img decoding=\"async\" alt=\"Do\u015bwiadczenie rozwoju us\u0142ugi Refund Tool z asynchronicznym API na Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/0e08dd384155289123ebee96430c2370.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nTeraz troch\u0119 przegl\u0105du dokumentacji, dla tych, kt\u00f3rzy nie s\u0105 zaznajomieni z Kafka (rysunek te\u017c z dokumentacji)<\/p>\n<p>W AMQP istniej\u0105 kolejki: zapisujemy wiadomo\u015bci do kolejki dla konsumenta. Zazwyczaj jedna kolejka jest obs\u0142ugiwana przez jeden system z t\u0105 sam\u0105 logik\u0105 biznesow\u0105. Je\u015bli trzeba powiadomi\u0107 kilka system\u00f3w, mo\u017cna nauczy\u0107 aplikacj\u0119 zapisywa\u0107 w kilku kolejkach lub skonfigurowa\u0107 exchange z mechanizmem fanout, kt\u00f3ry je klonuje.<\/p>\n<p>W Kafka istnieje podobna abstrakcja <em>temat<\/em>, do kt\u00f3rego zapisujesz wiadomo\u015bci, ale one nie znikaj\u0105 po odczycie. Domy\u015blnie, przy po\u0142\u0105czeniu z Kafka, otrzymujesz wszystkie wiadomo\u015bci, a jednocze\u015bnie istnieje mo\u017cliwo\u015b\u0107 zapisania miejsca, w kt\u00f3rym si\u0119 zatrzyma\u0142e\u015b. To znaczy, \u017ce czytasz sekwencyjnie, mo\u017cesz nie oznacza\u0107 wiadomo\u015bci jako przeczytanej, ale zapisa\u0107 id, od kt\u00f3rego p\u00f3\u017aniej wznowisz czytanie. Id, na kt\u00f3rym si\u0119 zatrzyma\u0142e\u015b, nazywa si\u0119 offset (przesuni\u0119cie), a mechanizm to commit offset. <\/p>\n<p>Odpowiednio, mo\u017cna zrealizowa\u0107 r\u00f3\u017cn\u0105 logik\u0119. Na przyk\u0142ad, nasz BOB istnieje w 4 instancjach dla r\u00f3\u017cnych kraj\u00f3w \u2013 Lamoda jest w Rosji, Kazachstanie, Ukrainie i Bia\u0142orusi. Poniewa\u017c s\u0105 one wdra\u017cane oddzielnie, maj\u0105 nieco w\u0142asne konfiguracje i swoj\u0105 logik\u0119 biznesow\u0105. Wskazujemy w wiadomo\u015bci, do kt\u00f3rego kraju si\u0119 odnosi. Ka\u017cdy konsument BOB w ka\u017cdym kraju czyta z r\u00f3\u017cnymi groupId, a je\u015bli wiadomo\u015b\u0107 do niego nie pasuje, pomijaj\u0105 j\u0105, tj. od razu commituj\u0105 offset +1. Je\u015bli ten sam temat czyta nasza us\u0142uga p\u0142atno\u015bci, robi to z oddzieln\u0105 grup\u0105, wi\u0119c offsety si\u0119 nie krzy\u017cuj\u0105.<\/p>\n<p><b>Wymagania dotycz\u0105ce zdarze\u0144:<\/b><\/p>\n<ul>\n<li><strong>Pe\u0142no\u015b\u0107 danych. <\/strong>Chcieliby\u015bmy, aby w zdarzeniu znajdowa\u0142o si\u0119 wystarczaj\u0105co du\u017co danych, aby mo\u017cna je by\u0142o przetworzy\u0107. <\/li>\n<\/ul>\n<p><\/p>\n<ul>\n<li><strong>Integralno\u015b\u0107. <\/strong>Delegujemy Events-bus sprawdzenie, \u017ce zdarzenie jest sp\u00f3jne i mo\u017ce je przetworzy\u0107.<\/li>\n<li><strong>Kolejno\u015b\u0107 ma znaczenie. <\/strong>W przypadku zwrotu musimy pracowa\u0107 z histori\u0105. W przypadku powiadomie\u0144 kolejno\u015b\u0107 nie ma znaczenia, je\u015bli s\u0105 to jednorodne powiadomienia, e-mail b\u0119dzie taki sam, niezale\u017cnie od tego, kt\u00f3ry zam\u00f3wienie przyby\u0142o jako pierwszy. W przypadku zwrotu istnieje wyra\u017any proces, je\u015bli zmienisz kolejno\u015b\u0107, mog\u0105 wyst\u0105pi\u0107 wyj\u0105tki, zwrot nie zostanie utworzony lub nie zostanie przetworzony \u2013 przejdziemy do innego statusu.<\/li>\n<li><strong>Sp\u00f3jno\u015b\u0107. <\/strong>Mamy magazyn, a teraz zamiast API tworzymy zdarzenia. Potrzebujemy sposobu na szybkie i tanie przekazywanie naszym us\u0142ugom informacji o nowych zdarzeniach i zmianach w ju\u017c istniej\u0105cych. Osi\u0105gamy to dzi\u0119ki wsp\u00f3lnej specyfikacji w osobnym repozytorium git oraz generatorom kodu. Dlatego klienci i serwery w r\u00f3\u017cnych us\u0142ugach s\u0105 u nas zsynchronizowane.<\/li>\n<\/ul>\n<p><\/p>\n<h2>Kafka w Lamoda<\/h2>\n<p>\nMamy trzy instalacje Kafka: <\/p>\n<ol>\n<li>Logi;<\/li>\n<li>R&amp;D;<\/li>\n<li>Events-bus.<\/li>\n<\/ol>\n<p>\nDzi\u015b m\u00f3wimy tylko o ostatnim punkcie. W events-bus mamy niezbyt du\u017ce instalacje - 3 broker\u00f3w (serwery) i tylko 27 temat\u00f3w. Zazwyczaj jeden temat to jeden proces. Ale to delikatna kwestia, kt\u00f3r\u0105 za chwil\u0119 poruszymy.<\/p>\n<p><img decoding=\"async\" alt=\"Do\u015bwiadczenie rozwoju us\u0142ugi Refund Tool z asynchronicznym API na Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/f398852689b31429cc97b4cbffcabab5.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nPowy\u017cej wykres rps. Proces zwrot\u00f3w oznaczony jest turkusow\u0105 lini\u0105 (tak, tak, t\u0105 na osi X), a r\u00f3\u017cow\u0105 - proces aktualizacji tre\u015bci. <\/p>\n<p>Katalog Lamoda zawiera miliony produkt\u00f3w, a dane s\u0105 ci\u0105gle aktualizowane. Niekt\u00f3re kolekcje wychodz\u0105 z mody, w ich miejsce wprowadzane s\u0105 nowe, w katalogu ci\u0105gle pojawiaj\u0105 si\u0119 nowe modele. Staramy si\u0119 przewidzie\u0107, co b\u0119dzie interesowa\u0107 naszych klient\u00f3w jutro, dlatego ci\u0105gle kupujemy nowe rzeczy, robimy im zdj\u0119cia i aktualizujemy witryn\u0119. <\/p>\n<p>R\u00f3\u017cowe szczyty to aktualizacja produkt\u00f3w, czyli zmiany dotycz\u0105ce towar\u00f3w. Wida\u0107, \u017ce ch\u0142opaki fotografowa\u0142y, fotografowa\u0142y, a potem nagle! \u2014 za\u0142adowali paczk\u0119 zdarze\u0144.<\/p>\n<h2>Przyk\u0142ady u\u017cycia Lamoda Events<\/h2>\n<p>\nZbudowan\u0105 architektur\u0119 wykorzystujemy do nast\u0119puj\u0105cych operacji:<\/p>\n<ul>\n<li><strong>\u015aledzenie status\u00f3w zwrot\u00f3w<\/strong>: call-to-action i \u015bledzenie status\u00f3w ze wszystkich zaanga\u017cowanych system\u00f3w. P\u0142atno\u015b\u0107, statusy, fiskalizacja, powiadomienia. Tutaj wypr\u00f3bowali\u015bmy podej\u015bcie, stworzyli\u015bmy narz\u0119dzia, zebrali\u015bmy wszystkie b\u0142\u0119dy, napisali\u015bmy dokumentacj\u0119 i opowiedzieli\u015bmy kolegom, jak z tego korzysta\u0107.<\/li>\n<li><strong>Aktualizacja kart produktu: <\/strong>konfiguracja, metadane, specyfikacje. Czyta jeden system (kt\u00f3ry wy\u015bwietla), a pisze ich kilka.<\/li>\n<li><strong>Email, push i sms<\/strong>: zam\u00f3wienie zosta\u0142o zrealizowane, zam\u00f3wienie dotar\u0142o, zwrot zosta\u0142 przyj\u0119ty itd., jest ich wiele. <\/li>\n<li><strong>Stan, aktualizacja zapas\u00f3w<\/strong>\u00a0\u2014 ilo\u015bciowa aktualizacja nazw, po prostu liczby: dostawa do magazynu, zwrot. Musi by\u0107 tak, aby wszystkie systemy zwi\u0105zane z rezerwowaniem towaru operowa\u0142y maksymalnie aktualnymi danymi. Obecnie system aktualizacji zapas\u00f3w jest do\u015b\u0107 skomplikowany, Kafka pozwoli go upro\u015bci\u0107.<\/li>\n<li><strong>Analiza danych<\/strong> (Dzia\u0142 R&amp;D), narz\u0119dzia ML, analityka, statystyka. Chcemy, \u017ceby informacje by\u0142y przejrzyste - do tego Kafka jest dobrze dopasowana.<\/li>\n<\/ul>\n<p>\nTeraz bardziej interesuj\u0105ca cz\u0119\u015b\u0107 dotycz\u0105ca zbierania do\u015bwiadcze\u0144 i interesuj\u0105cych odkry\u0107, kt\u00f3re mia\u0142y miejsce w ci\u0105gu p\u00f3\u0142 roku.<\/p>\n<h2>Problemy projektowe<\/h2>\n<p>\nZa\u0142\u00f3\u017cmy, \u017ce chcemy stworzy\u0107 now\u0105 rzecz - na przyk\u0142ad, przenie\u015b\u0107 ca\u0142y proces dostawy na Kafka. Obecnie cz\u0119\u015b\u0107 procesu jest realizowana w Order Processing w BOB. Po przekazaniu zam\u00f3wienia do us\u0142ugi dostawy, jego przemieszczeniu na magazyn po\u015bredni i innych rzeczach istnieje model status\u00f3w. Jest ca\u0142y monolit, nawet dwa, a do tego mn\u00f3stwo API po\u015bwi\u0119conych dostawie. Wiedz\u0105 o dostawie znacznie wi\u0119cej. <\/p>\n<p>Wydaje si\u0119, \u017ce to podobne obszary, ale dla Order Processing w BOB i dla systemu dostawy statusy r\u00f3\u017cni\u0105 si\u0119. Na przyk\u0142ad, niekt\u00f3re firmy kurierskie nie wysy\u0142aj\u0105 status\u00f3w po\u015brednich, a tylko ko\u0144cowe: \u201edostarczono\u201d lub \u201ezgubiono\u201d. Inne, wr\u0119cz przeciwnie, bardzo szczeg\u00f3\u0142owo informuj\u0105 o przemieszczeniu towaru. Ka\u017cdy ma swoje zasady walidacji: dla niekt\u00f3rych, je\u015bli email jest wa\u017cny, to zostanie przetworzony; dla innych - nie wa\u017cny, ale zam\u00f3wienie i tak b\u0119dzie przetworzone, poniewa\u017c jest telefon do kontaktu, a niekt\u00f3rzy powiedz\u0105, \u017ce takie zam\u00f3wienie w og\u00f3le nie b\u0119dzie przetwarzane.<\/p>\n<h3>Strumie\u0144 danych<\/h3>\n<p>\nW przypadku Kafki pojawia si\u0119 pytanie o organizacj\u0119 strumienia danych. To zadanie wi\u0105\u017ce si\u0119 z wyborem strategii w kilku punktach, przejd\u017amy przez wszystkie z nich.<\/p>\n<h4>Do jednego topiku czy do r\u00f3\u017cnych?<\/h4>\n<p>\nMamy specyfikacj\u0119 zdarzenia. W BOB piszemy, \u017ce takie zam\u00f3wienie trzeba dostarczy\u0107 i wskazujemy: numer zam\u00f3wienia, jego sk\u0142ad, jakie\u015b SKU i kody kreskowe itd. Gdy towar przyb\u0119dzie do magazynu, dostawa b\u0119dzie mog\u0142a otrzyma\u0107 statusy, znaczniki czasu i wszystko co potrzebne. Ale p\u00f3\u017aniej chcemy w BOB otrzymywa\u0107 aktualizacje na temat tych danych. Pojawia si\u0119 wi\u0119c proces odwrotnego pozyskiwania danych z dostawy. Czy to to samo zdarzenie? Czy jest to osobna wymiana, kt\u00f3ra zas\u0142uguje na osobny topik?<\/p>\n<p>Prawdopodobnie b\u0119d\u0105 one bardzo podobne, a pokusa stworzenia jednego topiku nie jest bezpodstawna, poniewa\u017c osobny topik to osobni konsumenci, osobne konfiguracje, osobne generowanie tego wszystkiego. Ale nie ma pewno\u015bci.<\/p>\n<h4>Nowe pole czy nowe zdarzenie?<\/h4>\n<p>\nJednak je\u015bli u\u017cyjemy tych samych zdarze\u0144, pojawia si\u0119 inny problem. Na przyk\u0142ad nie wszystkie systemy dostawy mog\u0105 wygenerowa\u0107 taki DTO, kt\u00f3ry potrafi\u0142by generowa\u0107 BOB. Wysy\u0142amy im id, a oni ich nie zapisuj\u0105, poniewa\u017c ich nie potrzebuj\u0105, a z punktu widzenia rozpocz\u0119cia procesu event-bus to pole jest obowi\u0105zkowe. <\/p>\n<p>Je\u015bli wprowadzimy dla event-bus zasad\u0119, \u017ce to pole jest obowi\u0105zkowe, b\u0119dziemy zmuszeni w BOB lub w obs\u0142udze zdarzenia startowego ustawi\u0107 dodatkowe zasady walidacji. Walidacja zaczyna si\u0119 rozprzestrzenia\u0107 po serwisie - to nie jest zbyt wygodne.<\/p>\n<p>Kolejnym problemem jest pokusa inkrementalnego rozwoju. M\u00f3wi\u0105 nam, \u017ce trzeba doda\u0107 co\u015b do zdarzenia, a by\u0107 mo\u017ce, je\u015bli dobrze pomy\u015ble\u0107, powinno to by\u0107 oddzielne zdarzenie. Ale w naszym schemacie oddzielne zdarzenie to oddzielny temat. Oddzielny temat to ca\u0142y ten proces, kt\u00f3ry opisa\u0142em powy\u017cej. Programista ma pokus\u0119, aby po prostu doda\u0107 jeszcze jedno pole do schemy JSON i wygenerowa\u0107 j\u0105 ponownie.<\/p>\n<p>W przypadku refundacji przez p\u00f3\u0142 roku dotarli\u015bmy do zdarzenia zdarze\u0144. Mieli\u015bmy jedno meta-zdarzenie, kt\u00f3re nazywa si\u0119 refund update, w kt\u00f3rym by\u0142o pole type, opisuj\u0105ce, na czym w\u0142a\u015bciwie polega ta aktualizacja. Od tego mieli\u015bmy \"wspania\u0142e\" prze\u0142\u0105czniki z walidatorami, kt\u00f3re m\u00f3wi\u0142y, jak nale\u017cy walidowa\u0107 to zdarzenie z tym typem.<\/p>\n<h4>Wersjonowanie zdarze\u0144<\/h4>\n<p>\nDo walidacji wiadomo\u015bci w Kafka mo\u017cna u\u017cywa\u0107 <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.confluent.io\/current\/schema-registry\/docs\/index.html\">Avro<\/a><\/noindex>, ale trzeba to od razu uwzgl\u0119dni\u0107 i u\u017cy\u0107 Confluent. W naszym przypadku z wersjonowaniem musimy by\u0107 ostro\u017cni. Nie zawsze b\u0119dzie mo\u017cliwe przeczytanie wiadomo\u015bci z logu replikacji, poniewa\u017c model \u201eodjecha\u0142\u201d. W zasadzie trzeba budowa\u0107 wersje tak, aby model by\u0142 wstecznie kompatybilny: na przyk\u0142ad uczyni\u0107 pole tymczasowo nieobowi\u0105zkowym. Je\u015bli r\u00f3\u017cnice s\u0105 zbyt du\u017ce, zaczynamy pisa\u0107 w nowy temat, a klient\u00f3w przesiadujemy, gdy przeczytaj\u0105 stary.<\/p>\n<h4>Gwarancja kolejno\u015bci odczytu partycji<\/h4>\n<p>\nTematy wewn\u0105trz Kafka s\u0105 podzielone na partycje. Nie jest to zbyt wa\u017cne, gdy projektujemy byty i wymiany, ale wa\u017cne, gdy decydujemy, jak to konsumowa\u0107 i skalowa\u0107.<\/p>\n<p>W normalnym przypadku wysy\u0142asz do Kafka jeden temat. Domy\u015blnie u\u017cywany jest jeden partycja, a wszystkie wiadomo\u015bci tego tematu trafiaj\u0105 do niej. Konsument odpowiednio kolejno odczytuje te wiadomo\u015bci. Za\u0142\u00f3\u017cmy, \u017ce teraz musisz rozszerzy\u0107 system tak, aby wiadomo\u015bci czyta\u0142y dwa r\u00f3\u017cne konsumenty. Je\u015bli na przyk\u0142ad wysy\u0142asz SMS, mo\u017cesz powiedzie\u0107 Kafka, aby utworzy\u0142a dodatkow\u0105 partycj\u0119, a Kafka zacznie rozdziela\u0107 wiadomo\u015bci na dwie cz\u0119\u015bci - po\u0142ow\u0119 tam, po\u0142ow\u0119 tutaj. <\/p>\n<p>Jak Kafka je dzieli? Ka\u017cda wiadomo\u015b\u0107 ma cia\u0142o (w kt\u00f3rym przechowujemy JSON) oraz klucz. Do tego klucza mo\u017cna zastosowa\u0107 funkcj\u0119 haszuj\u0105c\u0105, kt\u00f3ra b\u0119dzie okre\u015bla\u0107, do kt\u00f3rej partycji trafi wiadomo\u015b\u0107.<\/p>\n<p>W naszym przypadku z refundacjami jest to wa\u017cne, je\u015bli bierzemy dwie partycje, to istnieje szansa, \u017ce r\u00f3wnoleg\u0142y konsument przetworzy drugie zdarzenie wcze\u015bniej ni\u017c pierwsze, co mo\u017ce by\u0107 problematyczne. Funkcja haszuj\u0105ca gwarantuje, \u017ce wiadomo\u015bci z tym samym kluczem trafi\u0105 do tej samej partycji. <\/p>\n<h4>Zdarzenia vs polecenia<\/h4>\n<p>\nTo jeszcze jeden problem, z kt\u00f3rym si\u0119 spotkali\u015bmy. Zdarzenie to pewne wydarzenie: m\u00f3wimy, \u017ce co\u015b gdzie\u015b si\u0119 wydarzy\u0142o (something_happened), na przyk\u0142ad, przedmiot zosta\u0142 anulowany lub wyst\u0105pi\u0142 zwrot. Je\u015bli kto\u015b s\u0142ucha tych zdarze\u0144, to po \"przedmiot zosta\u0142 anulowany\" zostanie utworzona encja zwrotu, a \"wyst\u0105pi\u0142 zwrot\" zostanie zapisane gdzie\u015b w ustawieniach.<\/p>\n<p>Jednak zazwyczaj, gdy projektujesz zdarzenia, nie chcesz ich pisa\u0107 na darmo - zak\u0142adasz, \u017ce kto\u015b je przeczyta. Jest du\u017ca pokusa, aby napisa\u0107 co\u015b innego ni\u017c something_happened (item_canceled, refund_refunded), a raczej something_should_be_done. Na przyk\u0142ad, przedmiot jest gotowy do zwrotu.<\/p>\n<p>Z jednej strony to sugeruje, jak zdarzenie zostanie wykorzystane. Z drugiej strony, to znacznie mniej przypomina normaln\u0105 nazw\u0119 zdarzenia. Poza tym, to ju\u017c blisko do komendy do_something. Jednak nie masz gwarancji, \u017ce to zdarzenie kto\u015b przeczyta\u0142; a je\u015bli przeczyta\u0142, to przeczyta\u0142 je poprawnie; a je\u015bli przeczyta\u0142 poprawnie, to co\u015b zrobi\u0142, i to co\u015b przesz\u0142o pomy\u015blnie. W momencie, gdy zdarzenie staje si\u0119 do_something, potrzeba zwrotnej informacji staje si\u0119 konieczna, i to jest problem.<\/p>\n<p><img decoding=\"async\" alt=\"Do\u015bwiadczenie rozwoju us\u0142ugi Refund Tool z asynchronicznym API na Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/b755d91208092bd9791a41ce4633fb48.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nW asynchronicznej wymianie w RabbitMQ, gdy przeczytasz wiadomo\u015b\u0107, p\u00f3jdziesz na http, masz reakcj\u0119 - przynajmniej, \u017ce wiadomo\u015b\u0107 zosta\u0142a przyj\u0119ta. Kiedy zapisa\u0142e\u015b w Kafka, jest wiadomo\u015b\u0107, \u017ce zapisa\u0142e\u015b w Kafka, ale nie wiesz, jak zosta\u0142a przetworzona. <\/p>\n<p>W zwi\u0105zku z tym w naszym przypadku musieli\u015bmy wprowadzi\u0107 odpowiednie zdarzenie i skonfigurowa\u0107 monitoring, aby sprawdzi\u0107, czy po wyst\u0105pieniu okre\u015blonej liczby zdarze\u0144 w okre\u015blonym czasie powinno nadej\u015b\u0107 tyle samo odpowiedzi. Je\u015bli to nie nast\u0105pi, to wydaje si\u0119, \u017ce co\u015b posz\u0142o nie tak. Na przyk\u0142ad, je\u015bli wysy\u0142amy zdarzenie \u201eitem_ready_to_refund\u201d, spodziewamy si\u0119, \u017ce zwrot zostanie zrealizowany, klient odzyska pieni\u0105dze, a my otrzymamy zdarzenie \u201emoney_refunded\u201d. Ale to nie jest pewne, dlatego potrzebny jest monitoring.<\/p>\n<h3>Niemo\u017cno\u015bci<\/h3>\n<p>\nJest jeden do\u015b\u0107 oczywisty problem: je\u015bli odczytujesz z tematu sekwencyjnie, a masz jakie\u015b z\u0142e wiadomo\u015bci, konsument pada i dalej nie p\u00f3jdziesz. Musisz <strong>zatrzyma\u0107 wszystkich konsument\u00f3w<\/strong>, zatwierdzi\u0107 offset dalej, aby m\u00f3c kontynuowa\u0107 odczytywanie.<\/p>\n<p>Wiedzieli\u015bmy o tym, przewidzieli\u015bmy to, a mimo to to si\u0119 wydarzy\u0142o. A wydarzy\u0142o si\u0119 to, poniewa\u017c zdarzenie by\u0142o poprawne z punktu widzenia events-bus, zdarzenie by\u0142o poprawne z punktu widzenia walidatora aplikacji, ale nie by\u0142o poprawne z punktu widzenia PostgreSQL, poniewa\u017c w jednym systemie mieli\u015bmy MySQL z UNSIGNED INT, a w nowym systemie by\u0142 PostgreSQL z INT. Jego rozmiar jest nieco mniejszy i Id nie zmie\u015bci\u0142 si\u0119. Symfony zako\u0144czy\u0142o dzia\u0142anie z wyj\u0105tkiem. Oczywi\u015bcie z\u0142apali\u015bmy wyj\u0105tek, poniewa\u017c si\u0119 na niego przygotowali\u015bmy i zamierzali\u015bmy zatwierdzi\u0107 ten offset, ale wcze\u015bniej chcieli\u015bmy inkrementowa\u0107 licznik problem\u00f3w, gdy\u017c wiadomo\u015b\u0107 zosta\u0142a nieprawid\u0142owo przetworzona. Liczniki w tym projekcie tak\u017ce s\u0105 przechowywane w bazie, a Symfony ju\u017c zako\u0144czy\u0142o komunikacj\u0119 z baz\u0105, a drugi wyj\u0105tek zabi\u0142 ca\u0142y proces bez szans na zatwierdzenie offsetu.<\/p>\n<p>Przez jaki\u015b czas serwis przesta\u0142 dzia\u0142a\u0107 \u2014 na szcz\u0119\u015bcie z Kafka nie jest to takie straszne, poniewa\u017c wiadomo\u015bci pozostaj\u0105. Kiedy praca zostanie wznowiona, b\u0119dzie mo\u017cna je doko\u0144czy\u0107. To jest wygodne.<\/p>\n<p>Kafka ma mo\u017cliwo\u015b\u0107 ustawienia dowolnego offsetu przez tooling. Ale aby to zrobi\u0107, trzeba zatrzyma\u0107 wszystkich konsument\u00f3w \u2014 w naszym przypadku przygotowa\u0107 oddzielne wydanie, w kt\u00f3rym nie b\u0119dzie konsument\u00f3w ani redeployments. Wtedy przez narz\u0119dzia Kafka mo\u017cna przesun\u0105\u0107 offset i wiadomo\u015b\u0107 przejdzie.<\/p>\n<p>Inny niuans \u2014 <strong>log replikacji vs rdkafka.so<\/strong>\u00a0\u2014 zwi\u0105zane z charakterystyk\u0105 naszego projektu. U nas PHP, a w PHP, jak zwykle, wszystkie biblioteki komunikuj\u0105 si\u0119 z Kafka przez repozytorium rdkafka.so, a potem nast\u0119puje jaka\u015b nak\u0142adka. Mo\u017ce to nasze osobiste trudno\u015bci, ale okaza\u0142o si\u0119, \u017ce ponowne przeczytanie fragmentu ju\u017c przeczytanego nie jest takie proste. Og\u00f3lnie mieli\u015bmy problemy programowe.<\/p>\n<p>Wracaj\u0105c do specyfiki pracy z partitions, w dokumentacji jest napisane <strong>consumers &gt;= topic partitions<\/strong>. Ale dowiedzia\u0142em si\u0119 o tym znacznie p\u00f3\u017aniej, ni\u017c bym chcia\u0142. Je\u015bli chcesz si\u0119 skalowa\u0107 i mie\u0107 dw\u00f3ch konsument\u00f3w, potrzebujesz co najmniej dw\u00f3ch partitions. To znaczy, je\u015bli mia\u0142e\u015b jedn\u0105 partycj\u0119, w kt\u00f3rej zgromadzi\u0142o si\u0119 20 tysi\u0119cy wiadomo\u015bci, a stworzy\u0142e\u015b now\u0105, liczba wiadomo\u015bci nie wyr\u00f3wna si\u0119 szybko. Dlatego, aby mie\u0107 dw\u00f3ch r\u00f3wnoleg\u0142ych konsument\u00f3w, musisz zrozumie\u0107 dzia\u0142anie partitions.<\/p>\n<h2>Monitoring<\/h2>\n<p>\nMy\u015bl\u0119, \u017ce na podstawie tego, jak monitorujemy, b\u0119dzie jeszcze ja\u015bniej, jakie problemy wyst\u0119puj\u0105 w istniej\u0105cym podej\u015bciu.<\/p>\n<p>Na przyk\u0142ad, liczymy, ile produkt\u00f3w w bazie niedawno zmieni\u0142o status, i odpowiednio, na podstawie tych zmian, powinny zdarzy\u0107 si\u0119 wydarzenia, i wysy\u0142amy t\u0119 liczb\u0119 do naszego systemu monitorowania. Nast\u0119pnie z Kafka otrzymujemy drug\u0105 liczb\u0119, ile wiadomo\u015bci faktycznie zosta\u0142o zapisanych. Oczywi\u015bcie, r\u00f3\u017cnica mi\u0119dzy tymi dwiema liczbami zawsze powinna wynosi\u0107 zero.<\/p>\n<p><img decoding=\"async\" alt=\"Do\u015bwiadczenie rozwoju us\u0142ugi Refund Tool z asynchronicznym API na Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/07d8ed08514f2fb97d9019466b96342c.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nPonadto potrzeba monitorowa\u0107, jak radzi sobie producent, czy events-bus odebra\u0142 wiadomo\u015bci i jak radzi sobie konsument. Na przyk\u0142ad, na poni\u017cszych wykresach u Refund Tool wszystko jest w porz\u0105dku, a u BOB wyra\u017anie wyst\u0119puj\u0105 jakie\u015b problemy (niebieskie szczyty).<\/p>\n<p><img decoding=\"async\" alt=\"Do\u015bwiadczenie rozwoju us\u0142ugi Refund Tool z asynchronicznym API na Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/57112dbe70d2b388c53f19c34cca6f63.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nJu\u017c wspomina\u0142em o consumer-group lag. M\u00f3wi\u0105c prosto, to liczba nieprzeczytanych wiadomo\u015bci. Og\u00f3lnie nasi konsumenci dzia\u0142aj\u0105 szybko, wi\u0119c lag zazwyczaj wynosi 0, ale czasami mo\u017ce wyst\u0105pi\u0107 kr\u00f3tkotrwa\u0142y szczyt. Kafka potrafi to rozwi\u0105za\u0107 z pude\u0142ka, ale musisz ustawi\u0107 jaki\u015b interwa\u0142. <\/p>\n<p>Jest projekt <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/linkedin\/Burrow\">Burrow<\/a><\/noindex>, kt\u00f3ry dostarczy Ci wi\u0119cej informacji na temat Kafka. Po prostu przez API dla consumer-group zwraca status, jak radzi sobie ta grupa. Opr\u00f3cz OQ i Failed jest te\u017c warning, dzi\u0119ki czemu mo\u017cesz dowiedzie\u0107 si\u0119, \u017ce Twoi konsumenci maj\u0105 trudno\u015bci z tempem produkcji \u2014 nie nad\u0105\u017caj\u0105 z odczytywaniem tego, co jest zapisywane. System jest do\u015b\u0107 inteligentny, \u0142atwo go u\u017cywa\u0107. <\/p>\n<p><img decoding=\"async\" alt=\"Do\u015bwiadczenie rozwoju us\u0142ugi Refund Tool z asynchronicznym API na Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/cece8495801e187b155487a802e1a35a.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nTak wygl\u0105da odpowied\u017a przez API. Tutaj grupa bob-live-fifa, partycja refund.update.v1, status OK, lag 0 \u2014 ostatni ko\u0144cowy offset to ten.<\/p>\n<p><img decoding=\"async\" alt=\"Do\u015bwiadczenie rozwoju us\u0142ugi Refund Tool z asynchronicznym API na Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/1538b2ccc390e9b83075f54566f1039b.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nMonitoring <strong>updated_at SLA (stuck)<\/strong> Ju\u017c wspomina\u0142em. Na przyk\u0142ad, towar przeszed\u0142 w status, \u017ce jest gotowy do zwrotu. Ustawiamy Cron, kt\u00f3ry m\u00f3wi, \u017ce je\u015bli w ci\u0105gu 5 minut ten obiekt nie przeszed\u0142 w refund (zwracamy pieni\u0105dze przez systemy p\u0142atno\u015bci bardzo szybko), to na pewno co\u015b posz\u0142o nie tak, i to z pewno\u015bci\u0105 jest przypadek dla supportu. Dlatego po prostu bierzemy Cron, kt\u00f3ry czyta takie rzeczy, i je\u015bli ich liczba jest wi\u0119ksza od 0, to wysy\u0142a alert.<\/p>\n<p><b>Podsumowuj\u0105c, korzystanie z zdarze\u0144 jest wygodne, gdy<\/b>:<\/p>\n<ul>\n<li>informacja jest potrzebna kilku systemom;<\/li>\n<li>wynik przetwarzania nie ma znaczenia;<\/li>\n<li>jest ma\u0142o zdarze\u0144 lub zdarzenia s\u0105 ma\u0142e. <\/li>\n<\/ul>\n<blockquote><p>Zdawa\u0142oby si\u0119, \u017ce temat artyku\u0142u jest do\u015b\u0107 konkretny \u2014 asynchroniczne API na Kafka, ale w zwi\u0105zku z nim chc\u0119 od razu wiele rzeczy poleci\u0107.<br \/>\nPo pierwsze, nast\u0119pny <noindex><a rel=\"nofollow\" href=\"https:\/\/www.highload.ru\/\">HighLoad++<\/a><\/noindex> nie trzeba czeka\u0107 do listopada, ju\u017c w kwietniu b\u0119dzie jego wersja w Petersburgu, a w czerwcu porozmawiamy o du\u017cych obci\u0105\u017ceniach w Nowosybirsku.<br \/>\nPo drugie, autor referatu, Sergey Zaika, jest cz\u0142onkiem Komitetu Programowego naszej nowej konferencji po\u015bwi\u0119conej zarz\u0105dzaniu wiedz\u0105 <noindex><a rel=\"nofollow\" href=\"https:\/\/knowledgeconf.ru\/2019\">KnowledgeConf<\/a><\/noindex>. Konferencja jest jednodniowa, odb\u0119dzie si\u0119 26 kwietnia, ale program jest bardzo bogaty.<br \/>\nA w maju odb\u0119dzie si\u0119 <noindex><a rel=\"nofollow\" href=\"https:\/\/phprussia.ru\/2019\">PHP Russia<\/a><\/noindex> i\u00a0<noindex><a rel=\"nofollow\" href=\"https:\/\/ritfest.ru\/2019\">RIT++<\/a><\/noindex> (z DevOpsConf w sk\u0142adzie) \u2014 tam jeszcze mo\u017cna zaproponowa\u0107 sw\u00f3j temat, opowiedzie\u0107 o swoim do\u015bwiadczeniu i poskar\u017cy\u0107 si\u0119 na swoje zranione ego.<\/p><\/blockquote>\n<p>\u0179r\u00f3d\u0142o: <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.1.1 - 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\/pl\/blog\/administrirovanie\/opyt-razrabotki-servisa-refund-tool-s-asinhronnym-api-na-kafka\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.1.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"pl_PL\" \/>\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\/pl\/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\udd47Do\u015bwiadczenie opracowywania us\u0142ugi Refund Tool z asynchronicznym API na Kafka | ProHoster","description":"Co mo\u017ce sk\u0142oni\u0107 tak du\u017c\u0105 firm\u0119 jak Lamoda z ustalonym procesem i dziesi\u0105tkami wzajemnie powi\u0105zanych us\u0142ug do znacz\u0105cej zmiany podej\u015bcia?","canonical_url":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/opyt-razrabotki-servisa-refund-tool-s-asinhronnym-api-na-kafka","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"pl_PL","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\/pl\/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\/pl\/wp-json\/wp\/v2\/posts\/30778","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/comments?post=30778"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts\/30778\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/media\/22763"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/media?parent=30778"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/categories?post=30778"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/tags?post=30778"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}