{"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\/sk\/blog\/administrirovanie\/povtornaya-obrabotka-sobytij-poluchennyh-iz-kafka","title":{"rendered":"Op\u00e4tovn\u00e9 spracovanie udalost\u00ed prijat\u00fdch z Kafka","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p><img decoding=\"async\" alt=\"Op\u00e4tovn\u00e9 spracovanie udalost\u00ed prijat\u00fdch z Kafka\" src=\"\/wp-content\/uploads\/2020\/02\/d04319691af5c5de40ccfe4a2268558c.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Ahoj, Habr.<\/p>\n<p><\/p>\n<p>Ned\u00e1vno som <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tinkoff\/blog\/481784\/\">zdie\u013eal sk\u00fasenosti<\/a><\/noindex> o tom, ak\u00e9 parametre v t\u00edme naj\u010dastej\u0161ie pou\u017e\u00edvame pre Kafka Producer a Consumer, aby sme sa pribl\u00ed\u017eili k garantovanej dod\u00e1vke. V tomto \u010dl\u00e1nku chcem poveda\u0165, ako sme zorganizovali op\u00e4tovn\u00e9 spracovanie udalosti prijatej z Kafka v d\u00f4sledku do\u010dasnej nedostupnosti vonkaj\u0161ieho syst\u00e9mu.<\/p>\n<p><\/p>\n<p>S\u00fa\u010dasn\u00e9 aplik\u00e1cie funguj\u00fa v ve\u013emi zlo\u017eitom prostred\u00ed. Biznis logika, zabalen\u00e1 v modernom technologickom stacku, be\u017eiaca v Docker obr\u00e1zku, ktor\u00fd spravuje orchestr\u00e1tor ako Kubernetes alebo OpenShift, a komunikuj\u00faca s in\u00fdmi aplik\u00e1ciami alebo enterprise rie\u0161eniami prostredn\u00edctvom re\u0165azca fyzick\u00fdch a virtu\u00e1lnych smerova\u010dov. V takomto prostred\u00ed sa v\u017edy m\u00f4\u017ee nie\u010do pokazi\u0165, preto je op\u00e4tovn\u00e9 spracovanie udalost\u00ed v pr\u00edpade nedostupnosti jedn\u00e9ho z vonkaj\u0161\u00edch syst\u00e9mov d\u00f4le\u017eitou s\u00fa\u010das\u0165ou na\u0161ich biznis procesov.<\/p>\n<p><noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2 id=\"kak-bylo-do-kafka\">Ako to bolo pred Kafka<\/h2>\n<p><\/p>\n<p>Predt\u00fdm sme v projekte pou\u017e\u00edvali IBM MQ naasynchr\u00f3nny prenos spr\u00e1v. Ak do\u0161lo k nejakej chybe po\u010das fungovania slu\u017eby, prijat\u00e1 spr\u00e1va mohla by\u0165 umiestnen\u00e1 do dead-letter-queue (DLQ) na \u010fal\u0161ie manu\u00e1lne spracovanie. DLQ sa vytv\u00e1ral ved\u013ea vstupnej fronty a presun spr\u00e1vy prebiehal v r\u00e1mci IBM MQ. <\/p>\n<p><\/p>\n<p>Ak bola chyba do\u010dasn\u00e1 a mohli sme to ur\u010di\u0165 (napr\u00edklad ResourceAccessException pri HTTP volan\u00ed alebo MongoTimeoutException pri dotaze do MongoDb), do platnosti vst\u00fapila strat\u00e9gia opakovan\u00fdch volan\u00ed. Bez oh\u013eadu na vetvenie logiky aplik\u00e1cie bola p\u00f4vodn\u00e1 spr\u00e1va pres\u00favan\u00e1 bu\u010f do syst\u00e9movej fronty na oneskoren\u00e9 odoslanie, alebo do samostatnej aplik\u00e1cie, ktor\u00e1 bola d\u00e1vno vytvoren\u00e1 na op\u00e4tovn\u00e9 odosielanie spr\u00e1v. Pri tom sa do hlavi\u010dky spr\u00e1vy zapisuje \u010d\u00edslo op\u00e4tovn\u00e9ho odoslania, ktor\u00e9 je viazan\u00e9 na \u010dasov\u00e9 oneskorenie alebo na koniec strat\u00e9gie na \u00farovni aplik\u00e1cie. Ak sme dosiahli koniec strat\u00e9gie, ale vonkaj\u0161\u00ed syst\u00e9m je st\u00e1le nedostupn\u00fd, spr\u00e1va bude umiestnen\u00e1 do DLQ na manu\u00e1lne spracovanie.<\/p>\n<p><\/p>\n<h2 id=\"poisk-resheniya\">H\u013eadanie rie\u0161enia<\/h2>\n<p><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/www.google.com\/search?q=kafka+retry+message\">H\u013eadanie na internete<\/a><\/noindex>, m\u00f4\u017eete n\u00e1js\u0165 nasleduj\u00face <noindex><a rel=\"nofollow\" href=\"https:\/\/blog.pragmatists.com\/retrying-consumer-architecture-in-the-apache-kafka-939ac4cb851a\">rie\u0161enia.<\/a><\/noindex>. Ak to zhrnieme, navrhuje sa zavies\u0165 po jednu t\u00e9mu na ka\u017ed\u00fd interval oneskorenia a realizova\u0165 na strane aplik\u00e1cie Consumerov, ktor\u00ed bud\u00fa \u010d\u00edta\u0165 spr\u00e1vy s potrebn\u00fdm oneskoren\u00edm. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Op\u00e4tovn\u00e9 spracovanie udalost\u00ed prijat\u00fdch z Kafka\" src=\"\/wp-content\/uploads\/2020\/02\/718c4c3f71ccf95b5a6f60e1b2c685d2.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Hoci m\u00e1 ve\u013ea pozit\u00edvnych recenzi\u00ed, zd\u00e1 sa mi, \u017ee nie je \u00faplne \u00faspe\u0161n\u00e9. Predov\u0161etk\u00fdm preto, \u017ee v\u00fdvoj\u00e1rovi bude okrem implement\u00e1cie obchodn\u00fdch po\u017eiadaviek potrebova\u0165 ve\u013ea \u010dasu na zavedenie op\u00edsan\u00e9ho mechanizmu.<\/p>\n<p><\/p>\n<p>Okrem toho, ak je v klasteri Kafka zapnut\u00e9 riadenie pr\u00edstupu, bude potrebn\u00e9 str\u00e1vi\u0165 nejak\u00fd \u010das vytv\u00e1ran\u00edm t\u00e9m a zabezpe\u010den\u00edm potrebn\u00fdch pr\u00edstupov k nim. Navy\u0161e je potrebn\u00e9 vybra\u0165 spr\u00e1vny parameter retention.ms pre ka\u017ed\u00fa z retrial-t\u00e9m, aby spr\u00e1vy stihli by\u0165 znovu odoslan\u00e9 a nestratili sa. Implement\u00e1ciu a \u017eiados\u0165 o pr\u00edstupy bude potrebn\u00e9 zopakova\u0165 pre ka\u017ed\u00fa existuj\u00facu alebo nov\u00fa slu\u017ebu.<\/p>\n<p><\/p>\n<p>Po\u010fme sa teraz pozrie\u0165 na mechanizmy op\u00e4tovn\u00e9ho spracovania spr\u00e1v, ktor\u00e9 n\u00e1m pon\u00faka spring ako celok a spring-kafka osobitne. Spring-kafka m\u00e1 prenosn\u00fa z\u00e1vislos\u0165 na spring-retry, ktor\u00fd poskytuje abstrakcie na spr\u00e1vu r\u00f4znych BackOffPolicy. Je to celkom flexibiln\u00fd n\u00e1stroj, ale jeho v\u00fdznamn\u00fdm nedostatkom je uchov\u00e1vanie spr\u00e1v na op\u00e4tovn\u00e9 odoslanie v pam\u00e4ti aplik\u00e1cie. To znamen\u00e1, \u017ee re\u0161tart aplik\u00e1cie kv\u00f4li aktualiz\u00e1cii alebo chybe po\u010das prev\u00e1dzky povedie k strate v\u0161etk\u00fdch spr\u00e1v \u010dakaj\u00facich na op\u00e4tovn\u00e9 spracovanie. Ke\u010f\u017ee tento bod je kritick\u00fd pre n\u00e1\u0161 syst\u00e9m, \u010falej sme sa n\u00edm nezaoberali.<\/p>\n<p><\/p>\n<p>Sama spring-kafka poskytuje nieko\u013eko realiz\u00e1ci\u00ed ContainerAwareErrorHandler, ako napr\u00edklad <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>, pomocou ktor\u00e9ho je mo\u017en\u00e9 spracova\u0165 spr\u00e1vu nesk\u00f4r bez zmeny offsetu v pr\u00edpade chyby. Od verzie spring-kafka 2.3 existuje mo\u017enos\u0165 nastavi\u0165 BackOffPolicy.<\/p>\n<p><\/p>\n<p>Tento pr\u00edstup umo\u017e\u0148uje op\u00e4tovne spracov\u00e1van\u00fdm spr\u00e1vam pre\u017ei\u0165 re\u0161tart aplik\u00e1cie, ale mechanizmus DLQ st\u00e1le ch\u00fdba. Tento variant sme si vybrali za\u010diatkom roka 2019, optimisticky sme predpokladali, \u017ee DLQ nebude potrebn\u00fd (mali sme \u0161\u0165astie a naozaj nebol potrebn\u00fd po\u010das nieko\u013ek\u00fdch mesiacov prev\u00e1dzky aplik\u00e1cie s t\u00fdmto syst\u00e9mom op\u00e4tovn\u00e9ho spracovania). Do\u010dasn\u00e9 chyby viedli k aktiv\u00e1cii SeekToCurrentErrorHandler. Ostatn\u00e9 chyby sa zapisovali do logu, viedli k posunu offsetu a spracovanie pokra\u010dovalo s nasleduj\u00facou spr\u00e1vou.<\/p>\n<p><\/p>\n<h2 id=\"itogovoe-reshenie\">Kone\u010dn\u00e9 rie\u0161enie<\/h2>\n<p><\/p>\n<p>Implement\u00e1cia zalo\u017een\u00e1 na SeekToCurrentErrorHandler n\u00e1s podnietila k v\u00fdvoju vlastn\u00e9ho mechanizmu na opakovan\u00e9 odosielanie spr\u00e1v.<\/p>\n<p><\/p>\n<p>Predov\u0161etk\u00fdm sme chceli vyu\u017ei\u0165 u\u017e existuj\u00face sk\u00fasenosti a roz\u0161\u00edri\u0165 ich v z\u00e1vislosti od logiky aplik\u00e1cie. Pre aplik\u00e1ciu s line\u00e1rnou logikou by bolo optim\u00e1lne na kr\u00e1tky \u010das zastavi\u0165 \u010d\u00edtanie nov\u00fdch spr\u00e1v, ktor\u00fd je ur\u010den\u00fd v r\u00e1mci strat\u00e9gie opakovan\u00fdch volan\u00ed. Pre ostatn\u00e9 aplik\u00e1cie sme chceli ma\u0165 jedin\u00fd bod, ktor\u00fd by zabezpe\u010doval vykon\u00e1vanie strat\u00e9gie opakovan\u00fdch volan\u00ed. Okrem toho by tento jedin\u00fd bod mal ma\u0165 funk\u010dnos\u0165 DLQ pre oba pr\u00edstupy.<\/p>\n<p><\/p>\n<p>Samotn\u00e1 strat\u00e9gia opakovan\u00fdch volan\u00ed by mala by\u0165 ulo\u017een\u00e1 v aplik\u00e1cii, ktor\u00e1 je zodpovedn\u00e1 za z\u00edska\u0165 \u010fal\u0161\u00ed interval pri v\u00fdskyte do\u010dasnej chyby.<\/p>\n<p><\/p>\n<h3 id=\"ostanovka-consumera-dlya-prilozheniya-s-lineynoy-logikoy\">Zastavenie Consumer'a pre aplik\u00e1ciu s line\u00e1rnou logikou<\/h3>\n<p><\/p>\n<p>Pri pr\u00e1ci so spring-kafka m\u00f4\u017ee k\u00f3d na zastavenie Consumer'a vyzera\u0165 pribli\u017ene takto:<\/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>V pr\u00edklade je retryAt \u010das, kedy je potrebn\u00e9 znova spusti\u0165 MessageListenerContainer, ak e\u0161te funguje. Op\u00e4tovn\u00e9 spustenie sa uskuto\u010dn\u00ed v samostatnom vl\u00e1kne, ktor\u00e9 je spusten\u00e9 v TaskScheduler, ktor\u00e9ho implement\u00e1ciu tie\u017e poskytuje spring. <\/p>\n<p><\/p>\n<p>Hodnotu retryAt nach\u00e1dzame nasleduj\u00facim sp\u00f4sobom:<\/p>\n<p><\/p>\n<ol>\n<li>H\u013ead\u00e1 sa hodnota po\u010d\u00edtadla opakovan\u00fdch volan\u00ed.<\/li>\n<li>Pod\u013ea hodnoty po\u010d\u00edtadla sa h\u013ead\u00e1 aktu\u00e1lny interval oneskorenia v strat\u00e9gii opakovan\u00fdch volan\u00ed. Strat\u00e9gia je deklarovan\u00e1 priamo v aplik\u00e1cii, na jej uchovanie sme zvolili form\u00e1t JSON.<\/li>\n<li>N\u00e1jden\u00fd v JSON-rozsahu interval obsahuje po\u010det sek\u00fand, po ktor\u00fdch je potrebn\u00e9 opakova\u0165 spracovanie. Tento po\u010det sek\u00fand sa prid\u00e1 k aktu\u00e1lnemu \u010dasu, \u010d\u00edm vznikne hodnota pre retryAt.<\/li>\n<li>Ak interval nie je n\u00e1jden\u00fd, hodnota retryAt je null a spr\u00e1va bude odoslan\u00e1 do DLQ na manu\u00e1lne spracovanie.<\/li>\n<\/ol>\n<p><\/p>\n<p>Pri tomto pr\u00edstupe zost\u00e1va iba zachova\u0165 po\u010det opakovan\u00fdch volan\u00ed pre ka\u017ed\u00fa spr\u00e1vu, ktor\u00e1 je moment\u00e1lne spracov\u00e1van\u00e1, napr\u00edklad v pam\u00e4ti aplik\u00e1cie. Uchov\u00e1vanie po\u010d\u00edtadla pokusov v pam\u00e4ti nie je pre tento pr\u00edstup kritick\u00e9, preto\u017ee aplik\u00e1cia s line\u00e1rnou logikou nem\u00f4\u017ee vykon\u00e1va\u0165 spracovanie celkovo. Na rozdiel od spring-retry re\u0161tartovanie aplik\u00e1cie nevedie k strate v\u0161etk\u00fdch spr\u00e1v na op\u00e4tovn\u00e9 spracovanie, ale jednoducho k re\u0161tartovaniu strat\u00e9gie. <\/p>\n<p><\/p>\n<p>Tento pr\u00edstup pom\u00e1ha zn\u00ed\u017ei\u0165 z\u00e1\u0165a\u017e na extern\u00fd syst\u00e9m, ktor\u00fd m\u00f4\u017ee by\u0165 nedostupn\u00fd z d\u00f4vodu ve\u013emi ve\u013ekej z\u00e1\u0165a\u017ee. In\u00fdmi slovami, okrem op\u00e4tovn\u00e9ho spracovania sme dosiahli realiz\u00e1ciu vzoru <noindex><a rel=\"nofollow\" href=\"https:\/\/microservices.io\/patterns\/reliability\/circuit-breaker.html\">circuit breaker<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p>V na\u0161om pr\u00edpade prah chyby predstavuje iba hodnotu 1, a aby sme minimalizovali prestoj syst\u00e9mu z d\u00f4vodu do\u010dasnej sie\u0165ovej poruchy, pou\u017e\u00edvame ve\u013emi granularn\u00fa strat\u00e9giu opakovan\u00fdch volan\u00ed s mal\u00fdmi \u010dasov\u00fdmi intervalmi oneskorenia. To nemus\u00ed vyhovova\u0165 v\u0161etk\u00fdm aplik\u00e1ci\u00e1m skupiny, preto je potrebn\u00e9 prah chyby a ve\u013ekos\u0165 intervalu nastavi\u0165 na z\u00e1klade vlastnost\u00ed syst\u00e9mu.<\/p>\n<p><\/p>\n<h3 id=\"otdelnoe-prilozhenie-dlya-obrabotki-soobscheniy-ot-prilozheniy-s-nedeterminirovannoy-logikoy\">Samostatn\u00e1 aplik\u00e1cia na spracovanie spr\u00e1v z aplik\u00e1ci\u00ed s nedeterministickou logikou<\/h3>\n<p><\/p>\n<p>Tu je pr\u00edklad k\u00f3du, ktor\u00fd posiela spr\u00e1vu do takejto aplik\u00e1cie (Retryer), ktor\u00e1 vykon\u00e1 opakovan\u00e9 odoslanie do topiku DESTINATION pri dosiahnut\u00ed \u010dasu RETRY_AT:<\/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>Z pr\u00edkladu je vidie\u0165, \u017ee sa pren\u00e1\u0161a ve\u013ea inform\u00e1ci\u00ed v hlavi\u010dk\u00e1ch. Hodnota RETRY_AT sa nach\u00e1dza rovnako ako pre mechanizmus opakovan\u00e9ho pokusu prostredn\u00edctvom zastavenia Consumer\u2019a. Okrem DESTINATION a RETRY_AT pren\u00e1\u0161ame:<\/p>\n<p><\/p>\n<ul>\n<li>GROUP_ID, pod\u013ea ktor\u00e9ho skupinujeme spr\u00e1vy na ru\u010dn\u00fa anal\u00fdzu a zjednodu\u0161enie vyh\u013ead\u00e1vania.<\/li>\n<li>ORIGINAL_PARTITION, aby sme sa pok\u00fasili zachova\u0165 rovnak\u00e9ho Consumer pre op\u00e4tovn\u00e9 spracovanie. Tento parameter m\u00f4\u017ee by\u0165 rovn\u00fd null, v takom pr\u00edpade bude nov\u00e1 partition z\u00edskan\u00e1 pod\u013ea k\u013e\u00fa\u010da record.key() p\u00f4vodnej spr\u00e1vy.<\/li>\n<li>Aktualizovan\u00e1 hodnota COUNTER, aby sme dodr\u017eiavali strat\u00e9giu opakovan\u00fdch volan\u00ed.<\/li>\n<li>SEND_TO \u2014 kon\u0161tanta ukazuj\u00faca, \u010di posla\u0165 spr\u00e1vu na op\u00e4tovn\u00e9 spracovanie po dosiahnut\u00ed RETRY_AT, alebo ju umiestni\u0165 do DLQ.<\/li>\n<li>REASON \u2014 d\u00f4vod, pre\u010do bolo spracovanie spr\u00e1vy preru\u0161en\u00e9.<\/li>\n<\/ul>\n<p><\/p>\n<p>Retryer uchov\u00e1va spr\u00e1vy na op\u00e4tovn\u00e9 odoslanie a ru\u010dn\u00e9 spracovanie v PostgreSQL. Pod\u013ea \u010dasova\u010da sa sp\u00fa\u0161\u0165a \u00faloha, ktor\u00e1 vyh\u013ead\u00e1va spr\u00e1vy s nastaven\u00fdm RETRY_AT a posiela ich sp\u00e4\u0165 do partition ORIGINAL_PARTITION topiku DESTINATION s k\u013e\u00fa\u010dom record.key().<\/p>\n<p><\/p>\n<p>Po odoslan\u00ed s\u00fa spr\u00e1vy odstr\u00e1nen\u00e9 z PostgreSQL. Ru\u010dn\u00e9 spracovanie spr\u00e1v prebieha v jednoduchom UI, ktor\u00e9 interaguje s Retryer prostredn\u00edctvom REST API. Jeho hlavn\u00fdmi funkciami s\u00fa op\u00e4tovn\u00e9 odosielanie alebo odstra\u0148ovanie spr\u00e1v z DLQ, prezeranie inform\u00e1ci\u00ed o chyb\u00e1ch a vyh\u013ead\u00e1vanie spr\u00e1v, napr\u00edklad pod\u013ea n\u00e1zvu chyby. <\/p>\n<p><\/p>\n<p>Ke\u010f\u017ee v na\u0161ich klastroch je zapnut\u00e9 riadenie pr\u00edstupu, je potrebn\u00e9 navy\u0161e \u017eiada\u0165 pr\u00edstupy k topiku, ktor\u00fd sleduje Retryer, a umo\u017eni\u0165 Retryer\u2019ovi zapisova\u0165 do topiku DESTINATION. Je to nepr\u00edjemn\u00e9, ale na rozdiel od pr\u00edstupu s topikom na interval m\u00e1me plnohodnotn\u00fa DLQ a UI na jej spr\u00e1vu.<\/p>\n<p><\/p>\n<p>Existuj\u00fa pr\u00edpady, ke\u010f vstupn\u00fd topik \u010d\u00edta nieko\u013eko r\u00f4znych skup\u00edn consumer, aplik\u00e1cie ktor\u00fdch implementuj\u00fa r\u00f4znu logiku. Opakovan\u00e9 spracovanie spr\u00e1vy cez Retryer pre jednu z t\u00fdchto aplik\u00e1ci\u00ed povedie k duplicite v inej. Aby sme sa tomu vyhli, zaklad\u00e1me samostatn\u00fd topik na opakovan\u00e9 spracovanie. Vstupn\u00fd a retry-topik m\u00f4\u017ee \u010d\u00edta\u0165 ten ist\u00fd Consumer bez ak\u00fdchko\u013evek obmedzen\u00ed. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Op\u00e4tovn\u00e9 spracovanie udalost\u00ed prijat\u00fdch z Kafka\" src=\"\/wp-content\/uploads\/2020\/02\/06f2355839c6dd812ee3304bf2c72226.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Pod\u013ea predvolen\u00e9ho nastavenia tento pr\u00edstup neposkytuje mo\u017enos\u0165 circuit breaker\u2019a, ale d\u00e1 sa prida\u0165 do aplik\u00e1cie pomocou <noindex><a rel=\"nofollow\" href=\"https:\/\/spring.io\/projects\/spring-cloud-netflix\">spring-cloud-netflix<\/a><\/noindex> alebo nov\u00e9ho <noindex><a rel=\"nofollow\" href=\"https:\/\/spring.io\/projects\/spring-cloud-circuitbreaker\">spring cloud circuit breaker<\/a><\/noindex>, obalen\u00edm miest volan\u00ed vonkaj\u0161\u00edch slu\u017eieb do pr\u00edslu\u0161n\u00fdch abstrakci\u00ed. Okrem toho sa objavuje mo\u017enos\u0165 vo\u013eby strat\u00e9gie pre <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/architecture\/patterns\/bulkhead\">bulkhead<\/a><\/noindex> pattern, \u010do m\u00f4\u017ee by\u0165 tie\u017e u\u017eito\u010dn\u00e9. Napr\u00edklad v spring-cloud-netflix to m\u00f4\u017ee by\u0165 thread pool alebo semafor.<\/p>\n<p><\/p>\n<h2 id=\"vyvod\">Z\u00e1ver<\/h2>\n<p><\/p>\n<p>V d\u00f4sledku toho m\u00e1me samostatn\u00fa aplik\u00e1ciu, ktor\u00e1 umo\u017e\u0148uje zopakova\u0165 spracovanie spr\u00e1vy pri do\u010dasnej nedostupnosti akejko\u013evek vonkaj\u0161ej syst\u00e9mu.<\/p>\n<p><\/p>\n<p>Jednou z hlavn\u00fdch v\u00fdhod aplik\u00e1cie je, \u017ee ju m\u00f4\u017eu pou\u017e\u00edva\u0165 vonkaj\u0161ie syst\u00e9my, ktor\u00e9 funguj\u00fa na tom istom Kafka klastere, bez v\u00fdznamn\u00fdch \u00faprav na svojej strane! Takejto aplik\u00e1cii sta\u010d\u00ed z\u00edska\u0165 pr\u00edstup k retry-topic, vyplni\u0165 nieko\u013eko Kafka hlavi\u010diek a posla\u0165 spr\u00e1vu do Retryer. Nie je potrebn\u00e9 budova\u0165 \u017eiadnu \u010fal\u0161iu infra\u0161trukt\u00faru. A aby sme zn\u00ed\u017eili po\u010det pres\u00favan\u00fdch spr\u00e1v z aplik\u00e1cie do Retryer a sp\u00e4\u0165, vy\u010dlenili sme aplik\u00e1cie s line\u00e1rnou logikou a implementovali v nich op\u00e4tovn\u00e9 spracovanie cez zastavenie Consumer.<\/p>\n<p>Zdroj: <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.4 - 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\/sk\/blog\/administrirovanie\/povtornaya-obrabotka-sobytij-poluchennyh-iz-kafka\" \/>\n\t\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.4\" \/>\n\t\t<meta property=\"og:locale\" content=\"sk_SK\" \/>\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\/sk\/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\udd47Op\u00e4tovn\u00e9 spracovanie udalost\u00ed prijat\u00fdch z Kafka | ProHoster","description":"Ahoj, Habr. Ned\u00e1vno.","canonical_url":"https:\/\/prohoster.info\/sk\/blog\/administrirovanie\/povtornaya-obrabotka-sobytij-poluchennyh-iz-kafka","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"sk_SK","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\/sk\/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\/sk\/wp-json\/wp\/v2\/posts\/56186","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/sk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/sk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/sk\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/sk\/wp-json\/wp\/v2\/comments?post=56186"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/sk\/wp-json\/wp\/v2\/posts\/56186\/revisions"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/sk\/wp-json\/wp\/v2\/media?parent=56186"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/sk\/wp-json\/wp\/v2\/categories?post=56186"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/sk\/wp-json\/wp\/v2\/tags?post=56186"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}