{"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\/it\/blog\/administrirovanie\/opyt-razrabotki-servisa-refund-tool-s-asinhronnym-api-na-kafka","title":{"rendered":"Esperienza nello sviluppo del servizio Refund Tool con API asincrone su Kafka","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>Cosa pu\u00f2 spingere un'azienda cos\u00ec grande come Lamoda, con un processo collaudato e decine di servizi interconnessi, a cambiare radicalmente approccio? La motivazione pu\u00f2 essere delle pi\u00f9 varie: da quella legislativa al desiderio di sperimentare, comune a tutti i programmatori.<\/p>\n<p>Ma questo non significa affatto che non si possa contare su un vantaggio aggiuntivo. In cosa si pu\u00f2 guadagnare concretamente implementando un'API event-driven su Kafka, lo racconter\u00e0 Sergey Zaika (<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/users\/fewald\/\" class=\"user_link\">fewald<\/a><\/noindex>). Parleremo anche degli errori commessi e delle scoperte interessanti, perch\u00e9 un esperimento non pu\u00f2 prescindere da essi.<\/p>\n<p><img decoding=\"async\" alt=\"Esperienza nello sviluppo del servizio Refund Tool con API asincrone su Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/7ab959ab45ec5c6565b35b18b361c0ea.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\n<em>Avvertenza: Questo articolo si basa su materiali del meetup che Sergey ha tenuto nel novembre 2018 durante HighLoad++. L'esperienza reale di Lamoda con Kafka ha attirato l'attenzione del pubblico non meno di altre presentazioni del programma. Ci sembra un ottimo esempio di come si possano sempre trovare e unire forze simili, e gli organizzatori di HighLoad++ continueranno a creare un'atmosfera favorevole a questo.<\/em><br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2>Sul processo<\/h2>\n<p>\nLamoda \u00e8 una grande piattaforma di e-commerce che ha il suo centro di contatto, un servizio di consegna (e numerosi partner), uno studio fotografico, un enorme magazzino, e tutto ci\u00f2 funziona con il proprio software. Ci sono decine di metodi di pagamento, partner b2b che possono utilizzare parte o tutti questi servizi e vogliono conoscere informazioni aggiornate sui propri prodotti. Inoltre, Lamoda opera in tre paesi oltre alla RF, e l\u00ec tutto \u00e8 un po' diverso. Quindi ci sono probabilmente pi\u00f9 di cento modi per configurare un nuovo ordine, che deve essere elaborato in modo unico. Tutto questo funziona grazie a decine di servizi che comunicano a volte in modi non evidenti. C'\u00e8 anche un sistema centrale, la cui principale responsabilit\u00e0 sono gli stati degli ordini. La chiamiamo BOB, e io lavoro con essa.<\/p>\n<h2>Refund Tool con eventi-driven API <\/h2>\n<p>\nLa parola eventi-driven \u00e8 piuttosto abusata, pi\u00f9 avanti definiremo nel dettaglio cosa si intende. Comincer\u00f2 dal contesto in cui abbiamo deciso di testare l'approccio dell'API eventi-driven in Kafka. <\/p>\n<p><img decoding=\"async\" alt=\"Esperienza nello sviluppo del servizio Refund Tool con API asincrone su Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/3bfdce47dd8420fc63d84645e76de647.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nIn qualsiasi negozio, oltre agli ordini per cui i clienti pagano, ci sono momenti in cui il negozio deve restituire i soldi, perch\u00e9 il cliente non ha trovato il prodotto adatto. Questo \u00e8 un processo relativamente breve: verifichiamo le informazioni, se necessario, e trasferiamo i soldi. <\/p>\n<p>Ma il rimborso \u00e8 diventato complicato a causa dei cambiamenti normativi, e abbiamo dovuto realizzare un microservizio dedicato per questo.<\/p>\n<p><img decoding=\"async\" alt=\"Esperienza nello sviluppo del servizio Refund Tool con API asincrone su Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/0e358df5476e7448976e0f1147a103fb.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nLa nostra motivazione:<\/p>\n<ol>\n<li><strong>Legge FZ-54<\/strong>\u00a0\u2014 in sintesi, la legge richiede di segnalare all'Agenzia delle entrate ogni operazione di denaro, sia un rimborso che una entrata, in un SLA piuttosto breve di pochi minuti. Noi, come e-commerce, gestiamo un numero considerevole di operazioni. Tecnologicamente, questo comporta una nuova responsabilit\u00e0 (e quindi un nuovo servizio) e modifiche in tutti i sistemi coinvolti.<\/li>\n<li><strong>BOB split<\/strong>\u00a0\u2014 un progetto interno dell'azienda per liberare BOB da un gran numero di responsabilit\u00e0 non core e ridurre la sua complessit\u00e0 generale.<\/li>\n<\/ol>\n<p>\n<img decoding=\"async\" alt=\"Esperienza nello sviluppo del servizio Refund Tool con API asincrone su Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/d3ecf9961bdb372fc5f84ee9389f73ef.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nIn questo schema sono rappresentati i principali sistemi di Lamoda. Al momento, la maggior parte di essi \u00e8 pi\u00f9 simile a <strong>un insieme di 5-10 microservizi attorno a un monolite in via di riduzione<\/strong>. Crescono lentamente, ma cerchiamo di ridurli, perch\u00e9 effettuare il deployment di un frammento dedicato al centro \u00e8 spaventoso: non possiamo permetterci che cada. Tutti gli scambi (frecce) dobbiamo riservarli e considerare che qualsiasi di essi potrebbe risultare inaccessibile.<\/p>\n<p>In BOB ci sono anche molti scambi: sistemi di pagamento, consegna, notifiche, ecc. <\/p>\n<p>Tecnicamente BOB \u00e8:<\/p>\n<ul>\n<li>~150k righe di codice + ~100k righe di test;<\/li>\n<li>php7.2 + Zend 1 &amp; Symfony Components 3;<\/li>\n<li>&gt;100 API &amp; ~50 integrazioni in uscita;<\/li>\n<li>4 paesi con la propria logica commerciale. <\/li>\n<\/ul>\n<p>\nEffettuare il deployment di BOB \u00e8 costoso e doloroso, la quantit\u00e0 di codice e le questioni che gestisce sono tali che nessuno pu\u00f2 tenerlo in mente tutto. In generale, ci sono molte ragioni per semplificarlo.<\/p>\n<h2>Il processo di rimborso<\/h2>\n<p>\nInizialmente il processo coinvolge due sistemi: BOB e Payment. Ora se ne aggiungono altri due:<\/p>\n<ul>\n<li>Fiscalization Service, che si occuper\u00e0 dei problemi di fiscalizzazione e della comunicazione con servizi esterni.<\/li>\n<li>Refund Tool, dove vengono semplicemente spostati i nuovi scambi, per non gonfiare BOB.<\/li>\n<\/ul>\n<p>\nOra il processo appare cos\u00ec:<\/p>\n<p><img decoding=\"async\" alt=\"Esperienza nello sviluppo del servizio Refund Tool con API asincrone su Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/13c02975881ad35c61304053c604cda3.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<\/p>\n<ol>\n<li>BOB riceve una richiesta di rimborso.<\/li>\n<li>BOB comunica al Refund Tool.<\/li>\n<li>Refund Tool dice a Payment: \"Restituisci i soldi\".<\/li>\n<li>Payment restituisce i soldi.<\/li>\n<li>Refund Tool e BOB sincronizzano tra loro gli stati, perch\u00e9 al momento \u00e8 necessario per entrambi. Non siamo ancora pronti a passare completamente a Refund Tool, poich\u00e9 in BOB ci sono UI, report per la contabilit\u00e0 e molte altre informazioni che non possono essere trasferite cos\u00ec semplicemente. Dobbiamo rimanere su due sedie.<\/li>\n<li>Viene inviata una richiesta per la fiscalizzazione.<\/li>\n<\/ol>\n<p>\nAlla fine abbiamo creato su Kafka una sorta di bus degli eventi \u2013 event-bus, su cui \u00e8 tutto basato. Evviva, ora abbiamo un unico punto di guasto (sarcasmo).<\/p>\n<p><img decoding=\"async\" alt=\"Esperienza nello sviluppo del servizio Refund Tool con API asincrone su Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/674edd7972998b4985071f5250612c7e.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nI pro e i contro sono abbastanza evidenti. Abbiamo creato un bus, il che significa che tutti i servizi ora dipendono da esso. Questo semplifica la progettazione, ma introduce un unico punto di guasto nel sistema. Se Kafka va gi\u00f9, il processo si ferma.<\/p>\n<h2>Cos'\u00e8 un API basata sugli eventi <\/h2>\n<p>\nUna buona risposta a questa domanda si trova nella presentazione di Martin Fowler (GOTO 2017) <noindex><a rel=\"nofollow\" href=\"https:\/\/youtu.be\/STKCRSUsyPO\">\u00abThe Many Meanings of Event-Driven Architecture\u00bb<\/a><\/noindex>. <\/p>\n<p>In breve, cosa abbiamo fatto:<\/p>\n<ol>\n<li>Abbiamo incapsulato tutti gli scambi asincroni tramite <strong>events storage<\/strong>. Invece di comunicare in rete a ciascun consumatore interessato il cambiamento di stato, scriviamo in uno storage centralizzato un evento che indica il cambiamento di stato, e i consumatori interessati sull'argomento leggono tutto ci\u00f2 che appare l\u00ec.<\/li>\n<li>Un evento (event) in questo caso \u00e8 una notifica (<strong>notifications<\/strong>) che qualcosa \u00e8 cambiato da qualche parte. Ad esempio, lo stato di un ordine \u00e8 cambiato. Un consumatore, che ha bisogno di alcune informazioni accessorie relative al cambiamento di stato e non presenti nella notifica, pu\u00f2 autonomamente scoprirne lo stato.<\/li>\n<li>La variante massima \u00e8 un completo event sourcing, <strong>state transfer<\/strong>, in cui l'evento contiene tutte le informazioni necessarie per l'elaborazione: da dove e in quale stato sono passati, come sono cambiati i dati, ecc. La questione \u00e8 solo di opportunit\u00e0 e della quantit\u00e0 di informazioni che puoi permetterti di conservare.<\/li>\n<\/ol>\n<p>\nNel contesto del lancio di Refund Tool abbiamo utilizzato la terza variante. Questo ha semplificato l'elaborazione degli eventi, poich\u00e9 non c'era bisogno di recuperare informazioni dettagliate, e ha anche eliminato lo scenario in cui ogni nuovo evento genera un picco di richieste GET di chiarimento dai consumatori.<\/p>\n<p>Il servizio Refund Tool <strong>non \u00e8 gravoso<\/strong>, quindi Kafka l\u00ec \u00e8 pi\u00f9 una prova che una necessit\u00e0. Non credo che se il servizio di restituzione fondi diventasse un progetto ad alto carico, il business ne sarebbe contento.<\/p>\n<h4>Scambio asincrono AS IS<\/h4>\n<p>\nPer gli scambi asincroni, il dipartimento PHP generalmente utilizza RabbitMQ. Raccogliamo i dati per la richiesta, li mettiamo in coda e il consumatore dello stesso servizio li legge e li invia (o non li invia). Per l'API stessa, Lamoda utilizza attivamente Swagger. Progettiamo l'API, la descriviamo in Swagger, generiamo il codice client e server. Utilizziamo anche una versione leggermente estesa di JSON RPC 2.0. <\/p>\n<p>In alcuni casi vengono utilizzati esb-bus, qualcuno vive su activeMQ, ma, in generale, <strong>RabbitMQ \u2014 standard<\/strong>.<\/p>\n<h4>Scambio asincrono TO BE<\/h4>\n<p>\nProgettando lo scambio tramite events-bus, si evidenzia un'analogia. Descriviamo in modo simile i futuri scambi di dati attraverso la descrizione della struttura dell'evento. Il formato yaml, la generazione di codice \u00e8 stata fatta manualmente, il generatore secondo le specifiche crea DTO e insegna ai client e ai server a lavorare con essi. La generazione avviene in due lingue \u2014 <strong>golang e php<\/strong>. Questo consente di mantenere le librerie sincronizzate. Il generatore \u00e8 scritto in golang, per questo ha ricevuto il nome gogi.<\/p>\n<p>L'event sourcing su Kafka \u00e8 una pratica comune. C'\u00e8 una soluzione dalla versione enterprise principale di Kafka Confluent, c'\u00e8 <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/zalando\/nakadi\">nakadi<\/a><\/noindex>, una soluzione dai nostri \"fratelli\" nel settore Zalando. La nostra <strong>motivazione per iniziare con vanilla Kafka<\/strong>\u00a0\u00e8 quella di mantenere la soluzione gratuita, finch\u00e9 non decidiamo definitivamente se utilizzarla su larga scala, e anche di lasciarci spazio per manovre e miglioramenti: vogliamo supportare il nostro <strong>JSON RPC 2.0<\/strong>, generatori per due lingue e vedremo cos'altro. <\/p>\n<p>Ironico che anche in un caso cos\u00ec fortunato, quando esiste un business sostanzialmente analogo a Zalando, che ha realizzato una soluzione simile, non possiamo usarla efficacemente. <\/p>\n<p>Architettonicamente, al lancio il pattern \u00e8 questo: leggiamo direttamente da Kafka, ma scriviamo solo tramite events-bus. Per la lettura in Kafka esistono molte soluzioni pronte: broker, bilanciatori, ed \u00e8 pi\u00f9 o meno pronto per la scalabilit\u00e0 orizzontale, questo \u00e8 ci\u00f2 che volevamo mantenere. Per la scrittura, volevamo incapsularla attraverso un Gateway noto anche come Events-bus, ed ecco perch\u00e9.<\/p>\n<h3>Events-bus<\/h3>\n<p>\nO autobus degli eventi. \u00c8 semplicemente un gateway http stateless, che assume su di s\u00e9 diversi ruoli importanti:<\/p>\n<ul>\n<li><strong>Validazione della produzione<\/strong>\u00a0verifichiamo che gli eventi corrispondano alla nostra specifica.<\/li>\n<li><strong>Sistema principale per gli eventi<\/strong>, cio\u00e8 \u00e8 l'unico e principale sistema dell'azienda che risponde alla domanda su quali eventi con quali strutture siano considerati validi. Nella validazione rientrano semplicemente i tipi di dati e gli enum per una specifica rigorosa del contenuto. <\/li>\n<li><strong>Funzione hash<\/strong> per lo sharding \u2014 la struttura del messaggio Kafka \u00e8 key-value e in base all'hash dalla chiave si calcola dove collocarlo.<\/li>\n<\/ul>\n<p><\/p>\n<h3>Perch\u00e9<\/h3>\n<p>\nLavoriamo in una grande azienda con un processo collaudato. Perch\u00e9 cambiare qualcosa? <strong>\u00c8 un esperimento<\/strong>, e ci aspettiamo di ottenere alcuni vantaggi.<\/p>\n<h4>Scambi 1:n+1 (uno a molti)<\/h4>\n<p>\nCon Kafka \u00e8 molto semplice collegare nuove API dei consumatori. <\/p>\n<p>Supponiamo che tu abbia un database che deve rimanere aggiornato in pi\u00f9 sistemi contemporaneamente (e in alcuni nuovi). In passato abbiamo progettato un bundle che implementava il set-API, e alla master system comunicavamo gli indirizzi dei consumatori. Ora la master system invia aggiornamenti a un topic, e tutti quelli interessati li leggono. \u00c8 emerso un nuovo sistema \u2013 lo abbiamo iscritti al topic. S\u00ec, anche questo \u00e8 un bundle, ma pi\u00f9 semplice.<\/p>\n<p>Nel caso del refund-tool, che \u00e8 un componente di BOB, ci troviamo bene a tenere sincronizzati tramite Kafka. Il Payment comunica che il denaro \u00e8 stato restituito: BOB e RT ne sono stati informati, hanno aggiornato i loro stati e il Fiscalization Service ha preso atto e ha emesso lo scontrino.<\/p>\n<p><img decoding=\"async\" alt=\"Esperienza nello sviluppo del servizio Refund Tool con API asincrone su Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/b01b22a333b58e87aeef0c52d40e6960.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nAbbiamo in programma di creare un Notifications Service unico, che informer\u00e0 il cliente sulle novit\u00e0 relative al suo ordine\/ritorno. Attualmente questa responsabilit\u00e0 \u00e8 diffusa tra i sistemi. Sar\u00e0 sufficiente insegnare al Notifications Service a pescare informazioni rilevanti da Kafka e a reagire ad esse (e disattivare queste notifiche negli altri sistemi). Non saranno necessari nuovi scambi diretti.<\/p>\n<h4>Data-driven<\/h4>\n<p>\nLe informazioni tra i sistemi diventano trasparenti \u2013 non importa che tipo di \u00abimpegnativo enterprise\u00bb tu abbia e quanto sia denominato il tuo backlog. In Lamoda c'\u00e8 un dipartimento di Data Analytics che raccoglie dati dai sistemi e li rende riutilizzabili, sia per il business che per i sistemi intelligenti. Kafka consente di fornirgli rapidamente molti dati e di mantenere questo flusso informativo aggiornato.<\/p>\n<h4>Replication log<\/h4>\n<p>\nI messaggi non scompaiono dopo essere stati letti, come in RabbitMQ. Quando un evento contiene informazioni sufficienti per l'elaborazione, abbiamo una cronologia delle ultime modifiche all'oggetto, e, se lo desideriamo, la possibilit\u00e0 di applicare tali modifiche.<\/p>\n<p>Il periodo di conservazione del replication log dipende dall'intensit\u00e0 della scrittura in questo topic, Kafka consente di impostare in modo flessibile i limiti per il periodo di conservazione e per il volume di dati. Per i topic ad alta intensit\u00e0 \u00e8 importante che tutti i consumatori riescano a leggere le informazioni prima che scompaiano, anche in caso di breve inattivit\u00e0. Di solito riusciamo a conservare i dati per\u00a0<strong>un numero di giorni<\/strong>, che \u00e8 pi\u00f9 che sufficiente per il supporto. <\/p>\n<p><img decoding=\"async\" alt=\"Esperienza nello sviluppo del servizio Refund Tool con API asincrone su Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/0e08dd384155289123ebee96430c2370.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nIn seguito un breve riassunto della documentazione, per chi non ha familiarit\u00e0 con Kafka (l'immagine \u00e8 anch'essa dalla documentazione)<\/p>\n<p>In AMQP ci sono le code: scriviamo messaggi in coda per il consumatore. In generale, una coda \u00e8 gestita da un sistema con la stessa logica aziendale. Se \u00e8 necessario informare pi\u00f9 sistemi, \u00e8 possibile insegnare all'applicazione a scrivere in pi\u00f9 code o configurare un exchange con un meccanismo fanout, che le clona automaticamente.<\/p>\n<p>In Kafka c'\u00e8 un'astrazione simile <em>topic<\/em>, in cui scrivete messaggi, ma non scompaiono dopo la lettura. Di default, quando vi collegate a Kafka, ricevete tutti i messaggi e c'\u00e8 la possibilit\u00e0 di salvare la posizione in cui vi siete fermati. Cio\u00e8, leggete in modo sequenziale, potete non contrassegnare un messaggio come letto, ma salvare l'id da cui continuerete poi la lettura. L'id su cui vi siete fermati si chiama offset, e il meccanismo \u00e8 il commit offset. <\/p>\n<p>Di conseguenza, si pu\u00f2 implementare una logica diversa. Ad esempio, BOB esiste in 4 istanze per diversi paesi: Lamoda \u00e8 presente in Russia, Kazakistan, Ucraina, Bielorussia. Poich\u00e9 vengono distribuiti separatamente, hanno configurazioni e logiche aziendali leggermente diverse. Indichiamo nel messaggio a quale paese si riferisce. Ogni consumatore BOB in ogni paese legge con diversi groupId e, se il messaggio non lo riguarda, lo ignora, cio\u00e8 committa subito l'offset +1. Se lo stesso topic \u00e8 letto dal nostro Payment Service, allora lo fa con un gruppo separato, e quindi gli offset non si sovrappongono.<\/p>\n<p><b>Requisiti per gli eventi:<\/b><\/p>\n<ul>\n<li><strong>Completezza dei dati. <\/strong>Vorremmo che nell'evento ci fossero dati sufficienti per poterlo elaborare. <\/li>\n<\/ul>\n<p><\/p>\n<ul>\n<li><strong>Integrit\u00e0. <\/strong>Delegiamo all'Events-bus la verifica che l'evento sia coerente e che possa essere elaborato.<\/li>\n<li><strong>L'ordine \u00e8 importante. <\/strong>Nel caso dei resi, siamo costretti a lavorare con la storia. Con le notifiche, l'ordine non \u00e8 importante, se si tratta di notifiche omogenee, l'email sar\u00e0 la stessa indipendentemente da quale ordine \u00e8 arrivato per primo. Nel caso di un reso c'\u00e8 un processo definito, se si cambia l'ordine, potrebbero sorgere eccezioni, il rimborso non sar\u00e0 creato o elaborato, e ci troveremo in un altro stato.<\/li>\n<li><strong>Coerenza. <\/strong>Abbiamo un magazzino e ora stiamo creando eventi invece delle API. Abbiamo bisogno di un modo per trasmettere in modo rapido ed economico informazioni sui nuovi eventi e sulle modifiche a quelli esistenti. Questo viene realizzato tramite una specifica comune in un repository git separato e generatori di codice. Pertanto, i clienti e i server dei diversi servizi sono allineati.<\/li>\n<\/ul>\n<p><\/p>\n<h2>Kafka in Lamoda<\/h2>\n<p>\nAbbiamo tre installazioni di Kafka: <\/p>\n<ol>\n<li>Logs;<\/li>\n<li>R&amp;D;<\/li>\n<li>Events-bus.<\/li>\n<\/ol>\n<p>\nOggi parliamo solo dell'ultimo punto. Nell'events-bus non abbiamo installazioni molto grandi: 3 broker (server) e solo 27 topic. Di norma, un topic corrisponde a un processo. Ma questo \u00e8 un punto delicato e ora lo toccheremo.<\/p>\n<p><img decoding=\"async\" alt=\"Esperienza nello sviluppo del servizio Refund Tool con API asincrone su Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/f398852689b31429cc97b4cbffcabab5.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nSopra \u00e8 il grafico rps. Il processo di rimborsi \u00e8 segnato dalla linea turchese (s\u00ec, s\u00ec, quella sull'asse X), mentre quello dell'aggiornamento dei contenuti \u00e8 segnato in rosa. <\/p>\n<p>Il catalogo di Lamoda contiene milioni di articoli e i dati vengono costantemente aggiornati. Collezioni escono di moda, ne vengono rilasciate di nuove, e nel catalogo appaiono continuamente nuovi modelli. Cerchiamo di prevedere cosa potrebbe interessare ai nostri clienti domani, quindi acquisiamo costantemente nuovi articoli, li fotografiamo e aggiornando la vetrina. <\/p>\n<p>I picchi rosa rappresentano l'aggiornamento del prodotto, ovvero le modifiche agli articoli. Si vede chiaramente che i ragazzi stavano fotografando, fotografando e poi, puff! \u2014 hanno caricato un sacco di eventi.<\/p>\n<h2>Usi di Lamoda Events<\/h2>\n<p>\nL'architettura costruita la utilizziamo per le seguenti operazioni:<\/p>\n<ul>\n<li><strong>Monitoraggio degli stati di ritorno<\/strong>: call-to-action e tracciamento degli stati da tutti i sistemi coinvolti. Pagamenti, stati, fiscalizzazione, notifiche. Qui abbiamo testato un approccio, creato strumenti, raccolto bug, scritto documentazione e spiegato ai colleghi come usarlo.<\/li>\n<li><strong>Aggiornamento delle schede prodotto: <\/strong>configurazione, metadati, caratteristiche. Una sistema legge (quello che visualizza), mentre pi\u00f9 sistemi scrivono.<\/li>\n<li><strong>Email, push e sms<\/strong>: ordine raccolto, ordine arrivato, reso accettato, ecc., ce ne sono molti. <\/li>\n<li><strong>Stock, aggiornamento del magazzino<\/strong>\u00a0\u2014 aggiornamento quantitativo delle voci, solo numeri: arrivi in magazzino, resi. \u00c8 necessario affinch\u00e9 tutti i sistemi legati alla prenotazione dei prodotti operino con i dati pi\u00f9 aggiornati. Attualmente, il sistema di aggiornamento dello stock \u00e8 piuttosto complesso; Kafka lo semplificher\u00e0.<\/li>\n<li><strong>Analisi dei dati<\/strong> (R&amp;D-dipartimento), strumenti ML, analisi, statistica. Vogliamo che le informazioni siano trasparenti \u2014 per questo Kafka \u00e8 molto adatto.<\/li>\n<\/ul>\n<p>\nOra passiamo alla parte pi\u00f9 interessante riguardo gli errori e le scoperte interessanti avvenute negli ultimi sei mesi.<\/p>\n<h2>Problemi di progettazione<\/h2>\n<p>\nSupponiamo di voler creare una nuova cosa \u2014 per esempio, trasferire su Kafka l'intero processo di consegna. Attualmente, parte del processo \u00e8 realizzata nel processamento degli ordini in BOB. Prima di inviare un ordine al servizio di consegna, attraverso lo spostamento in un magazzino intermedio e altro, c'\u00e8 un modello di stato. Esiste un intero monolite, addirittura due, pi\u00f9 una marea di API dedicate alla consegna. Sanno molto di pi\u00f9 sulla consegna. <\/p>\n<p>Sembra che queste siano aree simili, ma per il processamento degli ordini in BOB e per il sistema di consegna gli stati sono diversi. Per esempio, alcune compagnie di corriere non inviano stati intermedi, ma solo finali: 'consegnato' o 'perso'. Altre, al contrario, comunicano in modo molto dettagliato il movimento del prodotto. Ognuno ha le proprie regole di validazione: per qualcuno un'email valida significa che verr\u00e0 elaborata; per altri, un'email non valida, ma l'ordine verr\u00e0 comunque elaborato perch\u00e9 c'\u00e8 un telefono per contattare, e qualcuno dir\u00e0 che quell'ordine non sar\u00e0 elaborato affatto.<\/p>\n<h3>Flusso di dati<\/h3>\n<p>\nNel caso di Kafka sorgono questioni sull'organizzazione del flusso di dati. Questo compito \u00e8 legato alla scelta della strategia su diversi punti; esaminiamo tutti questi punti.<\/p>\n<h4>In un unico topic o in diversi?<\/h4>\n<p>\nAbbiamo una specifica dell'evento. In BOB scriviamo che un certo ordine deve essere consegnato, e indichiamo: numero dell'ordine, il suo contenuto, alcuni SKU e codici a barre, ecc. Quando il prodotto arriva al magazzino, la consegna pu\u00f2 ricevere stati, timestamp e tutto ci\u00f2 di cui ha bisogno. Ma poi vogliamo ricevere aggiornamenti su questi dati in BOB. Si genera un processo inverso di ricezione dei dati dalla consegna. \u00c8 lo stesso evento? Oppure \u00e8 uno scambio separato che merita un topic separato?<\/p>\n<p>Probabilmente saranno molto simili, e la tentazione di creare un unico topic \u00e8 giustificata, perch\u00e9 un topic separato significa consumer separati, config separati, generazione separata di tutto questo. Ma non \u00e8 detto.<\/p>\n<h4>Nuovo campo o nuovo evento?<\/h4>\n<p>\nMa se si utilizzano gli stessi eventi, sorge un altro problema. Ad esempio, non tutti i sistemi di consegna possono generare un DTO che possa essere generato da BOB. Inviamo loro l'id, ma non li salvano, perch\u00e9 non ne hanno bisogno, e dal punto di vista dell'inizio del processo event-bus, questo campo \u00e8 obbligatorio. <\/p>\n<p>Se introduciamo per l'event-bus una regola secondo cui questo campo \u00e8 obbligatorio, siamo costretti a mettere in BOB o nel gestore dell'evento iniziale ulteriori regole di validazione. La validazione inizia a diffondersi nel servizio \u2014 non \u00e8 molto comodo.<\/p>\n<p>Un altro problema \u00e8 la tentazione dello sviluppo incrementale. Ci viene detto che dobbiamo aggiungere qualcosa all'evento e, forse, se ci pensiamo bene, sarebbe dovuto essere un evento separato. Ma nella nostra schema, un evento separato \u00e8 un argomento separato. Un argomento separato \u00e8 l'intero processo che ho descritto sopra. Il sviluppatore \u00e8 tentato di aggiungere semplicemente un altro campo nella schema JSON e rigenerarlo.<\/p>\n<p>Nel caso dei rimborsi, siamo arrivati all'evento eventi in sei mesi. Avevamo un evento meta, che si chiama aggiornamento rimborso, in cui c'era un campo tipo, descrivente in cosa consiste questo aggiornamento. Da ci\u00f2 abbiamo avuto \"ottimi\" switch con validator che dicevano come dovevamo validare questo evento con quel tipo.<\/p>\n<h4>Versionamento degli eventi<\/h4>\n<p>\nPer la validazione dei messaggi in Kafka si pu\u00f2 utilizzare <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.confluent.io\/current\/schema-registry\/docs\/index.html\">Avro<\/a><\/noindex>, ma era necessario pianificare subito su questo e utilizzare Confluent. Nel nostro caso, con il versionamento, bisogna essere cauti. Non sar\u00e0 sempre possibile rileggere i messaggi dal replication log, perch\u00e9 il modello \"\u00e8 partito\". Fondamentalmente, si deve costruire versioni in modo tale che il modello sia retrocompatibile: per esempio, rendere un campo temporaneamente non obbligatorio. Se le differenze sono troppo forti, iniziamo a scrivere in un nuovo argomento, e i clienti vengono trasferiti quando hanno finito di leggere il vecchio.<\/p>\n<h4>Garanzia dell'ordine di lettura delle partizioni<\/h4>\n<p>\nGli argomenti all'interno di Kafka sono suddivisi in partizioni. Questo non \u00e8 molto importante mentre progettiamo entit\u00e0 e scambi, ma \u00e8 importante quando decidiamo come consumare e scalare.<\/p>\n<p>In un caso normale, si scrive un topic in Kafka. Per impostazione predefinita viene utilizzata una sola partizione, e tutti i messaggi di questo topic vi arrivano. Il consumer legge quindi questi messaggi in modo sequenziale. Supponiamo che ora sia necessario espandere la sistema in modo che i messaggi vengano letti da due diversi consumer. Se, ad esempio, invii un SMS, puoi dire a Kafka di creare una partizione aggiuntiva, e Kafka inizier\u00e0 a distribuire i messaggi su due parti: met\u00e0 da una parte, met\u00e0 dall'altra. <\/p>\n<p>Come li divide Kafka? Ogni messaggio ha un corpo (in cui memorizziamo JSON) e una chiave. A questa chiave pu\u00f2 essere applicata una funzione hash, che determiner\u00e0 in quale partizione andr\u00e0 a finire il messaggio.<\/p>\n<p>Nel nostro caso con i rimborsi, questo \u00e8 importante; se prendiamo due partizioni, c'\u00e8 la possibilit\u00e0 che un consumer parallelo elabori il secondo evento prima del primo, e ci saranno problemi. La funzione hash garantisce che i messaggi con la stessa chiave finiscano nella stessa partizione. <\/p>\n<h4>Eventi vs comandi<\/h4>\n<p>\nQuesto \u00e8 un altro problema con cui ci siamo confrontati. Un evento \u00e8 un certo avvenimento: diciamo che qualcosa \u00e8 accaduto (something_happened), ad esempio, un articolo \u00e8 stato annullato o \u00e8 stato effettuato un rimborso. Se questi eventi vengono ascoltati da qualcuno, allora per \"articolo annullato\" verr\u00e0 creata un'entit\u00e0 di rimborso, e \"\u00e8 stato effettuato un rimborso\" verr\u00e0 registrato da qualche parte nelle impostazioni.<\/p>\n<p>Ma di solito, quando progetti eventi, non vuoi scriverli senza motivo: ti aspetti che qualcuno li legga. C'\u00e8 una forte tentazione di non scrivere something_happened (item_canceled, refund_refunded), ma something_should_be_done. Ad esempio, l'articolo \u00e8 pronto per il reso.<\/p>\n<p>Da un lato, questo suggerisce come sar\u00e0 utilizzato l'evento. Dall'altro lato, somiglia molto meno a un nome normale per un evento. Inoltre, da qui il passo verso un comando do_something \u00e8 breve. Ma non hai garanzie che questo evento sia stato letto da qualcuno; e se \u00e8 stato letto, \u00e8 stato letto con successo; e se \u00e8 stato letto con successo, \u00e8 stata fatta qualcosa, e questa qualcosa \u00e8 andata a buon fine. Nel momento in cui l'evento diventa do_something, diventa necessaria una retroazione, e questo \u00e8 un problema.<\/p>\n<p><img decoding=\"async\" alt=\"Esperienza nello sviluppo del servizio Refund Tool con API asincrone su Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/b755d91208092bd9791a41ce4633fb48.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nNello scambio asincrono in RabbitMQ, quando hai letto un messaggio e sei andato in http, hai una risposta: almeno che il messaggio sia stato ricevuto. Quando hai scritto in Kafka, hai un messaggio che hai scritto in Kafka, ma non sai come \u00e8 stato elaborato. <\/p>\n<p>Pertanto, nel nostro caso, abbiamo dovuto introdurre un evento di risposta e configurare il monitoraggio affinch\u00e9, se si verificano un certo numero di eventi, dopo un certo tempo dovrebbero arrivare un numero uguale di eventi di risposta. Se ci\u00f2 non \u00e8 avvenuto, sembra che ci sia stato un problema. Ad esempio, se abbiamo inviato l'evento \u00abitem_ready_to_refund\u00bb, ci aspettiamo che venga creato un rimborso, i soldi vengano restituiti al cliente e riceviamo l'evento \u00abmoney_refunded\u00bb. Ma non \u00e8 certo, quindi \u00e8 necessario un monitoraggio.<\/p>\n<h3>Nuanze<\/h3>\n<p>\nC'\u00e8 un problema abbastanza ovvio: se leggi i messaggi dal topic in modo sequenziale e hai un messaggio problematico, il consumer si blocca e non puoi andare avanti. Devi <strong>fermare tutti i consumer<\/strong>, impegnare l'offset oltre, per continuare a leggere.<\/p>\n<p>Ne eravamo a conoscenza, lo avevamo previsto, eppure \u00e8 successo. E questo \u00e8 accaduto perch\u00e9 l'evento era valido dal punto di vista dell'events-bus, era valido dal punto di vista del validatore dell'applicazione, ma non era valido dal punto di vista di PostgreSQL, perch\u00e9 in un sistema abbiamo MySQL con UNSIGNED INT, mentre nel nuovo sistema era PostgreSQL con semplicemente INT. Ha una dimensione leggermente pi\u00f9 piccola, e l'Id non ci stava. Symfony si \u00e8 bloccato con un'eccezione. Certamente, abbiamo catturato l'eccezione, perch\u00e9 l'avevamo prevista, e volevamo impegnare questo offset, ma prima volevamo incrementare il contatore dei problemi, dato che il messaggio era stato elaborato in modo non riuscito. Anche i contatori di questo progetto sono memorizzati nel database, e Symfony aveva gi\u00e0 chiuso la comunicazione con il database, e la seconda eccezione ha ucciso l'intero processo senza alcuna possibilit\u00e0 di impegnare l'offset.<\/p>\n<p>Il servizio \u00e8 rimasto inattivo per un certo periodo - per fortuna, con Kafka non \u00e8 cos\u00ec grave, poich\u00e9 i messaggi rimangono. Quando il lavoro riprender\u00e0, si potranno leggere i messaggi. \u00c8 comodo.<\/p>\n<p>Kafka ha la possibilit\u00e0 di impostare un offset arbitrario tramite il tooling. Ma per farlo, \u00e8 necessario fermare tutti i consumer - nel nostro caso, preparare un rilascio separato in cui non ci saranno consumer, redeployments. Allora, tramite il tooling di Kafka, si pu\u00f2 spostare l'offset e il messaggio passer\u00e0.<\/p>\n<p>Un'altra nuance - <strong>replication log vs rdkafka.so<\/strong>\u00a0\u00c8 legato alle specificit\u00e0 del nostro progetto. Abbiamo PHP, e in PHP, di solito, tutte le librerie comunicano con Kafka attraverso il repository rdkafka.so, e poi c'\u00e8 qualche tipo di wrapper. Forse sono solo le nostre difficolt\u00e0 personali, ma si \u00e8 rivelato che semplicemente rileggere un pezzo di ci\u00f2 che \u00e8 gi\u00e0 stato letto non \u00e8 poi cos\u00ec semplice. In generale, ci sono stati problemi software.<\/p>\n<p>Tornando alle peculiarit\u00e0 del lavoro con le partizioni, \u00e8 scritto proprio nella documentazione <strong>consumers &gt;= topic partitions<\/strong>. Ma ne ho saputo molto pi\u00f9 tardi di quanto avrei voluto. Se volete scalare e avere due consumatori, vi servono almeno due partizioni. Cio\u00e8, se avevate una partizione in cui si sono accumulate 20.000 messaggi, e ne avete creato una nuova, il numero di messaggi si bilancer\u00e0 lentamente. Pertanto, per avere due consumatori paralleli, bisogna capire come gestire le partizioni.<\/p>\n<h2>Monitoraggio<\/h2>\n<p>\nPenso che, dal modo in cui monitoriamo, sar\u00e0 ancora pi\u00f9 chiaro quali problemi ci siano nell'approccio esistente.<\/p>\n<p>Ad esempio, contiamo quanti prodotti hanno recentemente cambiato stato nel database e, di conseguenza, a queste modifiche dovrebbero corrispondere eventi, e inviamo questo numero al nostro sistema di monitoraggio. Poi riceviamo da Kafka un secondo numero, che rappresenta quanti eventi sono stati effettivamente registrati. \u00c8 ovvio che la differenza tra questi due numeri dovrebbe sempre essere zero.<\/p>\n<p><img decoding=\"async\" alt=\"Esperienza nello sviluppo del servizio Refund Tool con API asincrone su Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/07d8ed08514f2fb97d9019466b96342c.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nInoltre, bisogna monitorare come sta andando il produttore, se l'events-bus ha ricevuto i messaggi, e come sta andando il consumatore. Ad esempio, nei grafici qui sotto, il Refund Tool sta funzionando bene, ma BOB ha chiaramente qualche problema (picchi blu).<\/p>\n<p><img decoding=\"async\" alt=\"Esperienza nello sviluppo del servizio Refund Tool con API asincrone su Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/57112dbe70d2b388c53f19c34cca6f63.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nHo gi\u00e0 menzionato il lag del consumer-group. In sostanza, \u00e8 il numero di messaggi non letti. In generale i nostri consumatori lavorano velocemente, quindi il lag \u00e8 di solito 0, ma a volte pu\u00f2 esserci un picco temporaneo. Kafka gestisce questa cosa nativamente, ma bisogna impostare un determinato intervallo. <\/p>\n<p>C'\u00e8 un progetto <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/linkedin\/Burrow\">Burrow<\/a><\/noindex>, che vi dar\u00e0 pi\u00f9 informazioni su Kafka. Semplicemente tramite API, per il consumer-group restituisce lo stato di come stanno le cose per quel gruppo. Oltre a OK e Failed, ci sono anche warning, e potrete scoprire che i vostri consumatori non stanno tenendo il passo con il ritmo di produzione - non riescono a elaborare ci\u00f2 che viene scritto. Il sistema \u00e8 piuttosto intelligente, \u00e8 comodo da usare. <\/p>\n<p><img decoding=\"async\" alt=\"Esperienza nello sviluppo del servizio Refund Tool con API asincrone su Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/cece8495801e187b155487a802e1a35a.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nEcco come appare la risposta tramite API. Qui c'\u00e8 il gruppo bob-live-fifa, partizione refund.update.v1, stato OK, lag 0 - l'offset finale pi\u00f9 recente \u00e8 questo.<\/p>\n<p><img decoding=\"async\" alt=\"Esperienza nello sviluppo del servizio Refund Tool con API asincrone su Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/1538b2ccc390e9b83075f54566f1039b.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nMonitoraggio <strong>updated_at SLA (stuck)<\/strong> Ho gi\u00e0 accennato. Ad esempio, il prodotto \u00e8 passato allo stato di essere pronto per il reso. Impostiamo un Cron che indica che se dopo 5 minuti questo oggetto non \u00e8 passato a refund (restituiamo i soldi tramite i sistemi di pagamento molto rapidamente), allora qualcosa \u00e8 effettivamente andato storto, e questo \u00e8 sicuramente un caso per il supporto. Quindi prendiamo semplicemente un Cron che legge queste cose, e se sono pi\u00f9 di 0, invia un avviso.<\/p>\n<p><b>In sintesi, \u00e8 comodo usare eventi quando<\/b>:<\/p>\n<ul>\n<li>l'informazione \u00e8 necessaria a pi\u00f9 sistemi;<\/li>\n<li>non \u00e8 importante il risultato dell'elaborazione;<\/li>\n<li>ci sono pochi eventi o gli eventi sono piccoli. <\/li>\n<\/ul>\n<blockquote><p>A prima vista, l'articolo ha un tema molto specifico: API asincrono su Kafka, ma in relazione a questo vorrei subito raccomandare molte cose.<br \/>\nIn primo luogo, il prossimo <noindex><a rel=\"nofollow\" href=\"https:\/\/www.highload.ru\/\">HighLoad++<\/a><\/noindex> bisogna attendere fino a novembre, gi\u00e0 ad aprile ci sar\u00e0 la sua versione a San Pietroburgo, e a giugno parleremo di carichi elevati a Novosibirsk.<br \/>\nIn secondo luogo, l'autore della relazione Sergey Zaika fa parte del Comitato di Programma della nostra nuova conferenza sulla gestione della conoscenza <noindex><a rel=\"nofollow\" href=\"https:\/\/knowledgeconf.ru\/2019\">KnowledgeConf<\/a><\/noindex>. La conferenza \u00e8 di un giorno, si svolger\u00e0 il 26 aprile, ma il programma \u00e8 molto ricco.<br \/>\nInoltre, a maggio ci sar\u00e0 <noindex><a rel=\"nofollow\" href=\"https:\/\/phprussia.ru\/2019\">PHP Russia<\/a><\/noindex> e\u00a0<noindex><a rel=\"nofollow\" href=\"https:\/\/ritfest.ru\/2019\">RIT++<\/a><\/noindex> (con DevOpsConf inclusa) - qui \u00e8 ancora possibile proporre il proprio tema, raccontare la propria esperienza e lamentarsi dei propri errori.<\/p><\/blockquote>\n<p>Fonte: <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\/it\/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=\"it_IT\" \/>\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\/it\/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\udd47Esperienza nello sviluppo del servizio Refund Tool con API asincrono su Kafka | ProHoster","description":"Cosa pu\u00f2 spingere una grande azienda come Lamoda con un processo collaudato e decine di servizi interconnessi a cambiare sostanzialmente approccio?","canonical_url":"https:\/\/prohoster.info\/it\/blog\/administrirovanie\/opyt-razrabotki-servisa-refund-tool-s-asinhronnym-api-na-kafka","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"it_IT","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\/it\/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\/it\/wp-json\/wp\/v2\/posts\/30778","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/comments?post=30778"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/posts\/30778\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/media\/22763"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/media?parent=30778"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/categories?post=30778"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/tags?post=30778"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}