{"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":"Wiederverarbeitung von Ereignissen aus Kafka","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p><img decoding=\"async\" alt=\"Wiederverarbeitung von Ereignissen 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 Kafka Producer und Consumer verwenden, um eine garantierte Lieferung zu erreichen. In diesem Artikel m\u00f6chte ich erl\u00e4utern, wie wir die Verarbeitung von Ereignissen organisiert haben, die aus Kafka erhalten wurden, aufgrund der vor\u00fcbergehenden Nichterreichbarkeit eines externen Systems.<\/p>\n<p><\/p>\n<p>Moderne Anwendungen arbeiten in einer sehr komplexen Umgebung. Die Gesch\u00e4ftslogik, eingeh\u00fcllt in einen modernen Technologiestack, der in einem Docker-Image l\u00e4uft, welches von einem Orchestrator wie Kubernetes oder OpenShift verwaltet wird, und das mit anderen Anwendungen oder Unternehmensl\u00f6sungen \u00fcber eine Kette physischer und virtueller Router kommuniziert. In einer solchen Umgebung kann immer etwas kaputt gehen, weshalb die Wiederverarbeitung von Ereignissen bei Nichterreichbarkeit eines externen Systems ein wichtiger Teil unserer Gesch\u00e4ftsprozesse ist.<\/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. Wenn ein Fehler im Betrieb des Dienstes auftrat, konnte die empfangene Nachricht in eine Dead-Letter-Queue (DLQ) zur weiteren manuellen Analyse verschoben werden. Die DLQ wurde neben der eingehenden Warteschlange erstellt, die Verschiebung der Nachricht fand innerhalb von IBM MQ statt. <\/p>\n<p><\/p>\n<p>Wenn der Fehler vor\u00fcbergehender Natur war und wir dies bestimmen konnten (zum Beispiel ResourceAccessException bei einem HTTP-Aufruf oder MongoTimeoutException bei einer Anfrage an MongoDb), trat eine Strategie f\u00fcr Wiederholungsaufrufe in Kraft. Unabh\u00e4ngig von der Logik des Anwendungsszenarios wurde die urspr\u00fcngliche Nachricht entweder in die systemweite Warteschlange f\u00fcr verz\u00f6gerte Zustellungen verschoben oder in eine separate Anwendung, die einst zur Wiederzusendung von Nachrichten entwickelt wurde. Dabei wird in den Header der Nachricht die Anzahl der Wiederholungen geschrieben, die an den Verz\u00f6gerungsintervall oder das Ende der Strategie auf Anwendungsebene gebunden ist. Wenn wir das Ende der Strategie erreicht haben, die externe Systeme jedoch weiterhin nicht erreichbar sind, wird die Nachricht in die DLQ zur manuellen Pr\u00fcfung verschoben.<\/p>\n<p><\/p>\n<h2 id=\"poisk-resheniya\">Suche nach einer L\u00f6sung<\/h2>\n<p><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/www.google.com\/search?q=kafka+retry+message\">Nach einer Internetsuche<\/a><\/noindex>, kann Folgendes gefunden werden <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 Verz\u00f6gerungsintervall ein Thema zu erstellen und auf der Anwendungsseite Consumer zu implementieren, die Nachrichten mit der ben\u00f6tigten Verz\u00f6gerung lesen. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Wiederverarbeitung von Ereignissen aus Kafka\" src=\"\/wp-content\/uploads\/2020\/02\/718c4c3f71ccf95b5a6f60e1b2c685d2.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Trotz der vielen positiven Bewertungen scheint es mir nicht ganz gelungen zu sein. Vor allem weil der Entwickler, neben der Umsetzung der Gesch\u00e4ftsanforderungen, viel Zeit mit der Implementierung des beschriebenen Mechanismus verbringen muss.<\/p>\n<p><\/p>\n<p>Dar\u00fcber hinaus, wenn auf dem Kafka-Cluster die Zugriffsverwaltung aktiviert ist, muss einige Zeit f\u00fcr die Erstellung von Topics und die Gew\u00e4hrleistung der notwendigen Zugriffsrechte auf diese aufgewendet werden. Zus\u00e4tzlich muss der richtige Parameter retention.ms f\u00fcr jedes der Retry-Topics festgelegt werden, damit die Nachrichten rechtzeitig erneut gesendet werden und nicht verloren gehen. Die Implementierung und Anforderung von Zug\u00e4ngen muss f\u00fcr jeden bestehenden oder neuen Dienst wiederholt werden.<\/p>\n<p><\/p>\n<p>Lassen Sie uns nun betrachten, 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 von Spring-Retry, das Abstraktionen f\u00fcr die Verwaltung verschiedener BackOffPolicies bereitstellt. Es ist ein ziemlich flexibles Werkzeug, allerdings ist ein wesentlicher Nachteil die Speicherung von Nachrichten f\u00fcr die erneute Sendung 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 eine erneute Verarbeitung warten. Da dieser Punkt f\u00fcr unser System entscheidend ist, haben wir ihn nicht weiter betrachtet.<\/p>\n<p><\/p>\n<p>Spring-Kafka selbst bietet mehrere Implementierungen von ContainerAwareErrorHandler, wie 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 Offset im Fehlerfall nicht zu verschieben und die Nachricht sp\u00e4ter zu verarbeiten. Seit der Version 2.3 von Spring-Kafka gibt es die M\u00f6glichkeit, eine BackOffPolicy zu definieren.<\/p>\n<p><\/p>\n<p>Dieser Ansatz erm\u00f6glicht es, dass erneut verarbeitete Nachrichten einen Neustart der Anwendung \u00fcberstehen, jedoch fehlt nach wie vor der Mechanismus f\u00fcr Dead Letter Queues (DLQ). Diesen Ansatz w\u00e4hlten wir Anfang 2019 in der optimistischen Annahme, dass eine DLQ nicht erforderlich sein w\u00fcrde (wir hatten das Gl\u00fcck, dass sie in den ersten Monaten des Betriebs der Anwendung mit diesem Wiederverarbeitungssystem tats\u00e4chlich nicht ben\u00f6tigt wurde). Tempor\u00e4re Fehler f\u00fchrten zur Aktivierung des SeekToCurrentErrorHandler. Andere Fehler wurden in das Protokoll geschrieben, f\u00fchrten zur Verschiebung des Offsets und die Verarbeitung wurde mit der n\u00e4chsten Nachricht fortgesetzt.<\/p>\n<p><\/p>\n<h2 id=\"itogovoe-reshenie\">Die endg\u00fcltige Entscheidung<\/h2>\n<p><\/p>\n<p>Die auf SeekToCurrentErrorHandler basierende Implementierung hat uns dazu angeregt, einen eigenen Mechanismus zur erneuten \u00dcbermittlung von Nachrichten zu entwickeln.<\/p>\n<p><\/p>\n<p>Zun\u00e4chst wollten wir die bereits vorhandene Erfahrung 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 Strategien f\u00fcr erneute Aufrufe festgelegt wurde, zu unterbrechen. F\u00fcr andere Anwendungen wollte man einen zentralen Punkt haben, der die Durchf\u00fchrung der Wiederholungsstrategien gew\u00e4hrleistet. Dar\u00fcber hinaus sollte dieser zentrale Punkt \u00fcber die DLQ-Funktionalit\u00e4t f\u00fcr beide Ans\u00e4tze verf\u00fcgen.<\/p>\n<p><\/p>\n<p>Die Wiederholungsstrategie selbst sollte in der Anwendung gespeichert werden, die f\u00fcr den Empfang des n\u00e4chsten Intervalls bei einem zeitlichen Fehler verantwortlich ist.<\/p>\n<p><\/p>\n<h3 id=\"ostanovka-consumera-dlya-prilozheniya-s-lineynoy-logikoy\">Anhalten des Consumers f\u00fcr die Anwendung mit linearer Logik<\/h3>\n<p><\/p>\n<p>Beim Arbeiten mit spring-kafka k\u00f6nnte der Code zum Stoppen 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        \/\/ an DLQ\n    }<\/code><\/pre>\n<p><\/p>\n<p>Im Beispiel ist retryAt der Zeitpunkt, zu dem das MessageListenerContainer neu gestartet werden muss, wenn es noch l\u00e4uft. 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>Wir finden den Wert von retryAt auf die folgende Weise:<\/p>\n<p><\/p>\n<ol>\n<li>Der Wert des Wiederholungsz\u00e4hlers wird gesucht.<\/li>\n<li>Entsprechend dem Wert des Z\u00e4hlers wird das aktuelle Verz\u00f6gerungsintervall in der Strategie f\u00fcr erneute Aufrufe gesucht. Die Strategie wird in der Anwendung selbst erkl\u00e4rt; f\u00fcr ihre Speicherung haben wir das JSON-Format 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 von Sekunden wird zur aktuellen Zeit addiert, um den Wert f\u00fcr retryAt zu bilden.<\/li>\n<li>Wenn das Intervall nicht gefunden wird, ist der Wert von retryAt null und die Nachricht wird zur manuellen \u00dcberpr\u00fcfung in die DLQ gesendet.<\/li>\n<\/ol>\n<p><\/p>\n<p>Bei diesem Ansatz bleibt nur die M\u00f6glichkeit, die Anzahl der Wiederholungsversuche f\u00fcr jede Nachricht, die derzeit verarbeitet wird, beispielsweise im Arbeitsspeicher der Anwendung zu speichern. Das Speichern des Z\u00e4hlerstands im Speicher ist f\u00fcr diesen Ansatz nicht kritisch, da Anwendungen mit linearer Logik die Verarbeitung insgesamt nicht durchf\u00fchren k\u00f6nnen. Im Gegensatz zu spring-retry f\u00fchrt ein Neustart der Anwendung nicht zum Verlust aller Nachrichten f\u00fcr die erneute Verarbeitung, sondern einfach zum Neustart der Strategie. <\/p>\n<p><\/p>\n<p>Dieser Ansatz hilft, die Belastung f\u00fcr externe Systeme zu verringern, die aufgrund einer sehr hohen Last m\u00f6glicherweise nicht verf\u00fcgbar sind. Anders ausgedr\u00fcckt, zus\u00e4tzlich zur erneuten Verarbeitung haben wir die Implementierung des Musters erreicht. <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 Fehlergrenzwert nur bei 1, und um die Ausfallzeit des Systems aufgrund vor\u00fcbergehender Netzwerkunterbrechungen zu minimieren, verwenden wir eine sehr granulare Wiederholungsstrategie mit kurzen Verz\u00f6gerungsintervallen. Dies ist m\u00f6glicherweise nicht f\u00fcr alle Anwendungen der Unternehmensgruppe geeignet, daher muss das Verh\u00e4ltnis zwischen Fehlergrenzwert und Intervallgr\u00f6\u00dfe an die 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 von Anwendungen mit undeterministischer Logik.<\/h3>\n<p><\/p>\n<p>Hier ist ein Beispielcode, der eine Nachricht an eine solche Anwendung (Retryer) sendet, die die erneute Sendung an das Thema DESTINATION zur Zeit RETRY_AT durchf\u00fchren wird:<\/p>\n<p><\/p>\n<pre><code class=\"java\">\npublic  void retry(ConsumerRecord record, String retryToTopic, \n                         Instant retryAt, String counter, String groupId, Exception e) {\n        Headers headers = ofNullable(record.headers()).orElse(new RecordHeaders());\n        List<Header> arrayOfHeaders = \n            new ArrayList(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, \"retry\"::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, \"backout\"::getBytes);\n        }\n        ProducerRecord messageToSend =\n            new ProducerRecord(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 \u00fcbertragen werden. Der Wert RETRY_AT befindet sich ebenfalls wie beim Wiederholungsmechanismus \u00fcber das Stoppen des Consumers. Neben DESTINATION und RETRY_AT \u00fcbergeben wir:<\/p>\n<p><\/p>\n<ul>\n<li>GROUP_ID, nach dem wir Nachrichten f\u00fcr die manuelle Analyse und die Vereinfachung der Suche gruppieren.<\/li>\n<li>ORIGINAL_PARTITION, um zu versuchen, denselben Consumer f\u00fcr die Wiederverarbeitung beizubehalten. Dieser Parameter kann null sein, in diesem Fall wird eine neue Partition basierend auf dem Schl\u00fcssel record.key() der urspr\u00fcnglichen Nachricht erhalten.<\/li>\n<li>Aktualisierter Wert COUNTER, 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 werden soll.<\/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. Ein Timer startet eine Aufgabe, die Nachrichten mit abgelaufenem RETRY_AT findet und sie zur\u00fcck in die Partition ORIGINAL_PARTITION des Ziels mit dem Schl\u00fcssel record.key() sendet.<\/p>\n<p><\/p>\n<p>Nach dem Versenden werden die Nachrichten aus PostgreSQL gel\u00f6scht. Die manuelle Analyse der Nachrichten erfolgt in einer einfachen Benutzeroberfl\u00e4che, die \u00fcber eine REST-API mit dem Retryer interagiert. Zu den Hauptfunktionen geh\u00f6ren das erneute Senden oder L\u00f6schen von Nachrichten aus der DLQ, das Anzeigen von Fehlermeldungen und das Suchen nach Nachrichten, z. B. nach Fehlermeldungen. <\/p>\n<p><\/p>\n<p>Da in unseren Clustern die Zugriffskontrolle aktiviert ist, m\u00fcssen zus\u00e4tzlich Zugriffsrechte f\u00fcr das Thema angefordert werden, das der Retryer abh\u00f6rt, und es muss dem Retryer erm\u00f6glicht werden, in das DESTINATION-Thema zu schreiben. Das ist unpraktisch, aber im Gegensatz zum Ansatz mit einem Intervall-Thema erhalten wir eine vollst\u00e4ndige DLQ und eine Benutzeroberfl\u00e4che zu ihrer Verwaltung.<\/p>\n<p><\/p>\n<p>Es gibt F\u00e4lle, in denen das eingehende Thema von mehreren verschiedenen Consumer-Gruppen gelesen wird, deren Anwendungen unterschiedliche Logik implementieren. Die Wiederverarbeitung von Nachrichten durch den Retryer f\u00fcr eine dieser Anwendungen f\u00fchrt zu Duplikaten in einer anderen. Um dies zu vermeiden, richten wir ein separates Thema f\u00fcr die Wiederverarbeitung ein. Das eingehende und das Retry-Thema kann von demselben Consumer ohne Einschr\u00e4nkungen gelesen werden. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Wiederverarbeitung von Ereignissen 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 eines Circuit Breakers, allerdings kann diese Funktionalit\u00e4t in die Anwendung integriert werden mit <noindex><a rel=\"nofollow\" href=\"https:\/\/spring.io\/projects\/spring-cloud-netflix\">spring-cloud-netflix<\/a><\/noindex> oder dem neuen <noindex><a rel=\"nofollow\" href=\"https:\/\/spring.io\/projects\/spring-cloud-circuitbreaker\">spring cloud circuit breaker<\/a><\/noindex>, indem die Aufrufe externer Dienste in die entsprechenden Abstraktionen eingebettet werden. Dar\u00fcber hinaus besteht die M\u00f6glichkeit, eine Strategie f\u00fcr das <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. Zum Beispiel kann es in spring-cloud-netflix ein Thread-Pool oder ein Semaphore sein.<\/p>\n<p><\/p>\n<h2 id=\"vyvod\">Ausgabe<\/h2>\n<p><\/p>\n<p>Infolgedessen haben wir eine separate Anwendung entwickelt, die es erm\u00f6glicht, die Verarbeitung von Nachrichten bei vor\u00fcbergehender Nichterreichbarkeit eines externen Systems zu wiederholen.<\/p>\n<p><\/p>\n<p>Einer der Hauptvorteile der Anwendung besteht darin, dass externe Systeme, die im selben Kafka-Cluster arbeiten, sie ohne bedeutende \u00c4nderungen auf ihrer Seite nutzen k\u00f6nnen! Diese Anwendung ben\u00f6tigt lediglich Zugriff auf das Retry-Topic, muss einige Kafka-Header ausf\u00fcllen und die Nachricht an den Retryer senden. Es ist keine zus\u00e4tzliche Infrastruktur erforderlich. Um die Anzahl der umgeschichteten Nachrichten von der Anwendung zum Retryer und zur\u00fcck zu reduzieren, haben wir Anwendungen mit linearer Logik \u0432\u044b\u0434\u0435\u043b\u0438\u043b\u0438 und in ihnen die Wiederverarbeitung \u00fcber das Stoppen des Consumers umgesetzt.<\/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 5.0.1.1 - 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.\" \/>\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) 5.0.1.1\" \/>\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.\" \/>\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\udd47Wiederverarbeitung von Ereignissen, die aus Kafka empfangen wurden | ProHoster","description":"Hallo, Habr. K\u00fcrzlich.","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.","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","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"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}]}}