Co mogłoby zmusić dużą firmę, taką jak Lamoda, posiadającą dobrze ugruntowany proces i dziesiątki powiązanych ze sobą usług, do znaczącej zmiany podejścia? Motywacja może być zupełnie różna: od ustawodawczej po chęć eksperymentowania, która jest wrodzona wszystkim programistom.
Nie oznacza to jednak, że nie można liczyć na dodatkowe korzyści. Siergiej Zaika opowie Ci, co dokładnie możesz zyskać, implementując interfejs API oparty na zdarzeniach w Kafce (). Na pewno będzie też trochę wstrząsów i ciekawych odkryć – żaden eksperyment nie może się bez nich obyć.

Zastrzeżenie: Niniejszy artykuł powstał w oparciu o materiały ze spotkania, które Sergey zorganizował w listopadzie 2018 r. w HighLoad++. Wystąpienia Lamody na żywo poświęcone pracy z Kafką przyciągnęły nie mniejszą liczbę słuchaczy niż inne prezentacje przewidziane w programie. Uważamy, że jest to świetny przykład pokazujący, że zawsze można i trzeba znaleźć ludzi o podobnych poglądach. Organizatorzy HighLoad++ będą nadal starać się tworzyć atmosferę sprzyjającą temu zjawisku.
O procesie
Lamoda to duża platforma e-commerce z własnym centrum kontaktowym, usługą dostawy (i wieloma partnerami), studiem fotograficznym, ogromnym magazynem, a wszystko to działa w oparciu o własne oprogramowanie. Istnieją dziesiątki metod płatności, a partnerzy B2B mogą korzystać z niektórych lub wszystkich tych usług i chcą znać aktualne informacje o ich produktach. Poza tym Lamoda działa w trzech krajach poza Federacją Rosyjską i wszystko tam wygląda trochę inaczej. Łącznie istnieje zapewne ponad sto sposobów konfiguracji nowego zamówienia, które należy przetworzyć na swój własny sposób. Wszystko to odbywa się za pośrednictwem dziesiątek usług, które komunikują się ze sobą w sposób niekiedy nieoczywisty. Istnieje również system centralny, którego główną odpowiedzialnością jest status zamówień. Nazywamy ją BOB, pracuję z nią.
Narzędzie do zwrotu pieniędzy z interfejsem API sterowanym zdarzeniami
Określenie „napędzany zdarzeniami” jest dość nadużywane. Za chwilę wyjaśnimy dokładniej, co ono oznacza. Zacznę od kontekstu, w którym postanowiliśmy wypróbować podejście oparte na zdarzeniach w Kafce.

W każdym sklepie, oprócz zamówień, za które klienci płacą, zdarzają się sytuacje, gdy sklep jest zobowiązany do zwrotu pieniędzy, ponieważ otrzymany produkt nie spełnił oczekiwań klienta. To stosunkowo krótki proces: jeśli zachodzi taka potrzeba, wyjaśniamy informacje i przelewamy pieniądze.
Jednak zwrot stał się bardziej skomplikowany ze względu na zmiany w przepisach i musieliśmy wdrożyć w tym celu oddzielną mikrousługę.

Nasza motywacja:
- Prawo FZ-54 — krótko mówiąc, prawo wymaga zgłaszania do urzędu skarbowego każdej transakcji pieniężnej, niezależnie od tego, czy jest to zwrot, czy pokwitowanie, w stosunkowo krótkim czasie wynoszącym kilka minut. Jako firma zajmująca się handlem elektronicznym realizujemy całkiem sporo transakcji. Technicznie rzecz biorąc, oznacza to nową odpowiedzialność (a zatem nową usługę) i usprawnienia we wszystkich powiązanych systemach.
- Podział BOB-a — wewnętrzny projekt firmy mający na celu odciążenie BOB z dużej liczby obowiązków niezwiązanych z podstawową działalnością i zmniejszenie jego ogólnej złożoności.

Na tym schemacie przedstawiono główne systemy Lamody. Teraz większość z nich jest bardziej jak konstelacja 5-10 mikrousług skupionych wokół kurczącego się monolitu. Rosną one powoli, ale staramy się, aby były mniejsze, ponieważ wdrażanie fragmentu wybranego w środku jest przerażające — nie możemy dopuścić do jego awarii. Jesteśmy zmuszeni zarezerwować wszystkie wymiany (strzałki) i przewidujemy, że każda z nich może stać się niedostępna.
BOB oferuje również wiele giełd: systemy płatności, dostawy, powiadomienia itp.
Technicznie rzecz biorąc BOB to:
- ~150 tys. linii kodu + ~100 tys. linii testów;
- php7.2 + Zend 1 i komponenty Symfony 3;
- >100 interfejsów API i ~50 integracji wychodzących;
- 4 kraje z własną logiką biznesową.
Wdrożenie BOB-a jest kosztowne i żmudne, a ilość kodu i problemów, które rozwiązuje, jest taka, że nikt nie jest w stanie ogarnąć wszystkiego. Ogólnie rzecz biorąc, istnieje wiele powodów, dla których warto to uprościć.
Proces zwrotu
Początkowo w procesie biorą udział dwa systemy: BOB i Payment. Teraz pojawiły się dwa kolejne:
- Usługa Fiskalizacyjna, która będzie zajmować się sprawami fiskalizacji i komunikacją z usługami zewnętrznymi.
- Narzędzie do zwrotu pieniędzy, które po prostu przyjmuje nowe wymiany, aby nie zawyżać BOB.
Teraz proces wygląda następująco:

- BOB otrzymuje prośbę o zwrot pieniędzy.
- BOB mówi o tym narzędziu do zwrotu pieniędzy.
- Narzędzie Refund Tool wydaje polecenie Payment: „Zwróć pieniądze”.
- Płatność zwraca pieniądze.
- Refund Tool i BOB synchronizują ze sobą statusy, ponieważ obydwa potrzebują tej opcji w danej chwili. Nie jesteśmy jeszcze gotowi na pełne przejście na narzędzie do zwrotów, ponieważ BOB ma interfejs użytkownika, raporty księgowe i ogólnie wiele danych, których nie można tak łatwo przenieść. Musisz usiąść na dwóch krzesłach.
- Prośba o fiskalizację została wysłana.
W efekcie stworzyliśmy na Kafce coś w rodzaju magistrali zdarzeń – magistralę zdarzeń, do której wszystko było przypięte. Hurra, teraz mamy pojedynczy punkt awarii (sarkazm).

Zalety i wady są dość oczywiste. Stworzyliśmy autobus, co oznacza, że teraz wszystkie usługi są od niego uzależnione. Upraszcza to projekt, ale wprowadza do systemu pojedynczy punkt awarii. Kafka ulegnie awarii i proces się zatrzyma.
Czym jest interfejs API oparty na zdarzeniach?
Dobrą odpowiedź na to pytanie można znaleźć w raporcie Martina Fowlera (GOTO 2017) .
Oto w skrócie, co zrobiliśmy:
- Wszystkie asynchroniczne wymiany zostały zamknięte przechowywanie wydarzeń. Zamiast powiadamiać każdego zainteresowanego konsumenta za pośrednictwem sieci o zmianie statusu, zapisujemy zdarzenie zmiany statusu w scentralizowanym magazynie, a zainteresowani tematem konsumenci czytają wszystko, co się tam pojawi.
- Wydarzeniem w tym przypadku jest powiadomienie (Powiadomienia) że coś gdzieś się zmieniło. Na przykład zmienił się status zamówienia. Konsument, który potrzebuje danych towarzyszących zmianie statusu, a które nie zostały zawarte w powiadomieniu, może sam sprawdzić swój status.
- Maksymalną opcją jest pełne pozyskiwanie zdarzeń, transfer państwowy, w którym zdarzenie zawiera wszystkie informacje niezbędne do przetworzenia: skąd pochodzi i jaki ma status, jak dokładnie zmieniły się dane itp. Jedynym pytaniem jest wykonalność i ilość informacji, jaką możesz sobie pozwolić przechowywać.
W ramach uruchomienia narzędzia do zwrotu pieniędzy skorzystaliśmy z trzeciej opcji. Uprościło to przetwarzanie zdarzeń, ponieważ nie było potrzeby wydobywania szczegółowych informacji, a ponadto wyeliminowało sytuację, w której każde nowe zdarzenie generowałoby serię wyjaśniających żądań pobrania od konsumentów.
Usługa narzędzia do zwrotu pieniędzy nie załadowany, więc Kafka jest tam raczej próbą, niż koniecznością. Nie sądzę, że przedsiębiorstwa byłyby zadowolone, gdyby usługa zwrotów stała się projektem o dużym obciążeniu.
Wymiana asynchroniczna TAK JAK JEST
W przypadku komunikacji asynchronicznej dział PHP zazwyczaj używa RabbitMQ. Zebrali dane dotyczące żądania, umieścili je w kolejce, a użytkownik tej samej usługi je zliczył i wysłał (lub nie wysłał). Jeśli chodzi o sam API, Lamoda aktywnie wykorzystuje Swagger. Projektujemy API, opisujemy je w Swaggerze, generujemy kod klienta i serwera. Używamy również nieco rozszerzonego JSON RPC 2.0.
W niektórych miejscach używa się magistrali ESB, w niektórych korzysta się z technologii ActiveMQ, ale ogólnie rzecz biorąc, RabbitMQ — standard.
Wymiana asynchroniczna BYĆ
Można doszukać się pewnej analogii przy projektowaniu wymiany za pośrednictwem magistrali zdarzeń. W podobny sposób opisujemy przyszłą wymianę danych poprzez opisy struktury zdarzeń. Format to yaml, musieliśmy sami wygenerować kod, generator tworzy DTO zgodnie ze specyfikacją i uczy klientów i serwery pracy z nimi. Pokolenie przechodzi na dwa języki - golang i php. Dzięki temu biblioteki pozostają spójne. Generator został napisany w języku Golang, stąd też wzięła się jego nazwa – gogi.
Pozyskiwanie zdarzeń w Kafce jest rzeczą typową. Istnieje rozwiązanie z głównej wersji korporacyjnej Kafka Confluent, jest , rozwiązanie od naszych „braci” z obszaru domen Zalando. Nasz motywacja do rozpoczęcia pracy z waniliowym Kafkiem — polega na pozostawieniu rozwiązania wolnego, dopóki nie podejmiemy ostatecznej decyzji, czy będziemy go wszędzie stosować, a także na pozostawieniu sobie pola manewru i udoskonaleń: chcemy wsparcia dla naszych JSON-RPC 2.0, generatory dla dwóch języków i zobaczmy co jeszcze.
Ironią jest, że nawet w tym szczęśliwym przypadku, gdy istnieje podobna firma, Zalando, która podjęła podobną decyzję, nie możemy jej skutecznie wykorzystać.
Schemat architektoniczny na początku wygląda następująco: odczytujemy dane bezpośrednio z Kafki, ale zapisujemy wyłącznie za pośrednictwem magistrali zdarzeń. W Kafce jest sporo gotowych rzeczy do przeczytania: brokerzy, moduły równoważące; jest też mniej więcej przygotowana do skalowania poziomego, chciałem to zachować. Chcieliśmy zakończyć nagrywanie za pomocą jednej bramki, zwanej też magistralą zdarzeń, i oto dlaczego.
Wydarzenia-autobus
Albo autobus zdarzeń. Jest to po prostu bezstanowa brama http, która pełni kilka ważnych ról:
- Tworzenie walidacji — sprawdzamy, czy zdarzenia spełniają naszą specyfikację.
- System główny wydarzeń, czyli jest to główny i jedyny system w przedsiębiorstwie odpowiadający na pytanie, jakie zdarzenia i z jakimi strukturami uznajemy za ważne. Walidacja obejmuje po prostu typy danych i wyliczenia w celu ścisłej specyfikacji zawartości.
- Funkcja skrótu w przypadku partycjonowania — struktura wiadomości Kafka jest oparta na zasadzie klucz-wartość, a skrót klucza jest używany do obliczenia, gdzie ją umieścić.
Czemu
Pracujemy w dużej firmie, która ma dobrze opracowane procesy. Po co cokolwiek zmieniać? To jest eksperyment.i spodziewamy się szeregu korzyści.
Wymiany 1:n+1 (jeden do wielu)
Dzięki Kafce możesz w bardzo prosty sposób łączyć nowych użytkowników z Twoim API.
Załóżmy, że masz katalog, który musi być aktualizowany w kilku systemach jednocześnie (oraz w kilku nowych). Wcześniej wymyśliliśmy pakiet, który implementował set-API, a systemowi głównemu przekazano adresy konsumentów. Teraz system główny wysyła aktualizacje dotyczące tematu, a każdy zainteresowany może je przeczytać. Pojawił się nowy system - zasubskrybuj temat. Tak, to też pakiet, ale prostszy.
W przypadku narzędzia refund-tool, które jest w zasadzie częścią BOB, wygodnie jest nam utrzymywać ich synchronizację poprzez Kafkę. W serwisie Payments napisano, że pieniądze zostały zwrócone: BOB, RT dowiedziały się o tym i zmieniły statusy, Służba Fiskalizacyjna dowiedziała się o tym i wystawiła czek.

Planujemy stworzenie pojedynczej Usługi Powiadomień, która będzie informować klienta o nowościach dotyczących jego zamówienia/zwrotów. Obecnie odpowiedzialność ta jest rozłożona na wiele systemów. Wystarczy, że nauczymy usługę Notifications Service wychwytywania istotnych informacji z Kafki i reagowania na nie (oraz wyłączymy te powiadomienia w innych systemach). Nie będą wymagane żadne nowe wymiany bezpośrednie.
Oparte na danych
Informacje między systemami stają się przejrzyste – bez względu na to, jak „krwawe przedsiębiorstwo” prowadzisz i jak duże masz zaległości. W firmie Lamoda znajduje się dział analizy danych, który zajmuje się gromadzeniem danych z systemów i konwertowaniem ich do postaci nadającej się do ponownego wykorzystania zarówno w systemach biznesowych, jak i inteligentnych. Kafka pozwala na szybkie dostarczenie dużej ilości danych i zapewnienie aktualności przepływu informacji.
Dziennik replikacji
Wiadomości nie znikają po przeczytaniu, jak to ma miejsce w RabbitMQ. Gdy zdarzenie zawiera wystarczającą ilość informacji do przetworzenia, uzyskujemy historię ostatnich zmian obiektu i, jeśli chcemy, możemy te zmiany zastosować.
Okres przechowywania dziennika replikacji zależy od intensywności zapisu w danym temacie; Kafka umożliwia elastyczną konfigurację limitów czasu przechowywania i ilości danych. W przypadku tematów wymagających dużej uwagi ważne jest, aby wszyscy konsumenci mieli czas na zapoznanie się z informacjami, zanim znikną, nawet w przypadku krótkotrwałej niedostępności. Zwykle możliwe jest przechowywanie danych dla jednostki dni, co w zupełności wystarczy jako wsparcie.

Oto krótka wersja dokumentacji dla tych, którzy nie znają Kafki (ilustracja również pochodzi z dokumentacji)
W protokole AMQP występują kolejki: wiadomości są zapisywane w kolejce dla konsumenta. Zwykle jedną kolejkę przetwarza jeden system, który stosuje tę samą logikę biznesową. Jeśli chcesz powiadomić kilka systemów, możesz nauczyć aplikację zapisywania do kilku kolejek lub skonfigurować wymianę z mechanizmem fanout, który będzie je klonował.
Kafka ma podobną abstrakcję aktualny, w którym piszesz wiadomości, które nie znikają po przeczytaniu. Domyślnie po połączeniu się z Kafką otrzymujesz wszystkie wiadomości i masz możliwość zapisania ich od miejsca, w którym przerwałeś. Oznacza to, że czytasz wiadomość sekwencyjnie, nie możesz oznaczyć jej jako przeczytanej, ale zapisać identyfikator, od którego będziesz mógł kontynuować czytanie. Identyfikator, na którym się zatrzymałeś, nazywa się przesunięciem, a mechanizm to przesunięcie zatwierdzenia.
W związku z tym można wdrożyć inną logikę. Na przykład BOB mamy w 4 przypadkach dla różnych krajów - Lamoda jest w Rosji, Kazachstanie, Ukrainie i Białorusi. Ponieważ są wdrażane oddzielnie, mają nieco inną konfigurację i własną logikę biznesową. W wiadomości wskazujemy, jakiego kraju ona dotyczy. Każdy konsument BOB w każdym kraju odczytuje wiadomość z innym groupId, a jeśli wiadomość go nie dotyczy, jest pomijana, tj. natychmiast zatwierdza offset +1. Jeśli ten sam temat zostanie odczytany przez naszą Usługę Płatności, zostanie to odczytane za pomocą osobnej grupy, dzięki czemu przesunięcia nie będą się przecinać.
Wymagania wydarzenia:
- Kompletność danych. Chciałbym, aby zdarzenie miało wystarczającą ilość danych do przetworzenia.
- Uczciwość. Delegujemy do Events-bus sprawdzenie, czy zdarzenie jest spójne i czy jest w stanie je obsłużyć.
- Kolejność ma znaczenie. W przypadku powrotu jesteśmy zmuszeni posługiwać się historią. W przypadku powiadomień kolejność nie ma znaczenia — jeśli są to jednorodne powiadomienia, treść e-maila będzie taka sama, niezależnie od tego, które powiadomienie dotarło pierwsze. W przypadku zwrotu obowiązuje jasna procedura, jeśli zmienisz zamówienie, pojawią się wyjątki, zwrot pieniędzy nie zostanie utworzony ani przetworzony - skończymy w innym statusie.
- Spójność. Mamy sklep i teraz zamiast API tworzymy zdarzenia. Potrzebujemy sposobu na szybkie i tanie przekazywanie do naszych usług informacji o nowych wydarzeniach i zmianach w już istniejących. Osiąga się to poprzez wspólną specyfikację w oddzielnym repozytorium git i generatorach kodu. Dlatego klienci i serwery w różnych usługach są z nami koordynowane.
Kafka w Lamodzie
Mamy trzy instalacje Kafki:
- Dzienniki;
- Badania i rozwój;
- Autobus wydarzeń.
Dzisiaj zajmiemy się tylko tym ostatnim punktem. W events-bus nie mamy bardzo dużych instalacji - 3 brokerów (serwery) i tylko 27 tematów. Z reguły jeden temat oznacza jeden proces. Ale to jest subtelna kwestia, którą omówimy teraz.

Powyżej znajduje się wykres rps. Proces zwrotów pieniędzy oznaczony jest turkusową linią (tak, tą na osi X), a proces aktualizacji treści oznaczony jest na różowo.
Katalog Lamoda zawiera miliony produktów, a dane są stale aktualizowane. Niektóre kolekcje wychodzą z mody, na ich miejsce wypuszczane są nowe, a w katalogach ciągle pojawiają się nowe modele. Staramy się przewidzieć, co zainteresuje naszych klientów jutro, dlatego ciągle kupujemy nowe rzeczy, fotografujemy je i aktualizujemy gablotę wystawową.
Różowe szczyty oznaczają aktualizacje produktów, czyli zmiany w produktach. Widać, że chłopaki robili zdjęcia i robili zdjęcia, a potem bum! — załadował partię zdarzeń.
Przykłady zastosowań wydarzeń Lamoda
Wykorzystujemy skonstruowaną architekturę do następujących operacji:
- Śledzenie statusów zwrotów:wezwanie do działania i śledzenie statusu ze wszystkich zaangażowanych systemów. Płatności, statusy, fiskalizacja, powiadomienia. Tutaj przetestowaliśmy podejście, stworzyliśmy narzędzia, zebraliśmy wszystkie błędy, napisaliśmy dokumentację i pokazaliśmy współpracownikom, jak z niego korzystać.
- Aktualizowanie kart produktów: konfiguracja, metadane, charakterystyki. Jeden system odczytuje (ten, który wyświetla), ale kilka zapisuje.
- E-mail, push i SMS: zamówienie zostało złożone, zamówienie dotarło, zwrot został przyjęty, itd., jest ich wiele.
- Stan magazynowy, aktualizacja — aktualizacja ilościowa nazw, tylko liczby: przyjęcie do magazynu, zwrot. Konieczne jest, aby wszystkie systemy związane z rezerwacją produktów działały w oparciu o najnowsze dane. Obecnie system aktualizacji zapasów jest dość skomplikowany, Kafka go uprości.
- Analiza danych (dział badawczo-rozwojowy), narzędzia ML, analityka, statystyka. Chcemy, aby informacje były przejrzyste i Kafka doskonale się do tego nadaje.
A teraz ciekawsza część dotyczy odkryć i incydentów, które miały miejsce w ciągu ostatnich sześciu miesięcy.
Problemy projektowe
Załóżmy, że chcemy zrobić coś nowego, na przykład przenieść cały proces dostawy do Kafki. Część procesu jest obecnie zaimplementowana w Przetwarzaniu Zamówień w BOB. Za przekazaniem zamówienia do firmy dostawczej, jego przemieszczeniem do magazynu pośredniego itd. stoi określony model statusu. Istnieje cały monolit, nawet dwa, plus cała masa interfejsów API dedykowanych do dostarczania. Wiedzą o wiele więcej na temat wysyłki.
Wydaje się, że są to podobne obszary, jednak ich statusy są różne w przypadku Przetwarzania zamówień w BOB i w systemie wysyłkowym. Przykładowo niektóre firmy kurierskie nie wysyłają statusów pośrednich, lecz wyłącznie statusy ostateczne: „dostarczono” lub „zagubiono”. Inni natomiast bardzo szczegółowo opisują przepływ towarów. Każdy ma swoje własne zasady walidacji: dla niektórych e-mail jest prawidłowy, co oznacza, że zostanie przetworzony; dla innych nie jest to ważne, ale zamówienie i tak zostanie zrealizowane, bo jest numer telefonu, pod którym można się skontaktować, a ktoś powie, że takie zamówienie w ogóle nie zostanie zrealizowane.
Strumień danych
W przypadku Kafki pojawia się pytanie o organizację przepływu danych. Zadanie to wiąże się z wyborem strategii w kilku punktach, przeanalizujmy je wszystkie.
W jednym temacie czy w różnych?
Mamy specyfikację wydarzenia. W BOB piszemy, że takie a takie zamówienie musi zostać dostarczone i podajemy: numer zamówienia, jego skład, niektóre kody SKU i kody kreskowe itp. Gdy towar dotrze do magazynu, dostawa będzie mogła otrzymać statusy, znaczniki czasu i wszystko inne, co jest potrzebne. Ale potem chcemy otrzymywać aktualizacje tych danych w BOB. Mamy odwrotny proces pozyskiwania danych z dostawy. Czy to jest to samo wydarzenie? Czy jest to osobna wymiana zdań, która zasługuje na osobny temat?
Najprawdopodobniej będą one bardzo podobne, a pokusa założenia jednego tematu nie jest bezpodstawna, bo osobny temat to osobni konsumenci, osobne konfiguracje, osobne generowanie tego wszystkiego. Ale to nie jest fakt.
Nowe pole czy nowe wydarzenie?
Jeśli jednak użyjesz tych samych zdarzeń, pojawi się inny problem. Na przykład nie wszystkie systemy dostaw potrafią wygenerować DTO w taki sposób, w jaki potrafi to zrobić BOB. Wysyłamy im identyfikatory, ale oni ich nie zapisują, bo ich nie potrzebują, a z punktu widzenia uruchomienia procesu magistrali zdarzeń to pole jest obowiązkowe.
Jeżeli wprowadzimy regułę dla magistrali zdarzeń, zgodnie z którą to pole jest obowiązkowe, wówczas będziemy zmuszeni ustawić dodatkowe reguły walidacji w BOB lub w obsłudze zdarzeń początkowych. Walidacja zaczyna być stosowana w ramach całej usługi, co nie jest zbyt wygodne.
Innym problemem jest pokusa stopniowego rozwoju. Mówi się nam, że do wydarzenia trzeba coś dodać i być może, jeśli się nad tym zastanowimy, powinno to być osobne wydarzenie. Ale w naszym schemacie osobne wydarzenie jest osobnym tematem. Osobnym tematem jest cały proces, który opisałem powyżej. Programista może po prostu dodać kolejne pole do schematu JSON i wygenerować ponownie.
W przypadku zwrotów pieniędzy na zdarzenie musieliśmy czekać pół roku. Mieliśmy jedno metazdarzenie o nazwie aktualizacja zwrotu, które miało pole typu opisujące, na czym właściwie polega ta aktualizacja. Z tego powodu mieliśmy „wspaniałe” przełączniki z walidatorami, które mówiły, jak zweryfikować to zdarzenie przy użyciu tego typu.
Wersjonowanie zdarzeń
Aby sprawdzić poprawność wiadomości w Kafce, możesz użyć , ale konieczne było natychmiastowe zaplanowanie tego i użycie Confluent. W naszym przypadku musimy być ostrożni z wersjonowaniem. Nie zawsze będzie możliwe ponowne odczytanie wiadomości z dziennika replikacji, ponieważ model „opuścił”. Zasadniczo polega to na budowaniu wersji zapewniających wsteczną kompatybilność modelu: na przykład poprzez tymczasowe uczynienie pola opcjonalnym. Jeśli różnice są zbyt duże, zaczynamy pisać w nowym temacie i przenosimy klientów, gdy skończą czytać stary.
Gwarancja kolejności odczytu partycji
Tematy w Kafce są podzielone na partycje. Nie ma to większego znaczenia, gdy projektujemy podmioty i giełdy, ale jest istotne, gdy podejmujemy decyzję, jak je wykorzystywać i skalować.
W normalnym przypadku piszesz jeden temat do Kafki. Domyślnie używana jest jedna partycja i wszystkie wiadomości tego tematu są do niej kierowane. A konsument odczytuje te wiadomości po kolei. Załóżmy, że teraz musimy rozszerzyć system tak, aby wiadomości były odczytywane przez dwóch różnych odbiorców. Jeśli na przykład wysyłasz SMS-y, możesz polecić Kafce utworzenie dodatkowej partycji, a Kafka zacznie dzielić wiadomości na dwie części — połowę tu i połowę tu.
Jak Kafka je dzieli? Każda wiadomość ma treść (w której przechowujemy JSON) i klucz. Do tego klucza można zastosować funkcję skrótu, która określi, w której partycji znajdzie się wiadomość.
W naszym przypadku ze zwrotami pieniędzy ma to duże znaczenie, ponieważ jeśli weźmiemy dwie partycje, istnieje ryzyko, że równoległy konsument przetworzy drugie zdarzenie przed pierwszym i pojawią się problemy. Funkcja skrótu zapewnia, że wiadomości z tym samym kluczem trafią do tej samej partycji.
Wydarzenia kontra polecenia
To jest kolejny problem, z którym się zmierzyliśmy. Wydarzenie to określone zdarzenie: mówimy, że coś wydarzyło się gdzieś (something_happened), na przykład, że anulowano przedmiot lub dokonano zwrotu pieniędzy. Jeśli ktoś nasłuchuje tych zdarzeń, wówczas dla „anulowania przedmiotu” zostanie utworzona jednostka zwrotu, a „nastąpił zwrot” zostanie zapisane w ustawieniach.
Ale zazwyczaj, projektując wydarzenia, nie chcesz pisać ich na próżno – liczysz na to, że ktoś je przeczyta. Istnieje duża pokusa, aby napisać nie coś_się_stało (item_canceled, refund_refunded), ale coś_należy_zrobić. Na przykład, przedmiot jest gotowy do zwrotu.
Z jednej strony sugeruje to, w jaki sposób wydarzenie zostanie wykorzystane. Z drugiej strony wygląda to dużo mniej jak normalny tytuł wydarzenia. Poza tym niedaleko stąd do polecenia do_something. Ale nie masz gwarancji, że ktoś przeczytał to wydarzenie; a jeśli przeczytasz, to przeczytałeś pomyślnie; a jeśli przeczytałeś ją z powodzeniem, to coś zrobiłeś i to coś się udało. W chwili, gdy zdarzenie staje się zadaniem do_something, konieczna staje się informacja zwrotna, a to stanowi problem.

W komunikacji asynchronicznej w RabbitMQ, gdy odczytujesz wiadomość, przechodzisz na protokół http i otrzymujesz odpowiedź – przynajmniej informację, że wiadomość została odebrana. Kiedy piszesz do Kafki, istnieje wiadomość, którą napisałeś do Kafki, ale nie wiesz nic o tym, jak została przetworzona.
Dlatego w naszym przypadku musieliśmy wprowadzić zdarzenie odpowiedzi i skonfigurować monitorowanie tak, aby w przypadku wyzwolenia określonej liczby zdarzeń, taka sama liczba zdarzeń odpowiedzi powinna nadejść po określonym czasie. Jeżeli tak się nie dzieje, to znaczy, że coś poszło nie tak. Na przykład, jeśli wysłaliśmy zdarzenie „item_ready_to_refund”, oczekujemy, że zwrot zostanie utworzony, klient otrzyma z powrotem swoje pieniądze, a my otrzymamy zdarzenie „money_refunded”. Ale nie ma co do tego pewności, dlatego konieczne jest monitorowanie.
Niuanse
Problem jest oczywisty: jeśli konsekwentnie czytasz dany temat i masz zły przekaz, odbiorca traci zainteresowanie i nie pójdziesz dalej. Potrzebujesz zatrzymać wszystkich konsumentów, zatwierdź przesunięcie dalej, aby kontynuować czytanie.
Wiedzieliśmy o tym, liczyliśmy na to, ale i tak się stało. Stało się tak dlatego, że zdarzenie było poprawne z punktu widzenia magistrali zdarzeń, zdarzenie było poprawne z punktu widzenia walidatora aplikacji, ale nie było poprawne z punktu widzenia PostgreSQL, ponieważ w jednym systemie mieliśmy MySQL z UNSIGNED INT, a w nowo napisanym systemie był PostgreSQL po prostu z INT. Jest trochę mniejszy i identyfikator się nie zmieścił. Symfony zakończyło się niepowodzeniem z pewnym wyjątkiem. Oczywiście złapaliśmy wyjątek, ponieważ na nim polegaliśmy i zamierzaliśmy zatwierdzić to przesunięcie, ale wcześniej chcieliśmy zwiększyć licznik problemów, ponieważ wiadomość nie została przetworzona. Liczniki w tym projekcie znajdują się również w bazie danych, a Symfony już zamknął komunikację z bazą danych, a drugi wyjątek zabił cały proces bez możliwości zatwierdzenia przesunięcia.
Usługa przez jakiś czas była niedostępna - na szczęście w przypadku Kafki nie jest tak źle, bo wiadomości pozostały. Po wznowieniu pracy można dokończyć czytanie. To wygodne.
Kafka ma możliwość ustawienia dowolnego przesunięcia za pomocą narzędzi. Aby to jednak zrobić, trzeba zatrzymać wszystkich konsumentów - w naszym przypadku należy przygotować osobne wydanie, w którym nie będzie żadnych konsumentów, przegrupowań. Następnie można przesunąć przesunięcie w Kafce za pomocą narzędzi, a wiadomość zostanie przekazana.
Innym niuansem jest dziennik replikacji vs rdkafka.so — wiąże się ze specyfiką naszego projektu. Mamy PHP, a w PHP z reguły wszystkie biblioteki komunikują się z Kafką poprzez repozytorium rdkafka.so, a następnie istnieje pewnego rodzaju wrapper. Być może to nasze osobiste trudności, ale okazuje się, że ponowne przeczytanie fragmentu czegoś, co już przeczytaliśmy, nie jest takie proste. Generalnie były problemy z oprogramowaniem.
Wracając do szczegółów pracy z partycjami, jest to opisane bezpośrednio w dokumentacji konsumenci >= partycje tematyczne. Ale dowiedziałem się o tym dużo później, niż bym chciał. Jeśli chcesz skalować i mieć dwóch użytkowników, potrzebujesz co najmniej dwóch partycji. Oznacza to, że jeśli masz partycję, na której zgromadziło się 20 tysięcy wiadomości i utworzysz nową, liczba wiadomości nie wyrówna się równie szybko. Dlatego, aby mieć dwóch równoległych konsumentów, należy zająć się partycjami.
Monitorowanie
Myślę, że w oparciu o sposób monitorowania stanie się jeszcze bardziej jasne, jakie problemy występują w obecnym podejściu.
Na przykład obliczamy, ile produktów w bazie danych ostatnio zmieniło status, a następnie zdarzenia, które powinny były nastąpić na podstawie tych zmian, wysyłamy tę liczbę do naszego systemu monitorującego. Następnie z Kafki otrzymujemy drugą liczbę, dotyczącą liczby faktycznie zarejestrowanych zdarzeń. Oczywiste jest, że różnica między tymi dwiema liczbami zawsze musi wynosić zero.

Ponadto należy monitorować, jak radzi sobie producent, czy magistrala zdarzeń otrzymała wiadomości i jak radzi sobie konsument. Przykładowo na poniższych wykresach Refund Tool radzi sobie dobrze, natomiast BOB wyraźnie ma pewne problemy (niebieskie szczyty).

Wspomniałem już o opóźnieniu w stosunku do grup konsumenckich. Mówiąc w skrócie, jest to liczba nieprzeczytanych wiadomości. Ogólnie rzecz biorąc, nasi konsumenci pracują szybko, więc opóźnienie zwykle wynosi 0, ale czasami może wystąpić krótkotrwały szczyt. Kafka potrafi to zrobić od razu, ale trzeba ustawić pewien odstęp czasu.
Jest projekt , który dostarczy Ci więcej informacji na temat Kafki. Wysyła po prostu status dotyczący funkcjonowania tej grupy za pomocą interfejsu API grupy konsumenckiej. Oprócz komunikatów OK i Failed pojawia się również ostrzeżenie, dzięki któremu możesz dowiedzieć się, że Twoi konsumenci nie nadążają za tempem produkcji - nie mają czasu na sprawdzanie tego, co jest pisane. System jest bardzo inteligentny i łatwy w użyciu.

Tak wygląda odpowiedź API. Oto grupa bob-live-fifa, partition refund.update.v1, status OK, lag 0 — ostatnie ostateczne przesunięcie jest takie a takie.

Monitorowanie updated_at SLA (zablokowany) Już wspomniałem. Na przykład produkt zmienił status na gotowy do zwrotu. Ustawiliśmy cron, który informuje, że jeśli obiekt ten nie zostanie przeniesiony w celu dokonania zwrotu w ciągu 5 minut (pieniądze są zwracane za pośrednictwem systemów płatności bardzo szybko), to znaczy, że coś poszło nie tak i taka sytuacja wymaga wsparcia. Więc po prostu bierzemy Crona, który odczytuje takie rzeczy i jeśli są większe od 0, wysyła alert.
Podsumowując, wydarzenia są przydatne, gdy:
- informacja jest potrzebna wielu systemom;
- wynik przetwarzania nie jest istotny;
- jest mało wydarzeń lub są one niewielkie.
Wydawałoby się, że artykuł dotyczy bardzo konkretnego tematu - asynchronicznego API w Kafce, ale w związku z tym chciałbym polecić od razu wiele rzeczy.
Najpierw następny nie musimy czekać do listopada, w kwietniu będzie wersja petersburska, a w czerwcu będziemy mówić o dużych obciążeniach w Nowosybirsku.
Po drugie, autor raportu, Siergiej Zajka, jest członkiem Komitetu Programowego naszej nowej konferencji poświęconej zarządzaniu wiedzą . Konferencja odbędzie się 26 kwietnia, ale ma bardzo napięty program.
A w maju będzie kolejny и (w tym DevOpsConf) - możesz tam także zaproponować swój temat, podzielić się swoimi doświadczeniami i ponarzekać na siniaki i guzy.
Źródło: www.habr.com
