{"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\/de\/blog\/administrirovanie\/povtornaya-obrabotka-sobytij-poluchennyh-iz-kafka","title":{"rendered":"Ereigniswiederverarbeitung aus Kafka","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p><img decoding=\"async\" alt=\"Ereigniswiederverarbeitung aus Kafka\" src=\"\/wp-content\/uploads\/2020\/02\/d04319691af5c5de40ccfe4a2268558c.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Hallo, Habr.<\/p>\n<p><\/p>\n<p>Vor kurzem habe ich <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tinkoff\/blog\/481784\/\">meine Erfahrungen geteilt<\/a><\/noindex> dar\u00fcber, welche Parameter wir im Team am h\u00e4ufigsten f\u00fcr den Kafka Producer und Consumer verwenden, um eine garantierte Lieferung zu erreichen. In diesem Artikel m\u00f6chte ich erl\u00e4utern, wie wir die erneute Verarbeitung von Ereignissen organisiert haben, die wir aus Kafka empfangen, wenn ein externes System vor\u00fcbergehend nicht erreichbar ist.<\/p>\n<p><\/p>\n<p>Moderne Anwendungen arbeiten in einer sehr komplexen Umgebung. Die Gesch\u00e4ftsanwendungen, eingebettet in moderne Technologiestacks, laufen in Docker-Containern, die von einem Orchestrator wie Kubernetes oder OpenShift verwaltet werden, und kommunizieren mit anderen Anwendungen oder Unternehmensl\u00f6sungen \u00fcber eine Kette von physischen und virtuellen Routern. In einer solchen Umgebung kann immer etwas schiefgehen, daher ist die erneute Verarbeitung von Ereignissen im Falle der Nichtverf\u00fcgbarkeit eines externen Systems ein wichtiger Teil unserer Gesch\u00e4ftsprozesse.<\/p>\n<p><noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2 id=\"kak-bylo-do-kafka\">Wie es vor Kafka war<\/h2>\n<p><\/p>\n<p>Fr\u00fcher haben wir im Projekt IBM MQ f\u00fcr die asynchrone Zustellung von Nachrichten verwendet. Auftretende Fehler im Service konnten dazu f\u00fchren, dass die empfangene Nachricht in eine Dead-Letter-Queue (DLQ) gelegt wurde, um sie sp\u00e4ter manuell zu bearbeiten. Die DLQ wurde neben der eingehenden Warteschlange erstellt, und das Umlegen der Nachricht fand innerhalb von IBM MQ statt. <\/p>\n<p><\/p>\n<p>Wenn der Fehler vor\u00fcbergehender Natur war und wir dies feststellen konnten (zum Beispiel ResourceAccessException bei HTTP-Aufrufen oder MongoTimeoutException bei Anfragen an MongoDB), trat die R\u00fcckrufstrategie in Kraft. Unabh\u00e4ngig von den Verzweigungen in der Logik der Anwendung wurde die urspr\u00fcngliche Nachricht entweder in die Systemwarteschlange f\u00fcr verz\u00f6gerte Zustellungen oder in eine separate Anwendung gelegt, die einst f\u00fcr die erneute Zusendung von Nachrichten entwickelt wurde. Dabei wird der R\u00fccksendungsnummer im Nachrichtenkopf vermerkt, der an das Verz\u00f6gerungsintervall oder das Ende der Strategie auf Anwendungsebene gebunden ist. Wenn wir das Ende der Strategie erreicht haben, aber das externe System immer noch nicht verf\u00fcgbar ist, wird die Nachricht in die DLQ zur manuellen Bearbeitung gelegt.<\/p>\n<p><\/p>\n<h2 id=\"poisk-resheniya\">L\u00f6sungsfindung<\/h2>\n<p><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/www.google.com\/search?q=kafka+retry+message\">Nach einer Internetrecherche<\/a><\/noindex>, l\u00e4sst sich Folgendes finden <noindex><a rel=\"nofollow\" href=\"https:\/\/blog.pragmatists.com\/retrying-consumer-architecture-in-the-apache-kafka-939ac4cb851a\">L\u00f6sung<\/a><\/noindex>. Kurz gesagt, es wird vorgeschlagen, f\u00fcr jedes Delay-Intervall ein Topic einzurichten und Consumer auf der Anwendungseite zu implementieren, die die Nachrichten mit der erforderlichen Verz\u00f6gerung lesen werden. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ereigniswiederverarbeitung aus Kafka\" src=\"\/wp-content\/uploads\/2020\/02\/718c4c3f71ccf95b5a6f60e1b2c685d2.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Trotz der zahlreichen positiven R\u00fcckmeldungen erscheint es mir nicht ganz gelungen. Vor allem, weil der Entwickler neben der Umsetzung der Gesch\u00e4ftsanforderungen viel Zeit in die Implementierung des beschriebenen Mechanismus investieren muss.<\/p>\n<p><\/p>\n<p>Dar\u00fcber hinaus, wenn in einem Kafka-Cluster Zugriffsmanagement aktiviert ist, wird es einige Zeit kosten, Topics einzurichten und die erforderlichen Zugriffsrechte zu gew\u00e4hren. Hinzu kommt, dass der richtige Wert f\u00fcr retention.ms f\u00fcr jedes der Retry-Topics gefunden werden muss, um sicherzustellen, dass die Nachrichten rechtzeitig erneut gesendet werden und nicht verloren gehen. Die Implementierung und Beantragung von Zugriffsrechten muss f\u00fcr jede bestehende oder neue Dienstleistung wiederholt werden.<\/p>\n<p><\/p>\n<p>Schauen wir uns nun an, welche Mechanismen f\u00fcr die erneute Verarbeitung von Nachrichten Spring im Allgemeinen und Spring-Kafka im Besonderen bietet. Spring-Kafka hat eine transitive Abh\u00e4ngigkeit zu Spring-Retry, das Abstraktionen zur Verwaltung verschiedener BackOffPolicies bereitstellt. Dies ist ein ziemlich flexibles Werkzeug, aber ein wesentlicher Nachteil ist die Speicherung von Nachrichten zur erneuten Zustellung im Arbeitsspeicher der Anwendung. Das bedeutet, dass ein Neustart der Anwendung aufgrund eines Updates oder eines Fehlers w\u00e4hrend des Betriebs zum Verlust aller Nachrichten f\u00fchrt, die auf die erneute Verarbeitung warten. Da dieser Punkt kritisch f\u00fcr unser System ist, haben wir ihn nicht weiter untersucht.<\/p>\n<p><\/p>\n<p>Spring-Kafka selbst bietet mehrere Implementierungen von ContainerAwareErrorHandler, zum Beispiel <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>, mit dem es m\u00f6glich ist, die Nachricht sp\u00e4ter zu verarbeiten, ohne den Offset im Falle eines Fehlers zu verschieben. Seit Version 2.3 von Spring-Kafka gibt es die M\u00f6glichkeit, eine BackOffPolicy festzulegen.<\/p>\n<p><\/p>\n<p>Dieser Ansatz erm\u00f6glicht es verarbeiteten Nachrichten, einen Neustart der Anwendung zu \u00fcberstehen, jedoch fehlt nach wie vor der DLQ-Mechanismus. Genau diese Variante w\u00e4hlten wir Anfang 2019, optimistisch in der Annahme, dass wir keinen DLQ ben\u00f6tigen w\u00fcrden (wir hatten Gl\u00fcck, und tats\u00e4chlich ben\u00f6tigten wir ihn in den ersten Monaten des Betriebs dieser Wiederholungsbehandlungsanwendung nicht). Tempor\u00e4re Fehler f\u00fchrten zur Ausf\u00fchrung des SeekToCurrentErrorHandler. Andere Fehler wurden im Log protokolliert, f\u00fchrten zu einer Verschiebung des Offsets, und die Verarbeitung setzte mit der n\u00e4chsten Nachricht fort.<\/p>\n<p><\/p>\n<h2 id=\"itogovoe-reshenie\">Endg\u00fcltige Entscheidung<\/h2>\n<p><\/p>\n<p>Die Implementierung, die auf SeekToCurrentErrorHandler basiert, hat uns dazu angesto\u00dfen, einen eigenen Mechanismus zur Wiederholung des Sendens von Nachrichten zu entwickeln.<\/p>\n<p><\/p>\n<p>Zun\u00e4chst wollten wir die bestehenden Erfahrungen nutzen und sie je nach Logik der Anwendung erweitern. F\u00fcr eine Anwendung mit linearer Logik w\u00e4re es optimal, das Lesen neuer Nachrichten f\u00fcr einen kurzen Zeitraum, der im Rahmen der Retry-Strategie festgelegt ist, zu unterbrechen. F\u00fcr andere Anwendungen w\u00e4re es w\u00fcnschenswert, einen zentralen Punkt zu haben, der die Umsetzung der Retry-Strategie gew\u00e4hrleistet. Dar\u00fcber hinaus sollte dieser zentrale Punkt \u00fcber die Funktionalit\u00e4t einer DLQ f\u00fcr beide Ans\u00e4tze verf\u00fcgen.<\/p>\n<p><\/p>\n<p>Die Retry-Strategie selbst sollte in der Anwendung gespeichert werden, die f\u00fcr den Empfang des n\u00e4chsten Intervalls bei Auftreten eines tempor\u00e4ren Fehlers verantwortlich ist.<\/p>\n<p><\/p>\n<h3 id=\"ostanovka-consumera-dlya-prilozheniya-s-lineynoy-logikoy\">Anhalten des Consumers f\u00fcr eine Anwendung mit linearer Logik<\/h3>\n<p><\/p>\n<p>Bei der Arbeit mit spring-kafka k\u00f6nnte der Code zum Anhalten des Consumers etwa so aussehen:<\/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        \/\/ zur DLQ\n    }<\/code><\/pre>\n<p><\/p>\n<p>In dem Beispiel retryAt ist dies der Zeitpunkt, an dem der MessageListenerContainer neu gestartet werden muss, falls er noch aktiv ist. Der Neustart erfolgt in einem separaten Thread, der im TaskScheduler gestartet wird, dessen Implementierung ebenfalls von Spring bereitgestellt wird. <\/p>\n<p><\/p>\n<p>Der Wert retryAt wird folgenderma\u00dfen ermittelt:<\/p>\n<p><\/p>\n<ol>\n<li>Es wird der Wert des Retry-Z\u00e4hlers gesucht.<\/li>\n<li>Entsprechend dem Wert des Z\u00e4hlers wird das aktuelle Verz\u00f6gerungsintervall in der Retry-Strategie gesucht. Die Strategie wird in der Anwendung selbst definiert, wir haben das JSON-Format f\u00fcr ihre Speicherung gew\u00e4hlt.<\/li>\n<li>Das im JSON-Array gefundene Intervall enth\u00e4lt die Anzahl der Sekunden, nach denen die Verarbeitung wiederholt werden muss. Diese Anzahl wird zur aktuellen Zeit hinzugef\u00fcgt, um den Wert f\u00fcr retryAt zu bilden.<\/li>\n<li>Wenn das Intervall nicht gefunden wird, ist der Wert retryAt null und die Nachricht wird zur manuellen Analyse in die DLQ gesendet.<\/li>\n<\/ol>\n<p><\/p>\n<p>Bei diesem Ansatz bleibt nur, die Anzahl der Wiederholungsaufrufe f\u00fcr jede Nachricht zu speichern, die sich derzeit in der Verarbeitung befindet, zum Beispiel im Arbeitsspeicher der Anwendung. Das Speichern des Versuchsz\u00e4hlers im Speicher ist f\u00fcr diesen Ansatz nicht kritisch, da eine Anwendung mit linearer Logik die Verarbeitung insgesamt nicht durchf\u00fchren kann. Im Gegensatz zu spring-retry f\u00fchrt ein Neustart der Anwendung nicht zum Verlust aller Nachrichten zur erneuten Verarbeitung, sondern lediglich zum Neustart der Strategie. <\/p>\n<p><\/p>\n<p>Dieser Ansatz hilft, die Last von externen Systemen zu verringern, die aufgrund einer sehr hohen Auslastung m\u00f6glicherweise nicht verf\u00fcgbar sind. Anders ausgedr\u00fcckt, haben wir neben der erneuten Verarbeitung auch das Muster implementiert. <noindex><a rel=\"nofollow\" href=\"https:\/\/microservices.io\/patterns\/reliability\/circuit-breaker.html\">circuit breaker<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p>In unserem Fall liegt der Fehlerthreshold nur bei 1, und um die Ausfallzeiten des Systems aufgrund vor\u00fcbergehender Netzwerkunterbrechungen zu minimieren, verwenden wir eine sehr granulare Wiederholungsstrategie mit kurzen Verz\u00f6gerungsintervallen. Dies k\u00f6nnte nicht f\u00fcr alle Anwendungen der Unternehmensgruppe geeignet sein, daher muss das Verh\u00e4ltnis zwischen dem Fehlerthreshold und der L\u00e4nge des Intervalls je nach den Besonderheiten des Systems angepasst werden.<\/p>\n<p><\/p>\n<h3 id=\"otdelnoe-prilozhenie-dlya-obrabotki-soobscheniy-ot-prilozheniy-s-nedeterminirovannoy-logikoy\">Eine separate Anwendung zur Verarbeitung von Nachrichten aus Anwendungen mit nichtdeterministischer Logik<\/h3>\n<p><\/p>\n<p>Hier ist ein Beispielcode, der eine Nachricht an eine solche Anwendung (Retryer) sendet, die bei Erreichen der Zeit RETRY_AT erneut an das Thema DESTINATION sendet:<\/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>Aus dem Beispiel geht hervor, dass viele Informationen in den Headern \u00fcbermittelt werden. Der Wert RETRY_AT ist ebenfalls vorhanden, wie es beim Wiederholungsmechanismus durch die Unterbrechung des Consumers der Fall ist. Neben DESTINATION und RETRY_AT \u00fcbermitteln wir:<\/p>\n<p><\/p>\n<ul>\n<li>GROUP_ID, nach dem wir Nachrichten f\u00fcr die manuelle Analyse gruppieren und die Suche vereinfachen.<\/li>\n<li>ORIGINAL_PARTITION, um zu versuchen, denselben Consumer f\u00fcr die Wiederverarbeitung zu verwenden. Dieser Parameter kann null sein, in diesem Fall wird eine neue Partition anhand des Schl\u00fcssels record.key() der urspr\u00fcnglichen Nachricht erhalten.<\/li>\n<li>Das aktualisierte COUNTER-Wert, um der Strategie der Wiederholungsaufrufe zu folgen.<\/li>\n<li>SEND_TO \u2014 eine Konstante, die angibt, ob die Nachricht zur Wiederverarbeitung gesendet werden soll, wenn RETRY_AT erreicht wird, oder ob sie in die DLQ verschoben wird.<\/li>\n<li>REASON \u2014 der Grund, warum die Verarbeitung der Nachricht unterbrochen wurde.<\/li>\n<\/ul>\n<p><\/p>\n<p>Der Retryer speichert Nachrichten zur Wiederzusendung und manuellen Analyse in PostgreSQL. \u00dcber einen Timer wird eine Aufgabe gestartet, die Nachrichten mit dem erreichten RETRY_AT findet und sie zur\u00fcck in die ORIGINAL_PARTITION des Ziel-Topics mit dem Schl\u00fcssel record.key() sendet.<\/p>\n<p><\/p>\n<p>Nach dem Senden werden die Nachrichten aus PostgreSQL gel\u00f6scht. Die manuelle Verarbeitung der Nachrichten erfolgt \u00fcber eine einfache UI, die \u00fcber REST API mit dem Retryer interagiert. Zu den Hauptfunktionen geh\u00f6ren die erneute Zustellung oder L\u00f6schung von Nachrichten aus der DLQ, die Anzeige von Fehlerinformationen und die Suche nach Nachrichten, zum Beispiel nach dem Fehlernamen. <\/p>\n<p><\/p>\n<p>Da in unseren Clustern die Zugriffssteuerung aktiviert ist, m\u00fcssen zus\u00e4tzlich Zugriffsrechte f\u00fcr das Topic, das der Retryer h\u00f6rt, angefordert werden, und der Retryer muss die Berechtigung erhalten, in das DESTINATION Topic zu schreiben. Das ist unpraktisch, aber im Gegensatz zu dem Ansatz mit einem Intervall-Topic erhalten wir eine vollst\u00e4ndige DLQ und eine UI zur Verwaltung.<\/p>\n<p><\/p>\n<p>Es gibt F\u00e4lle, in denen ein eingehendes Topic von mehreren Consumer-Gruppen gelesen wird, deren Anwendungen unterschiedliche Logik implementieren. Die erneute Verarbeitung einer Nachricht \u00fcber den Retryer f\u00fcr eine dieser Anwendungen f\u00fchrt zu einem Duplikat bei einer anderen. Um sich davor zu sch\u00fctzen, richten wir ein separates Topic f\u00fcr die erneute Verarbeitung ein. Das eingehende und das Retry-Topic k\u00f6nnen ohne Einschr\u00e4nkungen von demselben Consumer gelesen werden. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ereigniswiederverarbeitung aus Kafka\" src=\"\/wp-content\/uploads\/2020\/02\/06f2355839c6dd812ee3304bf2c72226.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Standardm\u00e4\u00dfig bietet dieser Ansatz keine M\u00f6glichkeit f\u00fcr einen Circuit Breaker, jedoch kann dieser mit Hilfe von <noindex><a rel=\"nofollow\" href=\"https:\/\/spring.io\/projects\/spring-cloud-netflix\">spring-cloud-netflix<\/a><\/noindex> oder neu <noindex><a rel=\"nofollow\" href=\"https:\/\/spring.io\/projects\/spring-cloud-circuitbreaker\">Spring Cloud Circuit Breaker<\/a><\/noindex>, indem die Aufrufstellen externer Dienste in die entsprechenden Abstraktionen gekapselt werden. Zudem besteht die M\u00f6glichkeit, eine Strategie f\u00fcr <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/architecture\/patterns\/bulkhead\">Bulkhead<\/a><\/noindex> Muster auszuw\u00e4hlen, was ebenfalls n\u00fctzlich sein kann. Beispielsweise kann es in Spring Cloud Netflix ein Thread-Pool oder ein Semaphore sein.<\/p>\n<p><\/p>\n<h2 id=\"vyvod\">Fazit<\/h2>\n<p><\/p>\n<p>So entstand eine eigenst\u00e4ndige Anwendung, die es erm\u00f6glicht, die Verarbeitung einer Nachricht bei vor\u00fcbergehender Nichterreichbarkeit eines externen Systems zu wiederholen.<\/p>\n<p><\/p>\n<p>Ein wesentlicher Vorteil der Anwendung ist, dass externe Systeme, die im selben Kafka-Cluster arbeiten, sie ohne wesentliche Anpassungen nutzen k\u00f6nnen! Diese Anwendung ben\u00f6tigt lediglich den Zugang zum Retry-Topic, muss einige Kafka-Header ausf\u00fcllen und die Nachricht an den Retryer senden. Es ist keine zus\u00e4tzliche Infrastruktur notwendig. Um die Anzahl der \u00fcbermittelten Nachrichten zwischen der Anwendung und dem Retryer zu minimieren, haben wir Anwendungen mit linearer Logik getrennt und die Wiederverarbeitung \u00fcber das Stoppen des Consumers realisiert.<\/p>\n<p>Quelle: <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\/de\/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=\"de_DE\" \/>\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\/de\/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\udd47Erneute Verarbeitung von Ereignissen, die aus Kafka empfangen wurden | ProHoster","description":"Hallo, Habr. K\u00fcrzlich habe ich meine Erfahrungen dar\u00fcber geteilt, welche Parameter wir im Team am h\u00e4ufigsten f\u00fcr Kafka Producer und Consumer verwenden, um eine garantierte Lieferung zu erreichen. In diesem Artikel m\u00f6chte ich erl\u00e4utern, wie wir die erneute Verarbeitung von Ereignissen organisiert haben, die aus Kafka empfangen wurden, infolge der vor\u00fcbergehenden Unzug\u00e4nglichkeit eines externen Systems. Moderne Anwendungen arbeiten in einer sehr komplexen Umgebung. Die Gesch\u00e4ftslogik ist umh\u00fcllt","canonical_url":"https:\/\/prohoster.info\/de\/blog\/administrirovanie\/povtornaya-obrabotka-sobytij-poluchennyh-iz-kafka","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"de_DE","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\/de\/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\/de\/wp-json\/wp\/v2\/posts\/56186","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/comments?post=56186"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/posts\/56186\/revisions"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/media?parent=56186"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/categories?post=56186"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/tags?post=56186"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}