{"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\/pl\/blog\/administrirovanie\/povtornaya-obrabotka-sobytij-poluchennyh-iz-kafka","title":{"rendered":"Powt\u00f3rne przetwarzanie zdarze\u0144 otrzymanych z Kafka","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p><img decoding=\"async\" alt=\"Powt\u00f3rne przetwarzanie zdarze\u0144 otrzymanych z Kafka\" src=\"\/wp-content\/uploads\/2020\/02\/d04319691af5c5de40ccfe4a2268558c.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Cze\u015b\u0107, Habr.<\/p>\n<p><\/p>\n<p>Niedawno podzieli\u0142em si\u0119 do\u015bwiadczeniem <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tinkoff\/blog\/481784\/\">na temat parametr\u00f3w, kt\u00f3re nasz zesp\u00f3\u0142 najcz\u0119\u015bciej wykorzystuje dla Kafka Producer i Consumer, aby osi\u0105gn\u0105\u0107 gwarantowan\u0105 dostaw\u0119. W tym artykule chcia\u0142bym opowiedzie\u0107, jak zorganizowali\u015bmy ponowne przetwarzanie zdarzenia otrzymanego z Kafka w wyniku tymczasowej niedost\u0119pno\u015bci zewn\u0119trznego systemu.<\/a><\/noindex> Nowoczesne aplikacje dzia\u0142aj\u0105 w bardzo z\u0142o\u017conym \u015brodowisku. Logika biznesowa, otoczona nowoczesnym stosem technologicznym, dzia\u0142aj\u0105ca w obrazie Docker, zarz\u0105dzanym przez orkiestratora takiego jak Kubernetes lub OpenShift, i komunikuj\u0105ca si\u0119 z innymi aplikacjami lub rozwi\u0105zaniami enterprise poprzez \u0142a\u0144cuch fizycznych i wirtualnych router\u00f3w. W takim otoczeniu zawsze co\u015b mo\u017ce si\u0119 zepsu\u0107, dlatego ponowne przetwarzanie zdarze\u0144 w przypadku niedost\u0119pno\u015bci jednego z zewn\u0119trznych system\u00f3w jest wa\u017cn\u0105 cz\u0119\u015bci\u0105 naszych proces\u00f3w biznesowych.<\/p>\n<p><\/p>\n<p>Jak to wygl\u0105da\u0142o przed Kafka<\/p>\n<p><noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2 id=\"kak-bylo-do-kafka\">Wcze\u015bniej w projekcie korzystali\u015bmy z IBM MQ do asynchronicznej dostawy wiadomo\u015bci. W przypadku wyst\u0105pienia jakiegokolwiek b\u0142\u0119du w trakcie pracy serwisu otrzymana wiadomo\u015b\u0107 mog\u0142a zosta\u0107 umieszczona w kolejce dead-letter (DLQ) do dalszej r\u0119cznej analizy. DLQ by\u0142 tworzony obok kolejki przychodz\u0105cej, a przeniesienie wiadomo\u015bci nast\u0119powa\u0142o wewn\u0105trz IBM MQ.<\/h2>\n<p><\/p>\n<p>Je\u017celi b\u0142\u0105d mia\u0142 charakter tymczasowy i mogli\u015bmy to okre\u015bli\u0107 (na przyk\u0142ad, ResourceAccessException podczas wywo\u0142ania HTTP lub MongoTimeoutException podczas zapytania do MongoDb), w\u00f3wczas wchodzi\u0142a w \u017cycie strategia powt\u00f3rze\u0144. Niezale\u017cnie od rozga\u0142\u0119zienia logiki aplikacji, pierwotna wiadomo\u015b\u0107 by\u0142a przenoszona albo do kolejki systemowej do op\u00f3\u017anionej wysy\u0142ki, albo do oddzielnej aplikacji, kt\u00f3ra kiedy\u015b zosta\u0142a stworzona do ponownej wysy\u0142ki wiadomo\u015bci. W tym przypadku w nag\u0142\u00f3wku wiadomo\u015bci zapisywano numer powt\u00f3rzenia, kt\u00f3ry by\u0142 powi\u0105zany z interwa\u0142em op\u00f3\u017anienia lub ko\u0144cem strategii na poziomie aplikacji. Je\u015bli osi\u0105gn\u0119li\u015bmy koniec strategii, a zewn\u0119trzny system wci\u0105\u017c by\u0142 niedost\u0119pny, wiadomo\u015b\u0107 zostanie umieszczona w DLQ do r\u0119cznej analizy. <\/p>\n<p><\/p>\n<p>Szukaj\u0105c rozwi\u0105zania<\/p>\n<p><\/p>\n<h2 id=\"poisk-resheniya\">Szukaj\u0105c w internecie<\/h2>\n<p><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/www.google.com\/search?q=kafka+retry+message\">, mo\u017cna znale\u017a\u0107 nast\u0119puj\u0105ce<\/a><\/noindex>. W skr\u00f3cie, proponuje si\u0119 za\u0142o\u017cenie po jednym temacie dla ka\u017cdego interwa\u0142u op\u00f3\u017anienia i wdro\u017cenie po stronie aplikacji Consumer\u00f3w, kt\u00f3rzy b\u0119d\u0105 odczytywa\u0107 wiadomo\u015bci z odpowiednim op\u00f3\u017anieniem. <noindex><a rel=\"nofollow\" href=\"https:\/\/blog.pragmatists.com\/retrying-consumer-architecture-in-the-apache-kafka-939ac4cb851a\">decyzj\u0119<\/a><\/noindex>. M\u00f3wi\u0105c kr\u00f3tko, proponuje si\u0119 utworzenie tematu dla ka\u017cdego interwa\u0142u op\u00f3\u017anienia i wdro\u017cenie po stronie aplikacji konsument\u00f3w, kt\u00f3rzy b\u0119d\u0105 odczytywa\u0107 wiadomo\u015bci z odpowiednim op\u00f3\u017anieniem. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Powt\u00f3rne przetwarzanie zdarze\u0144 otrzymanych z Kafka\" src=\"\/wp-content\/uploads\/2020\/02\/718c4c3f71ccf95b5a6f60e1b2c685d2.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Mimo du\u017cej liczby pozytywnych opinii, wydaje si\u0119 mi ono nie do ko\u0144ca udane. Przede wszystkim dlatego, \u017ce deweloper, opr\u00f3cz realizacji wymaga\u0144 biznesowych, b\u0119dzie musia\u0142 po\u015bwi\u0119ci\u0107 du\u017co czasu na wdro\u017cenie opisanego mechanizmu.<\/p>\n<p><\/p>\n<p>Ponadto, je\u015bli w klastrze Kafka w\u0142\u0105czone jest zarz\u0105dzanie dost\u0119pem, trzeba b\u0119dzie po\u015bwi\u0119ci\u0107 troch\u0119 czasu na tworzenie temat\u00f3w i zapewnienie odpowiednich dost\u0119p\u00f3w do nich. Dodatkowo, trzeba b\u0119dzie dobra\u0107 odpowiedni parametr retention.ms dla ka\u017cdego z temat\u00f3w retry, aby wiadomo\u015bci mog\u0142y by\u0107 ponownie wysy\u0142ane i nie znika\u0142y. Wdra\u017canie i \u017c\u0105danie dost\u0119p\u00f3w b\u0119dzie trzeba powt\u00f3rzy\u0107 dla ka\u017cdej istniej\u0105cej lub nowej us\u0142ugi.<\/p>\n<p><\/p>\n<p>Przyjrzyjmy si\u0119 teraz, jakie mechanizmy ponownego przetwarzania wiadomo\u015bci oferuje nam Spring jako ca\u0142o\u015b\u0107 i Spring-Kafka w szczeg\u00f3lno\u015bci. Spring-Kafka ma transytywn\u0105 zale\u017cno\u015b\u0107 od Spring-Retry, kt\u00f3ry zapewnia abstrakcje do zarz\u0105dzania r\u00f3\u017cnymi BackOffPolicy. To do\u015b\u0107 elastyczne narz\u0119dzie, ale jego powa\u017cnym minusem jest przechowywanie wiadomo\u015bci do ponownego wys\u0142ania w pami\u0119ci aplikacji. Oznacza to, \u017ce ponowne uruchomienie aplikacji z powodu aktualizacji lub b\u0142\u0119du w trakcie eksploatacji spowoduje utrat\u0119 wszystkich wiadomo\u015bci oczekuj\u0105cych na ponowne przetworzenie. Poniewa\u017c ten punkt jest krytyczny dla naszego systemu, nie rozwa\u017cali\u015bmy go dalej.<\/p>\n<p><\/p>\n<p>Sama Spring-Kafka oferuje kilka implementacji ContainerAwareErrorHandler, na przyk\u0142ad <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>, kt\u00f3ry pozwala na przetworzenie wiadomo\u015bci p\u00f3\u017aniej bez przesuwania offsetu w przypadku wyst\u0105pienia b\u0142\u0119du. Od wersji Spring-Kafka 2.3 pojawi\u0142a si\u0119 mo\u017cliwo\u015b\u0107 ustalania BackOffPolicy.<\/p>\n<p><\/p>\n<p>To podej\u015bcie pozwala wiadomo\u015bciom do ponownego przetwarzania na przetrwanie restartu aplikacji, ale mechanizm DLQ wci\u0105\u017c jest niedost\u0119pny. W\u0142a\u015bnie ten wariant wybrali\u015bmy na pocz\u0105tku 2019 roku, optymistycznie zak\u0142adaj\u0105c, \u017ce DLQ nie b\u0119dzie potrzebny (mieli\u015bmy szcz\u0119\u015bcie i rzeczywi\u015bcie nie by\u0142 potrzebny przez kilka miesi\u0119cy eksploatacji aplikacji z takim systemem ponownego przetwarzania). B\u0142\u0119dy tymczasowe prowadzi\u0142y do uruchomienia SeekToCurrentErrorHandler. Pozosta\u0142e b\u0142\u0119dy by\u0142y rejestrowane w logach, prowadzi\u0142y do przesuni\u0119cia offsetu, a przetwarzanie by\u0142o kontynuowane z nast\u0119pn\u0105 wiadomo\u015bci\u0105.<\/p>\n<p><\/p>\n<h2 id=\"itogovoe-reshenie\">Ostateczne rozwi\u0105zanie<\/h2>\n<p><\/p>\n<p>Realizacja oparta na SeekToCurrentErrorHandler sk\u0142oni\u0142a nas do opracowania w\u0142asnego mechanizmu do ponownego wysy\u0142ania wiadomo\u015bci.<\/p>\n<p><\/p>\n<p>Przede wszystkim chcieli\u015bmy wykorzysta\u0107 dotychczasowe do\u015bwiadczenia i rozszerzy\u0107 je w zale\u017cno\u015bci od logiki aplikacji. Dla aplikacji o liniowej logice optymalne by\u0142oby wstrzymanie odczytu nowych wiadomo\u015bci na kr\u00f3tki czas okre\u015blony w strategii ponownych wywo\u0142a\u0144. Dla pozosta\u0142ych aplikacji chcieliby\u015bmy mie\u0107 jedn\u0105 wsp\u00f3ln\u0105 punkt, kt\u00f3ry zapewni realizacj\u0119 strategii ponownych wywo\u0142a\u0144. Dodatkowo ten wsp\u00f3lny punkt powinien mie\u0107 funkcjonalno\u015b\u0107 DLQ dla obu podej\u015b\u0107.<\/p>\n<p><\/p>\n<p>Strategia ponownych wywo\u0142a\u0144 powinna by\u0107 przechowywana w aplikacji, kt\u00f3ra odpowiada za uzyskanie nast\u0119pnego interwa\u0142u w przypadku wyst\u0105pienia b\u0142\u0119du czasowego.<\/p>\n<p><\/p>\n<h3 id=\"ostanovka-consumera-dlya-prilozheniya-s-lineynoy-logikoy\">Zatrzymanie Consumer\u2019a dla aplikacji o liniowej logice<\/h3>\n<p><\/p>\n<p>Przy pracy z spring-kafka kod do zatrzymania Consumer\u2019a mo\u017ce wygl\u0105da\u0107 mniej wi\u0119cej tak:<\/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        \/\/ do DLQ\n    }<\/code><\/pre>\n<p><\/p>\n<p>W przyk\u0142adzie retryAt to czas, w kt\u00f3rym nale\u017cy ponownie uruchomi\u0107 MessageListenerContainer, je\u015bli nadal dzia\u0142a. Ponowne uruchomienie nast\u0105pi w osobnym w\u0105tku uruchomionym w TaskScheduler, kt\u00f3rego realizacj\u0119 r\u00f3wnie\u017c zapewnia spring. <\/p>\n<p><\/p>\n<p>Warto\u015b\u0107 retryAt znajduje si\u0119 w nast\u0119puj\u0105cy spos\u00f3b:<\/p>\n<p><\/p>\n<ol>\n<li>Szukane jest warto\u015b\u0107 licznika ponownych wywo\u0142a\u0144.<\/li>\n<li>Zgodnie z warto\u015bci\u0105 licznika szukany jest bie\u017c\u0105cy interwa\u0142 op\u00f3\u017anienia w strategii ponownych wywo\u0142a\u0144. Strategia jest zadeklarowana w samej aplikacji, do jej przechowywania wybrali\u015bmy format JSON.<\/li>\n<li>Znaleziony w tablicy JSON interwa\u0142 zawiera liczb\u0119 sekund, po kt\u00f3rej nale\u017cy powt\u00f3rzy\u0107 przetwarzanie. Ta liczba sekund jest dodawana do bie\u017c\u0105cego czasu, tworz\u0105c warto\u015b\u0107 dla retryAt.<\/li>\n<li>Je\u015bli interwa\u0142 nie zostanie znaleziony, to warto\u015b\u0107 retryAt wynosi null, a wiadomo\u015b\u0107 zostanie wys\u0142ana do DLQ do r\u0119cznej analizy.<\/li>\n<\/ol>\n<p><\/p>\n<p>W takim podej\u015bciu pozostaje tylko zachowa\u0107 liczb\u0119 ponownych wywo\u0142a\u0144 dla ka\u017cdej wiadomo\u015bci, kt\u00f3ra jest obecnie przetwarzana, na przyk\u0142ad w pami\u0119ci aplikacji. Zachowanie licznika pr\u00f3b w pami\u0119ci nie jest krytyczne dla tego podej\u015bcia, poniewa\u017c aplikacja z liniow\u0105 logik\u0105 nie mo\u017ce prowadzi\u0107 przetwarzania w ca\u0142o\u015bci. W przeciwie\u0144stwie do spring-retry, ponowne uruchomienie aplikacji nie prowadzi do utraty wszystkich wiadomo\u015bci do ponownego przetworzenia, a jedynie do ponownego uruchomienia strategii. <\/p>\n<p><\/p>\n<p>To podej\u015bcie pomaga zredukowa\u0107 obci\u0105\u017cenie zewn\u0119trznego systemu, kt\u00f3ry mo\u017ce by\u0107 niedost\u0119pny z powodu bardzo wysokiego obci\u0105\u017cenia. Innymi s\u0142owy, opr\u00f3cz ponownego przetwarzania, uda\u0142o nam si\u0119 wdro\u017cy\u0107 wzorzec. <noindex><a rel=\"nofollow\" href=\"https:\/\/microservices.io\/patterns\/reliability\/circuit-breaker.html\">circuit breaker<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p>W naszym przypadku pr\u00f3g b\u0142\u0119du wynosi tylko 1, a aby zminimalizowa\u0107 przestoje systemu spowodowane tymczasow\u0105 awari\u0105 sieci, stosujemy bardzo granularn\u0105 strategi\u0119 ponownych wywo\u0142a\u0144 z ma\u0142ymi interwa\u0142ami op\u00f3\u017anienia. Mo\u017ce to nie pasowa\u0107 do wszystkich aplikacji w grupie firm, dlatego proporcje mi\u0119dzy progiem b\u0142\u0119du a wielko\u015bci\u0105 interwa\u0142u nale\u017cy dobiera\u0107 w zale\u017cno\u015bci od cech systemu.<\/p>\n<p><\/p>\n<h3 id=\"otdelnoe-prilozhenie-dlya-obrabotki-soobscheniy-ot-prilozheniy-s-nedeterminirovannoy-logikoy\">Osobna aplikacja do przetwarzania wiadomo\u015bci z aplikacji o niedeterministycznej logice.<\/h3>\n<p><\/p>\n<p>Oto przyk\u0142ad kodu, kt\u00f3ry wysy\u0142a wiadomo\u015b\u0107 do takiej aplikacji (Retryer), kt\u00f3ra wykona ponown\u0105 wysy\u0142k\u0119 do tematu DESTINATION po osi\u0105gni\u0119ciu czasu 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 przyk\u0142adu wida\u0107, \u017ce wiele informacji przekazywanych jest w nag\u0142\u00f3wkach. Warto\u015b\u0107 RETRY_AT znajduje si\u0119 tak samo, jak w mechanizmie powt\u00f3rze\u0144 poprzez zatrzymanie Consumer\u2019a. Opr\u00f3cz DESTINATION i RETRY_AT przekazujemy:<\/p>\n<p><\/p>\n<ul>\n<li>GROUP_ID, wed\u0142ug kt\u00f3rego grupujemy wiadomo\u015bci do r\u0119cznej analizy i uproszczenia wyszukiwania.<\/li>\n<li>ORIGINAL_PARTITION, aby spr\u00f3bowa\u0107 zachowa\u0107 tego samego Consumer do ponownego przetwarzania. Ten parametr mo\u017ce mie\u0107 warto\u015b\u0107 null, w takim przypadku nowa partycja zostanie uzyskana na podstawie klucza record.key() oryginalnej wiadomo\u015bci.<\/li>\n<li>Zaktualizowana warto\u015b\u0107 COUNTER, aby \u015bledzi\u0107 strategi\u0119 ponownych wywo\u0142a\u0144.<\/li>\n<li>SEND_TO \u2014 sta\u0142a wskazuj\u0105ca, czy wys\u0142a\u0107 wiadomo\u015b\u0107 do ponownego przetworzenia po osi\u0105gni\u0119ciu RETRY_AT, czy umie\u015bci\u0107 j\u0105 w DLQ.<\/li>\n<li>REASON \u2014 pow\u00f3d, dla kt\u00f3rego przetwarzanie wiadomo\u015bci zosta\u0142o przerwane.<\/li>\n<\/ul>\n<p><\/p>\n<p>Retryer przechowuje wiadomo\u015bci do ponownego wys\u0142ania i r\u0119cznej analizy w PostgreSQL. Na podstawie timera uruchamiana jest zadanie, kt\u00f3re znajduje wiadomo\u015bci z osi\u0105gni\u0119tym RETRY_AT i wysy\u0142a je z powrotem do partycji ORIGINAL_PARTITION tematu DESTINATION z kluczem record.key().<\/p>\n<p><\/p>\n<p>Po wys\u0142aniu wiadomo\u015bci s\u0105 one usuwane z PostgreSQL. R\u0119czna analiza wiadomo\u015bci odbywa si\u0119 w prostym interfejsie u\u017cytkownika, kt\u00f3ry wsp\u00f3\u0142dzia\u0142a z Retryer za pomoc\u0105 REST API. Jego g\u0142\u00f3wne cechy to ponowne wysy\u0142anie lub usuwanie wiadomo\u015bci z DLQ, przegl\u0105danie informacji o b\u0142\u0119dach oraz wyszukiwanie wiadomo\u015bci, na przyk\u0142ad wed\u0142ug nazwy b\u0142\u0119du. <\/p>\n<p><\/p>\n<p>Poniewa\u017c w naszych klastrach w\u0142\u0105czone jest zarz\u0105dzanie dost\u0119pem, konieczne jest dodatkowe \u017c\u0105danie dost\u0119pu do tematu, kt\u00f3ry nas\u0142uchuje Retryer, oraz umo\u017cliwienie Retryerowi pisania do tematu DESTINATION. Jest to niewygodne, ale w odr\u00f3\u017cnieniu od podej\u015bcia z tematem w interwa\u0142ach, mamy pe\u0142noprawny DLQ i interfejs u\u017cytkownika do zarz\u0105dzania nim.<\/p>\n<p><\/p>\n<p>Zdarzaj\u0105 si\u0119 sytuacje, gdy przychodz\u0105cy temat jest odczytywany przez kilka r\u00f3\u017cnych grup consumer, kt\u00f3rych aplikacje implementuj\u0105 r\u00f3\u017cn\u0105 logik\u0119. Ponowne przetwarzanie wiadomo\u015bci przez Retryer dla jednej z takich aplikacji spowoduje duplikat w innej. Aby si\u0119 przed tym zabezpieczy\u0107, tworzymy osobny temat do ponownego przetwarzania. Temat przychodz\u0105cy i temat retry mog\u0105 by\u0107 odczytywane przez tego samego Consumer bez \u017cadnych ogranicze\u0144. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Powt\u00f3rne przetwarzanie zdarze\u0144 otrzymanych z Kafka\" src=\"\/wp-content\/uploads\/2020\/02\/06f2355839c6dd812ee3304bf2c72226.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Domy\u015blnie podej\u015bcie to nie zapewnia mo\u017cliwo\u015bci circuit breaker\u2019a, jednak mo\u017cna go doda\u0107 do aplikacji za pomoc\u0105 <noindex><a rel=\"nofollow\" href=\"https:\/\/spring.io\/projects\/spring-cloud-netflix\">spring-cloud-netflix<\/a><\/noindex> lub nowego <noindex><a rel=\"nofollow\" href=\"https:\/\/spring.io\/projects\/spring-cloud-circuitbreaker\">spring cloud circuit breaker<\/a><\/noindex>, owijaj\u0105c miejsca wywo\u0142a\u0144 zewn\u0119trznych serwis\u00f3w w odpowiednie abstrakcje. Ponadto pojawia si\u0119 mo\u017cliwo\u015b\u0107 wyboru strategii dla <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/architecture\/patterns\/bulkhead\">bulkhead<\/a><\/noindex> wzoru, co r\u00f3wnie\u017c mo\u017ce by\u0107 przydatne. Na przyk\u0142ad, w spring-cloud-netflix mo\u017ce to by\u0107 pula w\u0105tk\u00f3w lub semafor.<\/p>\n<p><\/p>\n<h2 id=\"vyvod\">Wnioski<\/h2>\n<p><\/p>\n<p>W efekcie uzyskali\u015bmy osobn\u0105 aplikacj\u0119, kt\u00f3ra umo\u017cliwia powt\u00f3rzenie przetwarzania wiadomo\u015bci w przypadku tymczasowej niedost\u0119pno\u015bci jakiego\u015b zewn\u0119trznego systemu.<\/p>\n<p><\/p>\n<p>Jedn\u0105 z g\u0142\u00f3wnych zalet aplikacji jest to, \u017ce mog\u0105 z niej korzysta\u0107 zewn\u0119trzne systemy dzia\u0142aj\u0105ce w tym samym klastrze Kafka, bez wi\u0119kszych modyfikacji po swojej stronie! Takiej aplikacji wystarczy tylko uzyska\u0107 dost\u0119p do tematu retry, wype\u0142ni\u0107 kilka nag\u0142\u00f3wk\u00f3w Kafka i wys\u0142a\u0107 wiadomo\u015b\u0107 do Retryera. Nie trzeba uruchamia\u0107 \u017cadnej dodatkowej infrastruktury. Aby zredukowa\u0107 liczb\u0119 wiadomo\u015bci przenoszonych z aplikacji do Retryera i z powrotem, wyodr\u0119bnili\u015bmy aplikacje z liniow\u0105 logik\u0105 i wprowadzili\u015bmy w nich powt\u00f3rne przetwarzanie poprzez zatrzymanie Konsumenta.<\/p>\n<p>\u0179r\u00f3d\u0142o: <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\/pl\/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=\"pl_PL\" \/>\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\/pl\/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\udd47 Powt\u00f3rne przetwarzanie zdarze\u0144 z Kafka | ProHoster","description":"Cze\u015b\u0107, Habr. Niedawno.","canonical_url":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/povtornaya-obrabotka-sobytij-poluchennyh-iz-kafka","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"pl_PL","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\/pl\/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\/pl\/wp-json\/wp\/v2\/posts\/56186","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/comments?post=56186"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts\/56186\/revisions"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/media?parent=56186"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/categories?post=56186"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/tags?post=56186"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}