{"id":56186,"date":"2020-02-06T00:00:00","date_gmt":"2020-02-05T21:00:00","guid":{"rendered":"https:\/\/prohoster.info\/blog\/blog_prohoster\/povtornaya-obrabotka-sobytij-poluchennyh-iz-kafka"},"modified":"2020-02-18T14:04:24","modified_gmt":"2020-02-18T11:04:24","slug":"povtornaya-obrabotka-sobytij-poluchennyh-iz-kafka","status":"publish","type":"post","link":"https:\/\/prohoster.info\/it\/blog\/administrirovanie\/povtornaya-obrabotka-sobytij-poluchennyh-iz-kafka","title":{"rendered":"Rielaborazione degli eventi ricevuti da Kafka","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p><img decoding=\"async\" alt=\"Rielaborazione degli eventi ricevuti da Kafka\" src=\"\/wp-content\/uploads\/2020\/02\/d04319691af5c5de40ccfe4a2268558c.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Ciao, Habr.<\/p>\n<p><\/p>\n<p>Recentemente ho <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tinkoff\/blog\/481784\/\">condiviso la mia esperienza<\/a><\/noindex> su quali parametri utilizziamo pi\u00f9 spesso nel nostro team per Kafka Producer e Consumer per garantire la consegna dei messaggi. In questo articolo voglio raccontare come abbiamo organizzato il ri-processamento di un evento ricevuto da Kafka a causa della temporanea indisponibilit\u00e0 di un sistema esterno.<\/p>\n<p><\/p>\n<p>Le applicazioni moderne operano in un ambiente molto complesso. La logica di business, incapsulata in uno stack tecnologico moderno, funzionante in un'immagine Docker, gestita da un orchestratore come Kubernetes o OpenShift, e in comunicazione con altre applicazioni o soluzioni aziendali attraverso una rete di router fisici e virtuali. In un tale contesto, qualcosa pu\u00f2 sempre rompersi, quindi il ri-processamento degli eventi nel caso di indisponibilit\u00e0 di uno dei sistemi esterni \u00e8 una parte fondamentale dei nostri processi aziendali.<\/p>\n<p><noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2 id=\"kak-bylo-do-kafka\">Com'era prima di Kafka<\/h2>\n<p><\/p>\n<p>In precedenza, nel progetto utilizzavamo IBM MQ per la consegna asincrona dei messaggi. Se si verificava un errore durante l'esecuzione del servizio, il messaggio ricevuto poteva essere collocato nella coda di dead-letter (DLQ) per ulteriori analisi manuali. La DLQ veniva creata accanto alla coda di ingresso e il trasferimento del messaggio avveniva all'interno di IBM MQ. <\/p>\n<p><\/p>\n<p>Se l'errore era temporaneo e riuscivamo a determinarlo (ad esempio, ResourceAccessException durante una chiamata HTTP o MongoTimeoutException durante una query in MongoDb), entrava in gioco la strategia di retry. Indipendentemente dal ramo logico dell'applicazione, il messaggio originale veniva spostato o nella coda di sistema per l'invio ritardato o in un'applicazione separata, che era stata creata tempo fa per reinviare i messaggi. In questo caso, nell'intestazione del messaggio viene registrato il numero del retry, che \u00e8 legato a un intervallo di attesa o alla fine della strategia a livello di applicazione. Se raggiungiamo la fine della strategia ma il sistema esterno \u00e8 ancora non disponibile, il messaggio verr\u00e0 collocato nella DLQ per l'analisi manuale.<\/p>\n<p><\/p>\n<h2 id=\"poisk-resheniya\">Ricerca della soluzione<\/h2>\n<p><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/www.google.com\/search?q=kafka+retry+message\">Cercando su internet<\/a><\/noindex>, si possono trovare le seguenti informazioni <noindex><a rel=\"nofollow\" href=\"https:\/\/blog.pragmatists.com\/retrying-consumer-architecture-in-the-apache-kafka-939ac4cb851a\">soluzione<\/a><\/noindex>. In breve, si propone di creare un topic per ciascun intervallo di ritardo e di implementare sul lato dell'applicazione dei consumer che possano leggere i messaggi con il ritardo necessario. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Rielaborazione degli eventi ricevuti da Kafka\" src=\"\/wp-content\/uploads\/2020\/02\/718c4c3f71ccf95b5a6f60e1b2c685d2.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Nonostante un gran numero di recensioni positive, mi sembra che non sia del tutto riuscito. Innanzitutto perch\u00e9, oltre a soddisfare le esigenze aziendali, lo sviluppatore dovr\u00e0 dedicare molto tempo all'implementazione di meccanismi descritti.<\/p>\n<p><\/p>\n<p>Inoltre, se nel cluster Kafka \u00e8 attivata la gestione degli accessi, sar\u00e0 necessario investire del tempo per creare i topic e garantire i necessari permessi. Inoltre, sar\u00e0 necessario trovare il giusto parametro retention.ms per ciascun topic di retry, affinch\u00e9 i messaggi possano essere reinviati senza scomparire. L'implementazione e la richiesta di accesso dovranno essere ripetute per ogni servizio esistente o nuovo.<\/p>\n<p><\/p>\n<p>Diamo ora un'occhiata ai meccanismi di rielaborazione dei messaggi forniti da Spring in generale e da Spring-Kafka in particolare. Spring-Kafka ha una dipendenza transitiva da Spring-Retry, che offre astrazioni per gestire diverse BackOffPolicy. Si tratta di uno strumento molto flessibile, ma il suo principale svantaggio \u00e8 la conservazione dei messaggi in attesa di reinvio in memoria dell'applicazione. Ci\u00f2 significa che il riavvio dell'applicazione a causa di un aggiornamento o di un errore durante il funzionamento comporter\u00e0 la perdita di tutti i messaggi in attesa di rielaborazione. Poich\u00e9 questo aspetto \u00e8 critico per il nostro sistema, non abbiamo ritenuto opportuno considerarlo ulteriormente.<\/p>\n<p><\/p>\n<p>Spring-Kafka fornisce diverse implementazioni di ContainerAwareErrorHandler, ad esempio <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/spring-projects\/spring-kafka\/blob\/master\/spring-kafka\/src\/main\/java\/org\/springframework\/kafka\/listener\/SeekToCurrentErrorHandler.java\">SeekToCurrentErrorHandler<\/a><\/noindex>, che consente di rielaborare un messaggio in seguito senza spostare l'offset in caso di errore. A partire dalla versione 2.3 di Spring-Kafka, \u00e8 possibile specificare una BackOffPolicy.<\/p>\n<p><\/p>\n<p>Questo approccio consente ai messaggi ri-processabili di sopravvivere al riavvio dell'applicazione, ma il meccanismo DLQ \u00e8 ancora assente. Questa \u00e8 stata l'opzione che abbiamo scelto all'inizio del 2019, ottimisticamente ritenendo che il DLQ non fosse necessario (siamo stati fortunati e in effetti non \u00e8 stato necessario per diversi mesi di utilizzo dell'applicazione con questo sistema di ri-processamento). Gli errori temporanei hanno attivato SeekToCurrentErrorHandler. Gli altri errori venivano stampati nel log, portando a uno spostamento dell'offset, e il trattamento continuava con il messaggio successivo.<\/p>\n<p><\/p>\n<h2 id=\"itogovoe-reshenie\">Risultato finale<\/h2>\n<p><\/p>\n<p>L'implementazione basata su SeekToCurrentErrorHandler ci ha spinti a sviluppare il nostro meccanismo per la reinvio dei messaggi.<\/p>\n<p><\/p>\n<p>Prima di tutto, volevamo utilizzare l'esperienza gi\u00e0 acquisita e ampliarla in base alla logica dell'applicazione. Per un'applicazione con logica lineare, sarebbe ottimale interrompere la lettura di nuovi messaggi per un breve intervallo di tempo definito nell'ambito della strategia di ripetizione delle chiamate. Per le altre applicazioni, desideravamo avere un punto unico che garantisse l'esecuzione della strategia di ripetizione delle chiamate. Inoltre, questo punto unico dovrebbe avere funzionalit\u00e0 DLQ per entrambi gli approcci.<\/p>\n<p><\/p>\n<p>La strategia di ripetizione delle chiamate deve essere memorizzata nell'applicazione che si occupa di ottenere il successivo intervallo in caso di errore temporaneo.<\/p>\n<p><\/p>\n<h3 id=\"ostanovka-consumera-dlya-prilozheniya-s-lineynoy-logikoy\">Interruzione del Consumer per un'applicazione con logica lineare<\/h3>\n<p><\/p>\n<p>Quando si lavora con spring-kafka, il codice per fermare il Consumer potrebbe apparire simile a questo:<\/p>\n<p><\/p>\n<pre><code class=\"java\">public void pauseListenerContainer(MessageListenerContainer listenerContainer, \n                                   Instant retryAt) {\n        if (nonNull(retryAt) &amp;&amp; listenerContainer.isRunning()) {\n            listenerContainer.stop();\n            taskScheduler.schedule(() -&gt; listenerContainer.start(), retryAt);\n            return;\n        }\n        \/\/ to DLQ\n    }<\/code><\/pre>\n<p><\/p>\n<p>Nell'esempio, retryAt \u00e8 il momento in cui il MessageListenerContainer deve essere riavviato se \u00e8 ancora attivo. Il riavvio avverr\u00e0 in un thread separato avviato in TaskScheduler, la cui implementazione \u00e8 fornita anch'essa da Spring. <\/p>\n<p><\/p>\n<p>Troviamo il valore di retryAt nel seguente modo:<\/p>\n<p><\/p>\n<ol>\n<li>Viene cercato il valore del contatore dei tentativi.<\/li>\n<li>In base al valore del contatore, viene cercato l'attuale intervallo di ritardo nella strategia di retry. La strategia \u00e8 dichiarata nell'applicazione stessa e per conservarla abbiamo scelto il formato JSON.<\/li>\n<li>L'intervallo trovato nell'array JSON contiene il numero di secondi dopo i quali il trattamento deve essere ripetuto. Questo numero di secondi viene aggiunto all'ora attuale, formando il valore per retryAt.<\/li>\n<li>Se l'intervallo non viene trovato, il valore di retryAt \u00e8 null e il messaggio verr\u00e0 inviato alla DLQ per un'analisi manuale.<\/li>\n<\/ol>\n<p><\/p>\n<p>Con questo approccio, resta solo da mantenere il numero di invocazioni ripetute per ogni messaggio attualmente in elaborazione, ad esempio nella memoria dell'applicazione. Mantenere il contatore dei tentativi in memoria non \u00e8 critico per questo approccio, poich\u00e9 un'applicazione con logica lineare non pu\u00f2 elaborare nel suo insieme. A differenza di spring-retry, il riavvio dell'applicazione non comporter\u00e0 la perdita di tutti i messaggi per un nuovo tentativo, ma semplicemente il riavvio della strategia. <\/p>\n<p><\/p>\n<p>Questo approccio aiuta a ridurre il carico sul sistema esterno, che potrebbe non essere disponibile a causa di un elevato carico di lavoro. In altre parole, oltre alla riprocessazione, siamo riusciti a implementare il pattern. <noindex><a rel=\"nofollow\" href=\"https:\/\/microservices.io\/patterns\/reliability\/circuit-breaker.html\">circuit breaker<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p>Nel nostro caso, la soglia di errore \u00e8 solo 1, e per minimizzare il tempo di inattivit\u00e0 del sistema a causa di un temporaneo errore di rete, utilizziamo una strategia di ripetizione delle invocazioni molto granulare con brevi intervalli di attesa. Questo potrebbe non essere adatto a tutte le applicazioni del gruppo, quindi il rapporto tra la soglia di errore e la dimensione dell'intervallo deve essere scelto in base alle caratteristiche del sistema.<\/p>\n<p><\/p>\n<h3 id=\"otdelnoe-prilozhenie-dlya-obrabotki-soobscheniy-ot-prilozheniy-s-nedeterminirovannoy-logikoy\">Un'applicazione dedicata per la gestione di messaggi da applicazioni con logica non deterministica<\/h3>\n<p><\/p>\n<p>Ecco un esempio di codice che invia un messaggio a tale applicazione (Retryer), che ripeter\u00e0 l'invio al tema DESTINATION quando viene raggiunto il tempo RETRY_AT:<\/p>\n<p><\/p>\n<pre><code class=\"java\">\npublic &lt;K, V&gt; void retry(ConsumerRecord&lt;K, V&gt; record, String retryToTopic, \n                         Instant retryAt, String counter, String groupId, Exception e) {\n        Headers headers = ofNullable(record.headers()).orElse(new RecordHeaders());\n        List&lt;Header&gt; arrayOfHeaders = \n            new ArrayList&lt;&gt;(Arrays.asList(headers.toArray()));\n        updateHeader(arrayOfHeaders, GROUP_ID, groupId::getBytes);\n        updateHeader(arrayOfHeaders, DESTINATION, retryToTopic::getBytes);\n        updateHeader(arrayOfHeaders, ORIGINAL_PARTITION, \n                     () -&gt; Integer.toString(record.partition()).getBytes());\n        if (nonNull(retryAt)) {\n            updateHeader(arrayOfHeaders, COUNTER, counter::getBytes);\n            updateHeader(arrayOfHeaders, SEND_TO, &quot;retry&quot;::getBytes);\n            updateHeader(arrayOfHeaders, RETRY_AT, retryAt.toString()::getBytes);\n        } else {\n            updateHeader(arrayOfHeaders, REASON, \n                         ExceptionUtils.getStackTrace(e)::getBytes);\n            updateHeader(arrayOfHeaders, SEND_TO, &quot;backout&quot;::getBytes);\n        }\n        ProducerRecord&lt;K, V&gt; messageToSend =\n            new ProducerRecord&lt;&gt;(retryTopic, null, null, record.key(), record.value(), arrayOfHeaders);\n        kafkaTemplate.send(messageToSend);\n    }<\/code><\/pre>\n<p><\/p>\n<p>Dall'esempio si evince che molte informazioni vengono trasmesse negli header. Il valore RETRY_AT \u00e8 presente cos\u00ec come per il meccanismo di ripetizione tramite l'interruzione del Consumer. Oltre a DESTINATION e RETRY_AT, trasmettiamo:<\/p>\n<p><\/p>\n<ul>\n<li>GROUP_ID, che utilizziamo per raggruppare i messaggi per l'analisi manuale e facilitare la ricerca.<\/li>\n<li>ORIGINAL_PARTITION, per cercare di mantenere lo stesso Consumer per il ri-processing. Questo parametro pu\u00f2 essere uguale a null; in tal caso, una nuova partition sar\u00e0 ottenuta tramite la chiave record.key() del messaggio originale.<\/li>\n<li>Il valore aggiornato di COUNTER, per seguire la strategia di retry.<\/li>\n<li>SEND_TO \u2014 una costante che indica se inviare il messaggio per un nuovo tentativo al raggiungimento di RETRY_AT o inserirlo nella DLQ.<\/li>\n<li>REASON \u2014 il motivo per cui l'elaborazione del messaggio \u00e8 stata interrotta.<\/li>\n<\/ul>\n<p><\/p>\n<p>Il Retryer conserva i messaggi per la reinvio e l'analisi manuale in PostgreSQL. Un task \u00e8 avviato da un timer, che trova i messaggi con RETRY_AT scaduto e li ri-invia nella partition ORIGINAL_PARTITION del topic DESTINATION con la chiave record.key().<\/p>\n<p><\/p>\n<p>Una volta inviato, i messaggi vengono eliminati da PostgreSQL. L'analisi manuale dei messaggi avviene in un'interfaccia utente semplice, che interagisce con Retryer tramite REST API. Le sue principali funzionalit\u00e0 includono la retransmissione o l'eliminazione dei messaggi dalla DLQ, la visualizzazione delle informazioni sugli errori e la ricerca dei messaggi, ad esempio per nome dell'errore. <\/p>\n<p><\/p>\n<p>Poich\u00e9 nei nostri cluster \u00e8 attivata la gestione degli accessi, \u00e8 necessario richiedere l'accesso al topic che ascolta Retryer e consentire a Retryer di scrivere nel topic DESTINATION. Questo \u00e8 scomodo, ma, a differenza dell'approccio con il topic a intervallo, abbiamo una DLQ completa e un'interfaccia utente per gestirla.<\/p>\n<p><\/p>\n<p>Ci sono casi in cui un topic in entrata \u00e8 letto da diversi gruppi di consumer, le cui applicazioni implementano logiche diverse. La rielaborazione di un messaggio tramite Retryer per una di queste applicazioni porter\u00e0 a un duplicato nell'altra. Per proteggersi da ci\u00f2, creiamo un topic separato per la rielaborazione. Il topic in entrata e il topic di retry possono essere letti dallo stesso Consumer senza alcuna limitazione. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Rielaborazione degli eventi ricevuti da Kafka\" src=\"\/wp-content\/uploads\/2020\/02\/06f2355839c6dd812ee3304bf2c72226.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Per impostazione predefinita, questo approccio non fornisce la possibilit\u00e0 di un circuito di interruzione, tuttavia pu\u00f2 essere aggiunto all'applicazione tramite <noindex><a rel=\"nofollow\" href=\"https:\/\/spring.io\/projects\/spring-cloud-netflix\">spring-cloud-netflix<\/a><\/noindex> o nuovo <noindex><a rel=\"nofollow\" href=\"https:\/\/spring.io\/projects\/spring-cloud-circuitbreaker\">spring cloud circuit breaker<\/a><\/noindex>, avvolgendo i punti di chiamata ai servizi esterni nelle rispettive astrazioni. Inoltre, si presenta la possibilit\u00e0 di scegliere la strategia per <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/architecture\/patterns\/bulkhead\">bulkhead<\/a><\/noindex> pattern, che pu\u00f2 essere utile. Ad esempio, in spring-cloud-netflix pu\u00f2 essere un thread pool o un semaforo.<\/p>\n<p><\/p>\n<h2 id=\"vyvod\">Risultato<\/h2>\n<p><\/p>\n<p>Di conseguenza, abbiamo ottenuto un'applicazione separata che consente di ripetere l'elaborazione del messaggio in caso di temporanea indisponibilit\u00e0 di un sistema esterno.<\/p>\n<p><\/p>\n<p>Uno dei principali vantaggi dell'applicazione \u00e8 che pu\u00f2 essere utilizzata da sistemi esterni che operano sullo stesso cluster Kafka, senza significative modifiche da parte loro! Questa applicazione avr\u00e0 solo bisogno di accedere al topic di retry, compilare alcuni header Kafka e inviare un messaggio al Retryer. Non \u00e8 necessario sollevare alcuna infrastruttura aggiuntiva. E per ridurre il numero di messaggi trasferiti dall'applicazione al Retryer e viceversa, abbiamo isolato applicazioni con logica lineare e realizzato il loro ripristino tramite l'interruzione del Consumer.<\/p>\n<p>Fonte: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tinkoff\/blog\/487094\/\">habr.com<\/a><\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u041f\u0440\u0438\u0432\u0435\u0442, \u0425\u0430\u0431\u0440. \u041d\u0435\u0434\u0430\u0432\u043d\u043e \u044f \u043f\u043e\u0434\u0435\u043b\u0438\u043b\u0441\u044f \u043e\u043f\u044b\u0442\u043e\u043c \u043e \u0442\u043e\u043c, \u043a\u0430\u043a\u0438\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043c\u044b \u0432 \u043a\u043e\u043c\u0430\u043d\u0434\u0435 \u0447\u0430\u0449\u0435 \u0432\u0441\u0435\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0434\u043b\u044f Kafka Producer \u0438 Consumer, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0438\u0431\u043b\u0438\u0437\u0438\u0442\u044c\u0441\u044f \u043a \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0439 \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0435. \u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u0445\u043e\u0447\u0443 \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u0430\u0442\u044c, \u043a\u0430\u043a \u043c\u044b \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u043e\u0432\u0430\u043b\u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u0443\u044e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u0441\u043e\u0431\u044b\u0442\u0438\u044f, \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u043e\u0433\u043e \u0438\u0437 Kafka, \u0432 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0435 \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439 \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0441\u0442\u0438 \u0432\u043d\u0435\u0448\u043d\u0435\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b. \u0421\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442 \u0432 \u043e\u0447\u0435\u043d\u044c \u0441\u043b\u043e\u0436\u043d\u043e\u0439 \u0441\u0440\u0435\u0434\u0435. \u0411\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0430, \u043e\u0431\u0435\u0440\u043d\u0443\u0442\u0430\u044f [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-56186","post","type-post","status-publish","format-standard","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 4.9.10 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u041f\u0440\u0438\u0432\u0435\u0442, \u0425\u0430\u0431\u0440. \u041d\u0435\u0434\u0430\u0432\u043d\u043e \u044f \u043f\u043e\u0434\u0435\u043b\u0438\u043b\u0441\u044f \u043e\u043f\u044b\u0442\u043e\u043c \u043e \u0442\u043e\u043c, \u043a\u0430\u043a\u0438\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043c\u044b \u0432 \u043a\u043e\u043c\u0430\u043d\u0434\u0435 \u0447\u0430\u0449\u0435 \u0432\u0441\u0435\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0434\u043b\u044f Kafka Producer \u0438 Consumer, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0438\u0431\u043b\u0438\u0437\u0438\u0442\u044c\u0441\u044f \u043a \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0439 \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0435. \u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u0445\u043e\u0447\u0443 \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u0430\u0442\u044c, \u043a\u0430\u043a \u043c\u044b \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u043e\u0432\u0430\u043b\u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u0443\u044e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u0441\u043e\u0431\u044b\u0442\u0438\u044f, \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u043e\u0433\u043e \u0438\u0437 Kafka, \u0432 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0435 \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439 \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0441\u0442\u0438 \u0432\u043d\u0435\u0448\u043d\u0435\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b. \u0421\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442 \u0432 \u043e\u0447\u0435\u043d\u044c \u0441\u043b\u043e\u0436\u043d\u043e\u0439 \u0441\u0440\u0435\u0434\u0435. \u0411\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0430, \u043e\u0431\u0435\u0440\u043d\u0443\u0442\u0430\u044f\" \/>\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\/povtornaya-obrabotka-sobytij-poluchennyh-iz-kafka\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 4.9.10\" \/>\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\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u0439, \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0445 \u0438\u0437 Kafka | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u041f\u0440\u0438\u0432\u0435\u0442, \u0425\u0430\u0431\u0440. \u041d\u0435\u0434\u0430\u0432\u043d\u043e \u044f \u043f\u043e\u0434\u0435\u043b\u0438\u043b\u0441\u044f \u043e\u043f\u044b\u0442\u043e\u043c \u043e \u0442\u043e\u043c, \u043a\u0430\u043a\u0438\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043c\u044b \u0432 \u043a\u043e\u043c\u0430\u043d\u0434\u0435 \u0447\u0430\u0449\u0435 \u0432\u0441\u0435\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0434\u043b\u044f Kafka Producer \u0438 Consumer, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0438\u0431\u043b\u0438\u0437\u0438\u0442\u044c\u0441\u044f \u043a \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0439 \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0435. \u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u0445\u043e\u0447\u0443 \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u0430\u0442\u044c, \u043a\u0430\u043a \u043c\u044b \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u043e\u0432\u0430\u043b\u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u0443\u044e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u0441\u043e\u0431\u044b\u0442\u0438\u044f, \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u043e\u0433\u043e \u0438\u0437 Kafka, \u0432 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0435 \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439 \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0441\u0442\u0438 \u0432\u043d\u0435\u0448\u043d\u0435\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b. \u0421\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442 \u0432 \u043e\u0447\u0435\u043d\u044c \u0441\u043b\u043e\u0436\u043d\u043e\u0439 \u0441\u0440\u0435\u0434\u0435. \u0411\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0430, \u043e\u0431\u0435\u0440\u043d\u0443\u0442\u0430\u044f\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/it\/blog\/administrirovanie\/povtornaya-obrabotka-sobytij-poluchennyh-iz-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=\"2020-02-05T21:00:00+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2020-02-18T11:04:24+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\udd47Ri-processamento degli eventi ricevuti da Kafka | ProHoster","description":"Ciao, Habr. Recentemente ho condiviso la mia esperienza su quali parametri utilizziamo pi\u00f9 spesso nel nostro team per Kafka Producer e Consumer, al fine di avvicinarci a una consegna garantita. In questo articolo voglio spiegare come abbiamo organizzato il ri-processamento di un evento ricevuto da Kafka a causa della temporanea indisponibilit\u00e0 di un sistema esterno. Le applicazioni moderne operano in un ambiente molto complesso. La logica di business \u00e8 racchiusa","canonical_url":"https:\/\/prohoster.info\/it\/blog\/administrirovanie\/povtornaya-obrabotka-sobytij-poluchennyh-iz-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\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u0439, \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0445 \u0438\u0437 Kafka | ProHoster","og:description":"\u041f\u0440\u0438\u0432\u0435\u0442, \u0425\u0430\u0431\u0440. \u041d\u0435\u0434\u0430\u0432\u043d\u043e \u044f \u043f\u043e\u0434\u0435\u043b\u0438\u043b\u0441\u044f \u043e\u043f\u044b\u0442\u043e\u043c \u043e \u0442\u043e\u043c, \u043a\u0430\u043a\u0438\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043c\u044b \u0432 \u043a\u043e\u043c\u0430\u043d\u0434\u0435 \u0447\u0430\u0449\u0435 \u0432\u0441\u0435\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0434\u043b\u044f Kafka Producer \u0438 Consumer, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0438\u0431\u043b\u0438\u0437\u0438\u0442\u044c\u0441\u044f \u043a \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0439 \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0435. \u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u0445\u043e\u0447\u0443 \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u0430\u0442\u044c, \u043a\u0430\u043a \u043c\u044b \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u043e\u0432\u0430\u043b\u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u0443\u044e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u0441\u043e\u0431\u044b\u0442\u0438\u044f, \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u043e\u0433\u043e \u0438\u0437 Kafka, \u0432 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0435 \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439 \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0441\u0442\u0438 \u0432\u043d\u0435\u0448\u043d\u0435\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b. \u0421\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442 \u0432 \u043e\u0447\u0435\u043d\u044c \u0441\u043b\u043e\u0436\u043d\u043e\u0439 \u0441\u0440\u0435\u0434\u0435. \u0411\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0430, \u043e\u0431\u0435\u0440\u043d\u0443\u0442\u0430\u044f","og:url":"https:\/\/prohoster.info\/it\/blog\/administrirovanie\/povtornaya-obrabotka-sobytij-poluchennyh-iz-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":"2020-02-05T21:00:00+00:00","article:modified_time":"2020-02-18T11:04:24+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"56186","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":null,"breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-02-28 19:29:19","updated":"2022-10-02 15:56:10"},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/posts\/56186","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=56186"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/posts\/56186\/revisions"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/media?parent=56186"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/categories?post=56186"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/tags?post=56186"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}