Mikrousługi: obsługa ostatecznej spójności

22

Załóżmy, że mamy funkcję, która aktualizuje hasło użytkownika.

Po kliknięciu przycisku „Aktualizuj hasło”, UpdatePasswordEvent jest wysyłany do tematu, w którym subskrybowane są 3 inne usługi:

  1. Usługa, która faktycznie aktualizuje hasło użytkownika
  2. Usługa aktualizująca historię haseł użytkownika
  3. Usługa, która wysyła wiadomość e-mail informującą użytkownika, że ​​jego hasło zostało zmienione.

W oparciu o to, co zrozumiałem na temat ostatecznej spójności, wszystkie te usługi (konsumenci) otrzymają wydarzenie w tym samym czasie i przetworzą je osobno, co w dobrym scenariuszu doprowadzi do spójności danych.

Co jednak jeśli usługa nie przetworzy zdarzenia? np. nagłe rozłączenie, błąd bazy danych itp. Jaki jest dobry wzorzec / praktyka obsługi tych błędów transakcji?

Myślałem o stworzeniu RollbackTopic, w którym jeśli jakiekolwiek zdarzenie nie zostanie przetworzone, RollbackEvent zostanie utworzony w temacie, w którym „usługi wycofania” wykonają swoje zadanie i przywrócą dane z powrotem

mpmp
źródło
11
Nie można cofnąć wysłanego e-maila :-)
Laiv
2
Ponieważ wszystkie z nich powinny być częścią tej samej usługi. Mikro-usługi są przeciwne monolitom, nie oznacza to, że musisz je projektować tak mało, jak to możliwe „fizycznie”. Chociaż nie jest to bezpośrednio związane, powinieneś przeczytać to pytanie i dwie najważniejsze odpowiedzi: softwareengineering.stackexchange.com/questions/339230/…
Walfrat
1
Warto rozważyć synchronizację aktualizacji hasła użytkownika w bazie danych, aby zapewnić natychmiastową informację zwrotną dla użytkownika i asynchroniczne uruchomienie innych usług poprzez wysłanie wiadomości o zmianie hasła w temacie, dzięki czemu wiadomość nie musi zawierać hasło.
cr3
Jest to wiadomość e-mail z informacją dla użytkownika o zakończeniu transakcji, czy też informacja dla użytkownika, że ​​ktoś (mam nadzieję, że on) zmienił hasło. „Jeśli to nie byłeś ty, musisz działać”. Jeśli drugi, to po prostu wyślij teraz e-mail, najlepiej jak potrafisz.
ctrl-alt-delor

Odpowiedzi:

29

W oparciu o to, co zrozumiałem na temat ostatecznej spójności, wszystkie te usługi (konsumenci) otrzymają wydarzenie w tym samym czasie i przetworzą je osobno, co w dobrym scenariuszu doprowadzi do spójności danych.

Nie, niekoniecznie. Jak skomentowałem, nie możemy cofnąć wysłanego e-maila, więc nadal potrzebujemy czegoś w rodzaju „sekwencji”. IPC w zakresie zarządzania danymi opartymi na zdarzeniach nie jest zwolnione z organizacji 1 .

Na przykład wiadomość e-mail nie powinna zostać wysłana, chyba że poprzednie transakcje zakończą się powodzeniem, a usługa e-mail otrzyma dowód. 3)

Co jednak jeśli usługa nie przetworzy zdarzenia? np. nagłe rozłączenie, błąd bazy danych itp. Jaki jest dobry wzorzec / praktyka obsługi tych błędów transakcji?

Przywitaj się z błędami obliczeń rozproszonych . To właśnie komplikują sprawy i, jak zwykle, nie ma srebrnych kul, z którymi można by sobie poradzić.

Przed wyruszeniem w podróż w poszukiwaniu Zaginionej Arki musimy najpierw zapytać organizację. Często rozwiązaniem jest sposób, w jaki organizacja boryka się z tymi problemami w prawdziwym świecie .

Co robią wszyscy (działy), gdy brakuje niektórych danych lub są one niekompletne?

Przekonamy się, że różne działy mają różne rozwiązania, które łącznie stanowią rozwiązanie do wdrożenia.

W każdym razie, oto kilka praktyk, które mogą pomóc nam w realizacji strategii.

Ostateczna spójność

Zamiast zapewniać, że system jest cały czas w spójnym stanie, możemy zaakceptować, że system go dostanie w pewnym momencie w przyszłości. Takie podejście jest szczególnie przydatne w przypadku długotrwałych operacji biznesowych.

Sposób, w jaki system może osiągnąć spójność, różni się w zależności od systemu. Może obejmować od zautomatyzowanych procesów do pewnego rodzaju interwencji człowieka. Na przykład typowe próbowanie go ponownie później lub kontakt z obsługą klienta .

Przerwij wszystkie operacje

Przywróć system do spójnego stanu dzięki kompensacji transakcji . Musimy jednak wziąć pod uwagę, że transakcje te mogą również zawieść, co może doprowadzić nas do punktu, w którym niespójność jest jeszcze trudniejsza do rozwiązania. I znowu nie możemy cofnąć wysłanego e-maila.

W przypadku małej liczby transakcji takie podejście jest wykonalne, ponieważ liczba transakcji kompensujących jest również niska. Gdyby w IPC było zaangażowanych kilka transakcji biznesowych, obsługa jednej transakcji kompensacyjnej dla każdej z nich byłaby trudna.

Jeśli pójdziemy na transakcje kompensacyjne , okaże się, że wzorzec projektowy wyłącznika jest bardzo przydatny - i obowiązkowo ośmielę się powiedzieć -

Transakcje rozproszone

Pomysł polega na objęciu wielu transakcji w ramach jednej transakcji, poprzez ogólny proces zarządzania znany jako Transaction Manager . Częstym algorytmem do obsługi transakcji rozproszonych jest zatwierdzanie dwufazowe .

Główną troską rozproszonych transakcji jest to, że polegają one na blokowaniu zasobów w trakcie ich trwania, a jak wiemy, w przypadku Menedżera transakcji może się również nie udać .

Jeśli menedżerowie transakcji zostaną narażeni na szwank, możemy skończyć z kilkoma blokadami w różnych kontekstach ograniczonych, co skutkuje nieoczekiwanymi zachowaniami wynikającymi z kolejkowania wiadomości. 2)

Rozkład operacji. Czemu?

Jeśli dekomponujesz istniejący system i znajdziesz kolekcję pojęć, które naprawdę chcą mieścić się w granicach pojedynczej transakcji, być może zostaw je do końca.

Sam Newman

Zgodnie z powyższymi argumentami Sam - w swojej książce Building Microservices - stwierdza, że ​​jeśli naprawdę nie możemy sobie pozwolić na ostateczną spójność, powinniśmy teraz unikać podziału operacji.

Jeśli nie możemy sobie pozwolić na podział niektórych operacji na dwie lub więcej transakcji, można powiedzieć, że - prawdopodobnie - transakcje te należą do tego samego ograniczonego kontekstu, a przynajmniej do kontekstu przekrojowego, który pozostaje do modelowania.

Na przykład w naszym przypadku zdajemy sobie sprawę, że transakcje nr 1 i nr 2 są ze sobą ściśle powiązane i prawdopodobnie oba mogą należeć do tego samego ograniczonego kontekstu Konta , Użytkownicy , Rejestr , cokolwiek ...

Rozważ umieszczenie obu operacji w granicach tej samej transakcji. Ułatwi to obsługę całej operacji. Waży również poziom krytyczności każdej transakcji. Prawdopodobnie, jeśli transakcja nr 2 zakończy się niepowodzeniem, nie powinno to zagrozić całej operacji. W razie wątpliwości zapytaj organizację .


1: Nie taki rodzaj aranżacji, jaki myślisz. Nie mówię o orkiestrze ESB. Mówię o tym, aby usługi reagowały na właściwe wydarzenie.

2: Możesz znaleźć ciekawe opinie Sama Newmana dotyczące transakcji rozproszonych.

3: Sprawdź odpowiedź Davida Parkera na ten temat.

Laiv
źródło
3
Bardzo dobra odpowiedź. Chciałbym jedynie podkreślić znaczenie uwzględnienia ryzyka, które pojawia się podczas korzystania z transakcji rozproszonych - głównie blokowania zasobów, powodującego impasy i zatrzymania systemów. W przypadku produktu e-commerce, nad którym pracowałem około 3 lata temu, musieliśmy zastąpić ID systemem przesyłania wiadomości, ponieważ z powodu liczby użytkowników dostępnych w systemach, system był bardzo podatny na błędy. Problemy z ID występują najczęściej, gdy rośnie baza użytkowników.
Andy
7

W twoim przypadku nie możesz po prostu przetworzyć wszystkich trzech rzeczy naraz. Potrzebujesz procesu. Oto niezwykle uproszczony przykład:

Orkiestracja poleceń i zdarzeń

Ważne jest, aby wiedzieć, że operacje zmieniające stan MUSZĄ być zawsze wykonywane na spójnej jednostce. O ile nie można zagwarantować silnej spójności , należy to zrobić na podstawie rekordu głównego.

Twój system powinien zagwarantować, że zanim jakiekolwiek zdarzenie zostanie zgłoszone w zmianach w systemie, MUSI być najpierw utrwalony z bezpieczeństwem transakcyjnym. Ma to na celu zapewnienie, że podniesione wydarzenie naprawdę jest potwierdzeniem tego, co się naprawdę wydarzyło.

Jest kilka trudnych części tego procesu i zamierzam zignorować te oczywiste - na przykład: Co się stanie, jeśli serwer bazy danych umrze, gdy utrzyma użytkownika ze zmienionym hasłem? Po prostu ponownie wydajesz UpdatePassword. Należy jednak zająć się niektórymi częściami, a są to:

  • obsługa powielania wiadomości,
  • obsługa wysyłania wiadomości e-mail.

W systemie orkiestrator procesów (PO) jest niczym innym jak innym bytem, ​​który zawiera stan wewnętrzny - również w dosłownym znaczeniu - i umożliwia przechodzenie między stanami, skutecznie działając jako pewnego rodzaju maszyna stanu. Dzięki wewnętrznemu stanowi możesz usunąć przetwarzanie duplikacji wiadomości.

Kiedy PO jest w Newstanie i przetwarza UserPasswordHasBeenUpdated, zmienia swój stan na UserPasswordHasBeenUpdated(lub inną nazwę stanu, która Ci odpowiada). Gdyby zamówienie nadal znajdowało się na liście, UserPasswordHasBeenUpdateda inny UserPasswordHasBeenUpdatednadejdzie, zamówienie to całkowicie zignoruje wiadomość, wiedząc, że jest to powielanie. Podobny mechanizm zostałby wdrożony również dla innych państw.

Obsługa faktycznego wysyłania wiadomości e-mail jest nieco trudniejsza. Tutaj masz dwie opcje:

  1. wysłać co najwyżej raz,
  2. wyślij to przynajmniej raz.

Wyślij to co najwyżej raz

Dzięki tej opcji, gdy PO osiągnie UserPasswordHistoryHasBeenSavedstan, polecenie wysłania wiadomości e-mail jest wysyłane jako reakcja na zmianę stanu. Twój system upewni się, że UserPasswordHistoryHasBeenSavedstan zostanie utrwalony przed wysłaniem wiadomości e-mail, tzn. Zduplikowana wiadomość nie wyzwoli ponownego wysłania wiadomości e-mail. Dzięki takiemu podejściu masz pewność, że zachowany jest właściwy stan zamówienia zakupu, ale nie możesz zagwarantować żadnej kolejnej operacji.

Wyślij co najmniej raz

Po to bym poszedł.

Zamiast zapisywać UserPasswordHistoryHasBeenSavedi wysyłać wiadomości e-mail jako reakcję, najpierw próbujesz wysłać wiadomość e-mail. Jeśli operacja wysyłania zakończy się niepowodzeniem, stan zamówienia zakupu nigdy nie jest zmieniany na, UserPasswordHistoryHasBeenSaveda inna wiadomość tego samego typu jest nadal przetwarzana. Jeśli wysłanie wiadomości e-mail rzeczywiście się powiedzie, ale system zawiedzie podczas utrzymywania zamówienia zakupu w nowym UserPasswordHistoryHasBeenSavedstanie, kolejna wiadomość UserPasswordHistoryHasBeenSavedwysłałaby ponownie polecenie wysłania wiadomości e-mail, a użytkownik otrzymałby ją wiele razy .

W twoim przypadku chcesz się upewnić, że użytkownik faktycznie otrzyma wiadomość e-mail. Dlatego wybrałbym drugą opcję zamiast pierwszej.

Andy
źródło
2

Systemy kolejkowania nie są tak delikatne, jak mogłoby się wydawać.

Gdybyśmy pisali wszystkie trzy procesy do relacyjnej bazy danych, moglibyśmy użyć transakcji do obsługi awarii procesów w połowie.

Bez ostatecznego zatwierdzenia częściowa praca zostałaby odrzucona.

W systemie bazowym kolejki będziesz mieć podobne opcje, kiedy czytasz wiadomość z kolejki, aby obsłużyć awarie w trakcie procesu.

Na przykład Amazon SQS po prostu ukrywa czytane wiadomości. jeśli nie zostanie wysłane ostatnie polecenie Usuń, wiadomość pojawi się ponownie lub zostanie umieszczona w kolejce niedostarczonych wiadomości.

Możesz realizować podobne „transakcje” na różne sposoby, zasadniczo przechowując kopię wiadomości, dopóki nie otrzymasz potwierdzenia pomyślnego przetworzenia. Jeśli potwierdzenie nie zostanie odebrane na czas. możesz ponownie wysłać wiadomość lub zachować ją do ręcznej uwagi.

Potencjalnie możesz stworzyć „usługę wycofywania”, która monitorowałaby te błędne wiadomości, wiedziała o wiadomościach pokrewnych i przeszłym stanie oraz przeprowadzała wycofywanie.

Jednak! Zwykle lepiej jest po prostu ponownie wysłać błędne wiadomości. W końcu są to zwykle przypadki krawędziowe. Albo serwer uległ katastrofalnej awarii, albo wystąpił błąd w obsłudze określonego typu wiadomości.

Po otrzymaniu powiadomienia o błędzie usługa może zostać naprawiona, a wiadomości przetworzone pomyślnie. Przywracanie systemu jako całości do spójnego stanu.

Ewan
źródło
2

Stajesz przed problemem dwóch generałów . Zasadniczo: w jaki sposób możesz mieć pewność, że wiadomość zostanie odebrana i nastąpi odpowiedź na tę wiadomość? W wielu przypadkach idealne rozwiązanie nie istnieje. W rzeczywistości w systemie rozproszonym często niemożliwe jest uzyskanie dostawy wiadomości dokładnie raz .

Pierwszą oczywistą uwagą jest to, że usługa zmieniająca hasło powinna wysyłać zdarzenie zmiany hasła. W ten sposób historia haseł i usługi wysyłania poczty są uruchamiane tylko wtedy, gdy hasło faktycznie się zmienia, niezależnie od tego, dlaczego zostało zmienione.

Aby faktycznie rozwiązać problem, nie rozważyłbym transakcji rozproszonych, ale zamiast tego spojrzałem w kierunku dostarczenia wiadomości przynajmniej raz i idempotentnego przetwarzania.

  • Przynajmniej raz

    Aby upewnić się, że zdarzenie zmiany hasła jest rzeczywiście widoczne dla wszystkich konsumentów, musisz użyć trwałego kanału komunikacyjnego, w którym wiadomości mogą być odbierane w stylu „przynajmniej raz”. Konsumenci uznają wiadomość za zużytą tylko wtedy, gdy w pełni ją przetworzyli. Jeśli na przykład usługa historii haseł ulegnie awarii podczas zapisywania pozycji historii, po ponownym uruchomieniu ponownie odczyta to samo zdarzenie zmiany hasła i spróbuje ponownie, potwierdzając to zdarzenie jako tylko do odczytu po zapisaniu się w historii. Powinieneś wybrać rozwiązanie kolejki wiadomości w oparciu o jego zdolność do ponownego wysyłania wiadomości, dopóki nie zostaną potwierdzone.

  • Idempotencja

    Po osiągnięciu przynajmniej raz dostawy pojawia się problem zdublowanych działań, gdy wiadomość została częściowo przetworzona przed przerwaniem konsumenta, a następnie ponownym przetworzeniem. Należy to rozwiązać, projektując każdą usługę, aby była idempotentna. Albo zapisy, które wykonuje, mogą wystąpić wiele razy bez negatywnych skutków, albo zachowuje swój własny zbiór podejmowanych działań i unika wykonywania akcji więcej niż raz. W przypadku wysyłania poczty prawdopodobnie nie warto próbować sprawić, by zachowywała się idempotentnie i po prostu dobrze, że czasami poczta jest wysyłana dwukrotnie.

W każdym razie uważaj, jak mikro tworzysz swoje usługi. Czy usługa historii haseł naprawdę musi być niezależna od usługi zmiany hasła?

Joeri Sebrechts
źródło
1

Nie zgadzam się z wieloma odpowiedziami.

  1. Wyślij teraz wiadomość e-mail „Ktoś zmienił twoje hasło. Jeśli to ty, to nie musisz nic robić. Jeśli nie panika. ”To nadejdzie, kiedy dotrze.
  2. Zmień hasło. Chociaż masz ostateczną spójność. Chcesz mieć pewność, że w tej sesji zobaczysz zmiany wprowadzone przez użytkownika.

Istnieją inne obietnice spójności, które można dodać.

  • Upewnij się, że zmiany następują w kolejności czasowej.
  • Upewnij się, że użytkownik nigdy nie widzi wycofania, ale inni użytkownicy mogą nadal nie widzieć zmiany.
  • Są inni

Te dodatkowe spójności będą musiały zostać wdrożone w zależności od czynów aplikacji.


Nie mam pojęcia, co masz na myśli przez „aktualizowanie historii”, ale proszę, nigdy nie zmieniaj historii. Jeśli tylko rozszerzasz DAG, powinno to spowodować zmianę w bieżącym stanie. Nie są niezależni. Jeśli tak, to nie możesz polegać na historii odzwierciedlającej to, co się stało. (i na koniec, nie przechowuj haseł, zobacz, jak nie przechowywać haseł )

ctrl-alt-delor
źródło
Jeśli możesz wysłać wiadomość e-mail na początku, twoje podejście jest w porządku. Jeśli musisz wysłać coś oprócz e-maila. Może coś w rodzaju linku / danych, które można uzyskać dopiero po osiągnięciu spójności, wtedy nie możesz najpierw wysłać wiadomości e-mail. Tak skomentowałem consider asking the organization first.. Prawdopodobnie masz rację. Uważam jednak, że ważne jest uwarunkowanie zdarzeń, których nie możemy cofnąć. Na przykład powiadomienia dla użytkownika końcowego. Powiadomienie leżące na rzeczywistym stanie danych użytkownika może powodować złe wrażenie.
Laiv
To powiedziawszy, w tym konkretnym scenariuszu (powiadomienie o zmianie hasła) zgodziłem się z tym podejściem. Gdy tylko spełni wymagania.
Laiv