Wzory dla zachowania spójności w rozproszonym systemie opartym na zdarzeniach?

12

Czytałem ostatnio o pozyskiwaniu wydarzeń i bardzo podobają mi się pomysły, ale utknąłem z następującym problemem.

Załóżmy, że masz N równoczesnych procesów, które odbierają polecenia (np. Serwery sieciowe), generują w rezultacie zdarzenia i przechowują je w scentralizowanym sklepie. Załóżmy również, że cały przejściowy stan aplikacji jest utrzymywany w pamięci poszczególnych procesów poprzez sekwencyjne stosowanie zdarzeń ze sklepu.

Załóżmy, że mamy następującą regułę biznesową: każdy odrębny użytkownik musi mieć unikalną nazwę użytkownika.

Jeśli dwa procesy otrzymają polecenie rejestracji użytkownika dla tej samej nazwy użytkownika X, oba sprawdzają, czy X nie znajduje się na ich liście nazw użytkowników, reguła sprawdza się dla obu procesów i oba przechowują w sklepie zdarzenie „nowy użytkownik o nazwie X” .

Wprowadziliśmy teraz niespójny stan globalny, ponieważ naruszono regułę biznesową (dwóch różnych użytkowników ma tę samą nazwę użytkownika).

W tradycyjnym systemie typu N serwera <-> 1 RDBMS baza danych jest używana jako centralny punkt synchronizacji, który pomaga zapobiegać takim niespójnościom.

Moje pytanie brzmi: w jaki sposób systemy oparte na zdarzeniach zazwyczaj podchodzą do tego problemu? Czy po prostu przetwarzają wszystkie polecenia sekwencyjnie (np. Ograniczają ilość procesów, które można zapisać w sklepie do 1)?

Olivier Lalonde
źródło
1
Czy takie ograniczenie jest kontrolowane przez kod, czy jest to ograniczenie db? N zdarzeń może, ale nie musi, być przetwarzanych sekwencyjnie ... N zdarzeń może przechodzić walidację w tym samym czasie, nie powodując wzajemnego rozdzielania. Jeśli zamówienie ma znaczenie, musisz zsynchronizować sprawdzanie poprawności. Lub użyć kolejki do kolejkowania zdarzeń wysyłaj je kolejno
Laiv
@Laiv prawo. Dla uproszczenia założyłem, że nie ma bazy danych, wszystkie państwa są przechowywane w pamięci. Przetwarzanie określonych typów poleceń sekwencyjnie przez kolejkę byłoby opcją, ale wydaje się, że podjęcie złożonej decyzji, które mogą przyczynowo wpływać na inne, może być skomplikowane, i prawdopodobnie skończyłbym z umieszczaniem wszystkich poleceń w tej samej kolejce, co oznacza posiadanie poleceń przetwarzania pojedynczego procesu : / Na przykład, jeśli użytkownik dodaje komentarz do posta na blogu, „Usuń użytkownika”, „Zawieś użytkownika”, „Usuń wpis na blogu”, „Wyłącz komentarze na blogu” itp. Powinny znajdować się w tej samej kolejce.
Olivier Lalonde
1
Jestem w zgodzie z tobą, że praca z kolejkami lub semaforami nie jest łatwa. Ani do pracy z wzorcami współbieżności lub źródła zdarzeń. Ale w zasadzie wszystkie rozwiązania kończą się systemem zarządzającym ruchem zdarzenia. Jest to jednak ciekawy paradygmat. Istnieją również zewnętrzne pamięci podręczne zorientowane na krotki, takie jak Redis, które mogą pomóc w zarządzaniu ruchem między węzłami, np. Buforowanie ostatniego stanu encji lub jeśli taka encja jest obecnie przetwarzana. Udostępniane pamięci podręczne są dość powszechne w tego rodzaju zmianach. Może się to wydawać skomplikowane, ale nie poddawaj się ;-) jest dość interesujące
Laiv

Odpowiedzi:

6

W tradycyjnym systemie typu N serwera <-> 1 RDBMS baza danych jest używana jako centralny punkt synchronizacji, który pomaga zapobiegać takim niespójnościom.

W systemach pochodzących ze zdarzeń „magazyn zdarzeń” pełni tę samą rolę. W przypadku obiektu pozyskiwanego ze zdarzenia zapis jest dołączeniem nowych zdarzeń do określonej wersji strumienia zdarzeń. Tak więc, podobnie jak w przypadku programowania współbieżnego, można uzyskać blokadę tej historii podczas przetwarzania polecenia. W systemach opartych na zdarzeniach bardziej optymistyczne jest podejście - ładowanie poprzedniej historii, obliczanie nowej historii, a następnie porównywanie i zamiana. Jeśli do tego strumienia zapisano także inne polecenie, porównanie i zamiana nie powiodą się. Stamtąd albo ponownie uruchomisz polecenie, albo je porzucisz, a może nawet połączysz swoje wyniki z historią.

Rywalizacja staje się poważnym problemem, jeśli wszystkie N serwerów z ich komendami M próbuje zapisać w jednym strumieniu. Zwykłą odpowiedzią jest przydzielenie historii każdemu podmiotowi pochodzącemu ze zdarzenia w twoim modelu. Użytkownik (Bob) miałby więc inną historię niż Użytkownik (Alice) i zapisy do jednego nie blokują zapisu do drugiego.

Moje pytanie brzmi: w jaki sposób systemy oparte na zdarzeniach zazwyczaj podchodzą do tego problemu? Czy po prostu przetwarzają wszystkie polecenia sekwencyjnie?

Greg Young o sprawdzaniu poprawności zestawu

Czy istnieje elegancki sposób sprawdzania unikalnych ograniczeń atrybutów obiektów domeny bez przenoszenia logiki biznesowej do warstwy usług?

Krótka odpowiedź, w wielu przypadkach, głębsze zbadanie tego wymogu ujawnia, że ​​(a) jest to słabo rozumiany serwer zastępczy dla innych wymagań lub (b) że naruszenia „reguły” są dopuszczalne, jeśli można je wykryć (raport wyjątków) , zostały złagodzone w określonym przedziale czasu lub mają niską częstotliwość (np .: klienci mogą sprawdzić, czy nazwa jest dostępna przed wysłaniem polecenia, aby z niej skorzystać).

W niektórych przypadkach, gdy Twój magazyn zdarzeń jest dobry w sprawdzaniu poprawności zestawu (np. Relacyjna baza danych), wówczas implementujesz wymaganie, pisząc do tabeli „unikalnych nazw” w tej samej transakcji, która utrzymuje zdarzenia.

W niektórych przypadkach można wymusić wymóg tylko poprzez opublikowanie wszystkich nazw użytkowników w tym samym strumieniu (co pozwala na ocenę zestawu nazw w pamięci, jako część modelu domeny). - W takim przypadku dwa procesy zaktualizują próbę aktualizacji historii strumienia, ale jedna z operacji porównania i zamiany zakończy się niepowodzeniem, a ponowna próba wykonania polecenia będzie w stanie wykryć konflikt.

VoiceOfUnreason
źródło
1) Dzięki za sugestie i referencje. Kiedy mówisz „porównaj i zamień”, czy masz na myśli, że w momencie przechowywania zdarzenia proces wykryłby nowe zdarzenia, odkąd zaczął przetwarzać polecenie? Myślę, że wymagałoby to magazynu zdarzeń, który obsługuje semantykę „porównuj i zamień”, prawda? (np. „zapisz to zdarzenie i tylko jeśli ostatnie zdarzenie ma identyfikator X”)?
Olivier Lalonde
2) Podoba mi się również pomysł akceptowania tymczasowych niespójności i ich naprawy w końcu, ale nie jestem pewien, w jaki sposób mógłbym to kodować w niezawodny sposób ... może mieć dedykowany proces, który sprawdza sekwencyjnie zdarzenia i tworzy zdarzenia wycofania po wykryciu coś poszło nie tak? Dzięki!
Olivier Lalonde
(1) Powiedziałbym „nową wersję historii” zamiast „nowych wydarzeń”, ale masz pomysł; zastąp historię tylko wtedy, gdy się tego spodziewamy.
VoiceOfUnreason
(2) Tak. To trochę logika, która odczytuje partie ze sklepu w partiach, a na końcu partii rozgłasza raport wyjątków („mamy zbyt wielu użytkowników o imieniu Bob”) lub wysyła polecenia, aby zrekompensować problem (zakładając, że właściwa odpowiedź to obliczalny bez interwencji człowieka).
VoiceOfUnreason
2

Wygląda na to, że można wdrożyć proces biznesowy ( sagaw kontekście Domain Driven Design) rejestracji użytkownika, w którym użytkownik jest traktowany jak użytkownik CRDT.

Zasoby

  1. https://doc.akka.io/docs/akka/current/distribut-data.html http://archive.is/t0QIx

  2. „CRDT z danymi rozproszonymi Akka” https://www.slideshare.net/markusjura/crdts-with-akka-distribution-data, aby dowiedzieć się o

    • CmRDTs - CRDT oparte na operacji
    • CvRDTs - CRTD oparte na stanie
  3. Przykłady kodu w Scali https://github.com/akka/akka-samples/tree/master/akka-sample-distribution-data-scala . Być może najbardziej odpowiedni jest „koszyk”.

  4. Tour of Akka Cluster - Akka Distributed Data https://manuel.bernhardt.io/2018/01/03/tour-akka-cluster-akka-distribut-data/
SemanticBeeng
źródło