Jak obejść brak transakcji w MongoDB?

139

Wiem, że są tutaj podobne pytania, ale albo mówią mi, żebym przełączył się z powrotem na zwykłe systemy RDBMS, jeśli potrzebuję transakcji, albo użyj operacji atomowych lub zatwierdzania dwufazowego . Drugie rozwiązanie wydaje się najlepszym wyborem. Trzeci, za którym nie chcę podążać, ponieważ wydaje się, że wiele rzeczy może pójść nie tak i nie mogę tego przetestować pod każdym względem. Mam trudności z refaktoryzacją mojego projektu, aby wykonać operacje atomowe. Nie wiem, czy wynika to z mojego ograniczonego punktu widzenia (do tej pory pracowałem tylko z bazami danych SQL), czy też faktycznie nie można tego zrobić.

Chcielibyśmy przeprowadzić pilotażowe testy MongoDB w naszej firmie. Wybraliśmy stosunkowo prosty projekt - bramkę SMS. Pozwala naszemu oprogramowaniu na wysyłanie wiadomości SMS do sieci komórkowej, a bramka wykonuje brudną robotę: faktycznie komunikuje się z dostawcami za pośrednictwem różnych protokołów komunikacyjnych. Brama zarządza również fakturowaniem wiadomości. Każdy klient zgłaszający się do usługi musi wykupić kilka kredytów. System automatycznie zmniejsza saldo użytkownika w przypadku wysłania wiadomości i odmawia dostępu, jeśli saldo jest niewystarczające. Ponieważ jesteśmy klientami zewnętrznych dostawców usług SMS, możemy również mieć u nich własne salda. Te również musimy śledzić.

Zacząłem się zastanawiać, jak mogę przechowywać wymagane dane w MongoDB, jeśli zredukuję trochę złożoności (rozliczenia zewnętrzne, wysyłanie SMS-ów w kolejce). Pochodząc ze świata SQL, stworzyłbym osobną tabelę dla użytkowników, drugą dla wiadomości SMS i jedną do przechowywania transakcji dotyczących salda użytkowników. Powiedzmy, że tworzę osobne kolekcje dla wszystkich w MongoDB.

Wyobraź sobie zadanie wysyłania wiadomości SMS z następującymi krokami w tym uproszczonym systemie:

  1. sprawdzić, czy użytkownik ma wystarczającą równowagę; odmów dostępu, jeśli nie ma wystarczających środków

  2. wysłać i zapisać wiadomość w kolekcji SMS ze szczegółami i kosztem (w systemie na żywo wiadomość miałaby statusatrybut, a zadanie odebrałoby ją do doręczenia i ustalało cenę SMS-a zgodnie z jego aktualnym stanem)

  3. zmniejszyć saldo użytkowników o koszt wysłanej wiadomości

  4. zarejestruj transakcję w kolekcji transakcji

Jaki jest z tym problem? MongoDB może wykonywać niepodzielne aktualizacje tylko w jednym dokumencie. W poprzednim przepływie może się zdarzyć, że wkradnie się jakiś błąd i wiadomość zostanie zapisana w bazie danych, ale saldo użytkownika nie jest aktualizowane i / lub transakcja nie jest rejestrowana.

Wpadłem na dwa pomysły:

  • Utwórz pojedynczą kolekcję dla użytkowników i przechowuj saldo jako pole, transakcje związane z użytkownikiem i komunikaty jako dokumenty podrzędne w dokumencie użytkownika. Ponieważ możemy aktualizować dokumenty w sposób atomowy, w rzeczywistości rozwiązuje to problem transakcji. Wady: jeśli użytkownik wyśle ​​wiele wiadomości SMS, rozmiar dokumentu może stać się duży i może zostać osiągnięty limit 4 MB dokumentów. Może uda mi się stworzyć dokumenty historyczne w takich scenariuszach, ale nie sądzę, żeby to był dobry pomysł. Nie wiem też, jak szybki byłby system, gdybym wypychał coraz więcej danych do tego samego dużego dokumentu.

  • Utwórz jedną kolekcję dla użytkowników i jedną dla transakcji. Mogą być dwa rodzaje transakcji: zakup kredytowy z dodatnią zmianą salda oraz wiadomości wysłane z ujemną zmianą salda. Transakcja może mieć dokument podrzędny; na przykład w wysyłanych wiadomościach szczegóły SMS-a mogą być osadzone w transakcji. Wady: nie przechowuję aktualnego salda użytkownika, więc muszę je obliczać za każdym razem, gdy użytkownik próbuje wysłać wiadomość, aby stwierdzić, czy wiadomość może przejść, czy nie. Obawiam się, że te obliczenia mogą stać się powolne w miarę wzrostu liczby przechowywanych transakcji.

Jestem trochę zdezorientowany, którą metodę wybrać. Czy są inne rozwiązania? Nie udało mi się znaleźć w internecie żadnych sprawdzonych metod dotyczących rozwiązywania tego typu problemów. Myślę, że wielu programistów, którzy próbują zaznajomić się ze światem NoSQL, ma na początku podobne problemy.

NagyI
źródło
61
Wybacz mi, jeśli się mylę, ale wygląda na to, że ten projekt będzie korzystał z magazynu danych NoSQL, niezależnie od tego, czy odniesie z tego korzyści, czy nie. NoSQL nie są alternatywą dla SQL jako „modowy” wybór, ale do sytuacji, gdy technologia relacyjnych RDBMS nie pasuje do problemu, a nierelacyjny magazyn danych tak. Wiele z twoich pytań brzmi „Gdyby to był SQL ...” i to mnie ostrzega. Wszystkie NoSQL powstały z potrzeby rozwiązania problemu, którego SQL nie mógł, a następnie zostały nieco uogólnione, aby uczynić je łatwiejszymi w użyciu, a następnie oczywiście zaczyna się rozwijać.
PurplePilot,
4
Zdaję sobie sprawę, że ten projekt nie jest najlepszy do wypróbowania NoSQL. Jednak obawiam się, że zaczniemy go używać w innych projektach (powiedzmy, że program do zarządzania zbiorami bibliotecznymi, ponieważ zajmujemy się zarządzaniem zbiorami) i nagle pojawi się jakieś żądanie, które wymaga transakcji (i faktycznie tam jest, wyobraź sobie, że książka jest przenoszony z jednej kolekcji do drugiej) musimy wiedzieć, jak możemy rozwiązać ten problem. Może to tylko ja jestem osobą ograniczoną i uważam, że transakcje zawsze są potrzebne. Ale może być sposób, aby to jakoś przezwyciężyć.
NagyI,
3
Zgadzam się z PurplePilot, powinieneś wybrać technologię, która pasuje do rozwiązania, a nie próbować przeszczepiać rozwiązania, które nie jest odpowiednie dla problemu. Modelowanie danych dla grafowych baz danych to zupełnie inny paradygmat niż projektowanie RDBMS i musisz zapomnieć o wszystkim, co wiesz i na nowo nauczyć się nowego sposobu myślenia.
9
Rozumiem, że powinienem użyć odpowiedniego narzędzia do tego zadania. Jednak dla mnie - kiedy czytam takie odpowiedzi - wydaje mi się, że NoSQL nie nadaje się do niczego, gdzie dane są krytyczne. Jest to dobre dla Facebooka lub Twittera, gdzie jeśli zgubią się niektóre komentarze, świat się toczy, ale wszystko powyżej jest nieczynne. Jeśli to prawda, nie rozumiem, dlaczego innym zależy na budowaniu np. sklep internetowy z MongoDB: kylebanker.com/blog/2010/04/30/mongodb-and-ecommerce Wspomina nawet, że większość transakcji można pokonać za pomocą atomowych operacji. To, czego szukam, to jak.
NagyI,
2
Mówisz, że „wydaje się, że NoSQL nie nadaje się do niczego, gdzie dane są krytyczne” nie jest prawdą, gdy nie jest dobre (być może) jest transakcyjnym przetwarzaniem transakcyjnym typu ACID. Również NoSQL są zaprojektowane dla rozproszonych magazynów danych, których przechowywanie typu SQL może być bardzo trudne do osiągnięcia, gdy przejdziesz do scenariuszy replikacji master slave. NoSQL ma strategie dla ostatecznej spójności i zapewnienia, że ​​używany jest tylko najnowszy zestaw danych, ale nie ACID.
PurplePilot,

Odpowiedzi:

23

Począwszy od 4.0, MongoDB będzie obsługiwać wielodokumentowe transakcje ACID. Plan polega na tym, aby najpierw włączyć te we wdrożeniach zestawu replik, a następnie klastry podzielone na fragmenty. Transakcje w MongoDB będą wyglądać tak, jak transakcje, które programiści znają z relacyjnych baz danych - będą składać się z wielu instrukcji, z podobną semantyką i składnią (jak start_transactioni commit_transaction). Co ważne, zmiany w MongoDB, które umożliwiają transakcje, nie wpływają na wydajność obciążeń, które ich nie wymagają.

Więcej informacji można znaleźć tutaj .

Posiadanie transakcji rozproszonych nie oznacza, że ​​należy modelować dane tak, jak w tabelarycznych relacyjnych bazach danych. Wykorzystaj możliwości modelu dokumentu i postępuj zgodnie z dobrymi i zalecanymi praktykami modelowania danych.

Grigori Melnik
źródło
1
Nadeszły transakcje! 4.0 GA'ed. mongodb.com/blog/post/…
Grigori Melnik
Transakcje MongoDB nadal mają ograniczenie rozmiaru transakcji do 16 MB, ostatnio miałem przypadek użycia, w którym muszę umieścić 50k rekordów z pliku w mongoDB, więc aby zachować atomową właściwość, myślałem o użyciu transakcji, ale od 50k rekordów JSON przekracza ten limit, zgłasza błąd „Całkowity rozmiar wszystkich operacji transakcyjnych musi być mniejszy niż 16793600. Rzeczywisty rozmiar to 16793817”. aby uzyskać więcej informacji, możesz przejść przez oficjalny bilet na Jirę otwarty w mongoDB jira.mongodb.org/browse/SERVER-36330
Gautam Malik
MongoDB 4.2 (obecnie w wersji beta, RC4) obsługuje duże transakcje. Reprezentując transakcje w wielu wpisach oplog, będziesz mógł zapisać ponad 16 MB danych w jednej transakcji ACID (z zastrzeżeniem istniejącego domyślnego 60-sekundowego maksymalnego czasu wykonania). Możesz je wypróbować już teraz - mongodb.com/download-center/community
Grigori Melnik
MongoDB 4.2 jest teraz GA z pełną obsługą transakcji rozproszonych. mongodb.com/blog/post/…
Grigori Melnik
83

Życie bez transakcji

Transakcje obsługują właściwości ACID , ale chociaż nie ma transakcji w MongoDB, mamy operacje atomowe. Cóż, niepodzielne operacje oznaczają, że kiedy pracujesz nad pojedynczym dokumentem, praca ta zostanie zakończona, zanim ktokolwiek inny zobaczy dokument. Zobaczą wszystkie wprowadzone przez nas zmiany lub żadną z nich. Używając operacji atomowych, często można osiągnąć to samo, co osiągnęlibyśmy przy użyciu transakcji w relacyjnej bazie danych. Powodem jest to, że w relacyjnej bazie danych musimy wprowadzić zmiany w wielu tabelach. Zwykle stoły, które trzeba połączyć, więc chcemy to zrobić od razu. Aby to zrobić, ponieważ istnieje wiele tabel, będziemy musieli rozpocząć transakcję i wykonać wszystkie aktualizacje, a następnie zakończyć transakcję. Ale zMongoDB, zamierzamy osadzić dane, ponieważ zamierzamy je wstępnie łączyć w dokumentach i są to te bogate dokumenty, które mają hierarchię. Często możemy osiągnąć to samo. Na przykład w przykładzie na blogu, jeśli chcieliśmy mieć pewność, że zaktualizowaliśmy post na blogu atomowo, możemy to zrobić, ponieważ możemy zaktualizować cały wpis na blogu jednocześnie. Gdyby to była grupa tabel relacyjnych, prawdopodobnie musielibyśmy otworzyć transakcję, abyśmy mogli zaktualizować zbiór postów i kolekcję komentarzy.

Więc jakie są nasze podejścia, które możemy przyjąć, MongoDBaby przezwyciężyć brak transakcji?

  • restrukturyzacja - zrestrukturyzuj kod, abyśmy pracowali w jednym dokumencie i korzystali z atomowych operacji, które oferujemy w tym dokumencie. A jeśli to robimy, zwykle wszystko jest gotowe.
  • implementować w oprogramowaniu - możemy zaimplementować blokowanie w oprogramowaniu, tworząc sekcję krytyczną. Możemy zbudować test, przetestować i ustawić za pomocą funkcji znajdź i zmodyfikuj. W razie potrzeby możemy zbudować semafory. W pewnym sensie tak i tak działa większy świat. Jeśli pomyślimy o tym, jeśli jeden bank musi przelać pieniądze do innego banku, to nie żyją w tym samym systemie relacji. I każdy z nich ma często własne relacyjne bazy danych. Muszą być w stanie koordynować tę operację, nawet jeśli nie możemy rozpocząć i zakończyć transakcji w tych systemach baz danych, tylko w jednym systemie w jednym banku. W oprogramowaniu z pewnością istnieją sposoby obejścia problemu.
  • tolerować - ostatecznym podejściem, które często sprawdza się we współczesnych aplikacjach internetowych i innych aplikacjach, które pobierają ogromne ilości danych, jest po prostu tolerowanie niewielkiej niespójności. Na przykład, jeśli mówimy o kanale znajomych na Facebooku, nie ma znaczenia, czy wszyscy widzą aktualizację Twojej ściany jednocześnie. Jeśli okej, jeśli jedna osoba jest kilka uderzeń za kilka sekund i nadrabia zaległości. W wielu projektach systemów często nie jest krytyczne, aby wszystko było idealnie spójne i aby wszyscy mieli doskonale spójny i ten sam widok bazy danych. Moglibyśmy więc po prostu tolerować odrobinę niespójności, która jest nieco tymczasowa.

Update, findAndModify, $addToSet(W ramach aktualizacji) i $push(w ramach aktualizacji) operacje działają atomowo w ramach jednego dokumentu.

Zameer
źródło
2
Podoba mi się ta odpowiedź, zamiast pytać, czy powinniśmy wrócić do relacyjnej bazy danych. Dzięki @xameeramir!
DonnyTian
3
krytyczna część kodu nie będzie działać, jeśli masz więcej niż 1 serwer, musisz użyć zewnętrznej rozproszonej usługi blokowania
Alexander Mills,
@AlexanderMills Czy możesz coś rozwinąć?
Zameer
answere wydaje się być transkrypcją wideo stąd: youtube.com/watch?v=_Iz5xLZr8Lw
Fritz
Myślę, że wydaje się to w porządku, dopóki nie ograniczymy konieczności operowania na jednej kolekcji. Ale nie możemy umieścić wszystkiego w jednym dokumencie z różnych powodów (rozmiar dokumentu lub jeśli korzystasz z referencji). Myślę, że wtedy możemy potrzebować transakcji.
user2488286
24

Sprawdź to , autorstwa Tokutka. Opracowują wtyczkę do Mongo, która obiecuje nie tylko transakcje, ale także wzrost wydajności.

Giovanni Bitliner
źródło
@Giovanni Bitliner. Tokutek został od tego czasu przejęty przez Perconę, a na podanym przez Ciebie linku nie widzę żadnych informacji o tym, co wydarzyło się od czasu opublikowania tego posta. Czy wiesz, co się stało z ich wysiłkiem? Wysłałem e-mail na adres e-mail z tej strony, aby się dowiedzieć.
Tyler Collier
Czego konkretnie potrzebujesz? Jeśli potrzebujesz technologii toku zastosowanej w Mongodb, wypróbuj github.com/Tokutek/mongo , jeśli potrzebujesz wersji mysql, może dodali ją do swojej standardowej wersji Mysql, którą zwykle zapewniają
Giovanni Bitliner
Jak mogę zintegrować tokutka z nodejs.
Manoj Sanjeewa
11

Skoncentruj się na rzeczy: jeśli integralność transakcyjna jest koniecznością , nie używaj MongoDB, ale używaj tylko komponentów w systemie obsługujących transakcje. Niezwykle trudno jest zbudować coś na wierzchu komponentu, aby zapewnić podobną funkcjonalność dla komponentów niezgodnych z ACID. W zależności od indywidualnych przypadków może mieć sens rozdzielenie działań na czynności transakcyjne i nietransakcyjne w jakiś sposób ...

Andreas Jung
źródło
1
Chyba masz na myśli, że NoSQL może być używany jako pomocnicza baza danych z klasycznymi RDBMS. Nie podoba mi się pomysł mieszania NoSQL i SQL w tym samym projekcie. Zwiększa to złożoność i prawdopodobnie wprowadza również nietrywialne problemy.
NagyI,
1
Rozwiązania NoSQL są rzadko używane samodzielnie. Sklepy z dokumentami (mongo i kanapa) są prawdopodobnie jedynym wykonaniem tej reguły.
Karoly Horvath
7

Jaki jest z tym problem? MongoDB może wykonywać niepodzielne aktualizacje tylko w jednym dokumencie. W poprzednim przepływie może się zdarzyć, że wkradnie się jakiś błąd i wiadomość zostanie zapisana w bazie danych, ale saldo użytkownika nie zostanie zmniejszone i / lub transakcja nie zostanie zarejestrowana.

To naprawdę nie jest problem. Błąd, o którym wspomniałeś, to błąd logiczny (błąd) lub błąd we / wy (sieć, awaria dysku). Tego rodzaju błąd może pozostawić zarówno sklepy bez transakcji, jak i sklepy transakcyjne w stanie niespójnym. Na przykład, jeśli wysłał już SMS, ale podczas zapisywania wiadomości wystąpił błąd - nie może cofnąć wysyłania SMS-ów, co oznacza, że ​​nie zostanie zalogowany, saldo użytkownika nie zostanie zmniejszone itp.

Prawdziwy problem polega na tym, że użytkownik może wykorzystać sytuację wyścigu i wysłać więcej wiadomości, niż pozwala na to jego saldo. Dotyczy to również RDBMS, chyba że wysyłasz SMS-a wewnątrz transakcji z blokowaniem pola salda (co byłoby wielkim wąskim gardłem). Jako możliwe rozwiązanie dla MongoDB byłoby użycie findAndModifynajpierw do zmniejszenia salda i sprawdzenia go, jeśli jest ujemne, nie zezwalaj na wysyłanie i zwrot kwoty (przyrost atomowy). Jeśli wynik jest pozytywny, kontynuuj wysyłanie, aw przypadku niepowodzenia zwróć kwotę. Zbieranie historii sald może być również utrzymywane, aby pomóc naprawić / zweryfikować saldo.

pingw33n
źródło
Dziękuję za świetną odpowiedź! Wiem, że jeśli korzystam z magazynów obsługujących transakcje, dane mogą ulec uszkodzeniu z powodu systemu SMS, nad którym zresztą nie mam kontroli. Jednak w przypadku Mongo istnieje szansa, że ​​błąd danych może wystąpić również w firmie. Powiedzmy, że kod zmienia saldo użytkownika za pomocą findAndModify, saldo staje się ujemne, ale zanim będę mógł poprawić błąd, pojawia się błąd i aplikacja musi zostać ponownie uruchomiona. Chyba masz na myśli, że powinienem zaimplementować coś podobnego do dwufazowego zatwierdzenia opartego na zbieraniu transakcji i regularnie sprawdzać korektę bazy danych.
NagyI,
9
Nieprawda, sklepy transakcyjne wycofają się, jeśli nie wykonasz ostatecznego zatwierdzenia.
Karoly Horvath
9
Ponadto nie wysyłasz wiadomości SMS, a następnie logujesz się do DB, to po prostu błąd. Najpierw zapisz wszystko w DB i wykonaj ostateczne zatwierdzenie, a następnie możesz wysłać wiadomość. W tym momencie coś może nadal zawieść, więc potrzebujesz zadania crona, aby sprawdzić, czy wiadomość została faktycznie wysłana, jeśli nie, spróbuj wysłać. Być może lepsza byłaby do tego dedykowana kolejka komunikatów. Ale cała sprawa sprowadza się do tego, czy możesz wysyłać SMS-y w sposób transakcyjny ...
Karoly Horvath
@NagyI tak, o to mi chodziło. Aby ułatwić skalowalność, trzeba handlować korzyściami z transakcji. Zasadniczo aplikacja musi oczekiwać, że dowolne dwa dokumenty w różnych kolekcjach mogą być w niespójnym stanie i być gotowa do obsługi tego. @yi_H nastąpi wycofanie, ale stan nie będzie już aktualny (informacje o wiadomości zostaną utracone). Nie jest to dużo lepsze niż posiadanie częściowych danych (takich jak zmniejszone saldo, ale brak informacji o wiadomości lub odwrotnie).
pingw33n,
Widzę. W rzeczywistości nie jest to łatwe ograniczenie. Może powinienem dowiedzieć się więcej o tym, jak systemy RDBMS dokonują transakcji. Czy możesz polecić jakieś materiały lub książki online, w których mogę o nich przeczytać?
NagyI,
6

Projekt jest prosty, ale musisz obsługiwać transakcje o zapłatę, co utrudnia całość. Na przykład złożony system portalu z setkami kolekcji (forum, czat, ogłoszenia itp.) Jest w pewnym sensie prostszy, ponieważ jeśli stracisz wpis na forum lub czacie, nikogo to nie obchodzi. Z drugiej strony, jeśli stracisz transakcję płatniczą, to poważny problem.

Jeśli więc naprawdę chcesz mieć projekt pilotażowy dla MongoDB, wybierz taki, który jest prosty pod tym względem.

Karoly Horvath
źródło
Dziękuję za wyjaśnienie. Przykro mi to słyszeć. Podoba mi się prostota NoSQL i użycie JSON. Poszukujemy alternatywy dla ORM, ale wygląda na to, że musimy się z nią jeszcze chwilę trwać.
NagyI
Czy możesz podać jakieś dobre powody, dla których MongoDB jest lepsze niż SQL w tym zadaniu? Projekt pilotażowy brzmi trochę głupio.
Karoly Horvath
Nie powiedziałem, że MongoDB jest lepsze niż SQL. Chcemy po prostu wiedzieć, czy jest lepszy niż SQL + ORM. Ale teraz staje się jasne, że nie są konkurencyjni w tego rodzaju projektach.
NagyI
6

Transakcje są nieobecne w MongoDB z ważnych powodów. To jedna z tych rzeczy, które sprawiają, że MongoDB jest szybsze.

W twoim przypadku, jeśli transakcja jest koniecznością, mongo wydaje się nie pasować.

Może to być RDMBS + MongoDB, ale zwiększy to złożoność i utrudni zarządzanie i obsługę aplikacji.

kheya
źródło
1
Obecnie istnieje dystrybucja MongoDB o nazwie TokuMX, która wykorzystuje technologię fraktalną, aby zapewnić 50-krotną poprawę wydajności i jednocześnie zapewnia pełną obsługę transakcji ACID: tokutek.com/tokumx-for-mongodb
OCDev
9
Jak transakcja mogłaby nigdy nie być „koniecznością”. Jak tylko będziesz potrzebować 1 prostego przypadku, w którym musisz zaktualizować 2 tabele, mongo nagle przestaje być dobrym rozwiązaniem? To wcale nie pozostawia zbyt wielu przypadków użycia.
Mr_E
1
@Mr_E zgadzam się, dlatego MongoDB jest trochę głupi :)
Alexander Mills,
6

To prawdopodobnie najlepszy blog, jaki znalazłem na temat implementacji funkcji typu transakcyjnego dla mongodb.!

Flaga synchronizacji: najlepsza do kopiowania danych z dokumentu głównego

Kolejka zadań: bardzo ogólnego przeznaczenia, rozwiązuje 95% spraw. Większość systemów i tak musi mieć co najmniej jedną kolejkę zadań!

Dwufazowe zatwierdzenie: ta technika zapewnia, że ​​każda jednostka zawsze ma wszystkie informacje potrzebne do uzyskania spójnego stanu

Log Reconciliation: najsolidniejsza technika, idealna dla systemów finansowych

Wersjonowanie: zapewnia izolację i obsługuje złożone struktury

Przeczytaj to, aby uzyskać więcej informacji: https://dzone.com/articles/how-implement-robust-and

Vaibhav
źródło
Prosimy o uwzględnienie odpowiednich części połączonego zasobu potrzebnego do udzielenia odpowiedzi na pytanie. Twoja odpowiedź jest bardzo podatna na gnicie linków (tj. Jeśli połączona witryna przestaje działać lub zmienia odpowiedź, jest potencjalnie bezużyteczna).
mech
Dzięki @mech za sugestię
Vaibhav
4

Jest już późno, ale myślę, że pomoże to w przyszłości. Używam Redis do tworzenia kolejki w celu rozwiązania tego problemu.

  • Wymaganie:
    Poniższy obraz przedstawia 2 akcje, które należy wykonać jednocześnie, ale faza 2 i faza 3 działania 1 muszą zakończyć się przed rozpoczęciem fazy 2 działania 2 lub odwrotnie (Faza może być żądaniem interfejsu API REST, żądaniem bazy danych lub wykonaniem kodu javascript ... ). wprowadź opis obrazu tutaj

  • W jaki sposób kolejka pomaga
    Kolejka upewnij się, że każdy kod blokowy między funkcjami lock()i release()w wielu nie będzie działał w tym samym czasie, spraw, aby były izolowane.

    function action1() {
      phase1();
      queue.lock("action_domain");
      phase2();
      phase3();
      queue.release("action_domain");
    }
    
    function action2() {
      phase1();
      queue.lock("action_domain");
      phase2();
      queue.release("action_domain");
    }
  • Jak zbudować kolejkę
    Skoncentruję się tylko na tym, jak uniknąć części warunkowej wyścigu podczas budowania kolejki na stronie zaplecza. Jeśli nie znasz podstawowej idei kolejki, przyjdź tutaj .
    Poniższy kod pokazuje tylko koncepcję, którą musisz zaimplementować w prawidłowy sposób.

    function lock() {
      if(isRunning()) {
        addIsolateCodeToQueue(); //use callback, delegate, function pointer... depend on your language
      } else {
        setStateToRunning();
        pickOneAndExecute();
      }
    }
    
    function release() {
      setStateToRelease();
      pickOneAndExecute();
    }

Ale musisz isRunning() setStateToRelease() setStateToRunning()odizolować się od siebie, bo inaczej znowu staniesz w obliczu rasy. Aby to zrobić, wybrałem Redis dla celów ACID i skalowalny. Dokument
Redis mówi o swojej transakcji:

Wszystkie polecenia w transakcji są serializowane i wykonywane sekwencyjnie. Nigdy nie może się zdarzyć, że żądanie wysłane przez innego klienta zostanie obsłużone w trakcie wykonywania transakcji Redis. Gwarantuje to, że polecenia są wykonywane jako pojedyncza izolowana operacja.

P / s:
używam Redis, ponieważ moja usługa już go używa, możesz w tym celu skorzystać z dowolnego innego sposobu wsparcia izolacji.
Kod action_domainw moim kodzie jest powyżej, gdy potrzebujesz tylko wywołania akcji 1 przez użytkownika. Działanie blokowe 2 użytkownika A, nie blokuj innego użytkownika. Pomysł polega na umieszczeniu unikalnego klucza do zamka każdego użytkownika.

Đinh Anh Huy
źródło
Otrzymałbyś więcej głosów za, gdyby Twój wynik był już wyższy. Tak myśli większość tutaj. Twoja odpowiedź jest przydatna w kontekście pytania. Głosowałem za tobą.
Mukus
3

Transakcje są teraz dostępne w MongoDB 4.0. Próbka tutaj

// Runs the txnFunc and retries if TransientTransactionError encountered

function runTransactionWithRetry(txnFunc, session) {
    while (true) {
        try {
            txnFunc(session);  // performs transaction
            break;
        } catch (error) {
            // If transient error, retry the whole transaction
            if ( error.hasOwnProperty("errorLabels") && error.errorLabels.includes("TransientTransactionError")  ) {
                print("TransientTransactionError, retrying transaction ...");
                continue;
            } else {
                throw error;
            }
        }
    }
}

// Retries commit if UnknownTransactionCommitResult encountered

function commitWithRetry(session) {
    while (true) {
        try {
            session.commitTransaction(); // Uses write concern set at transaction start.
            print("Transaction committed.");
            break;
        } catch (error) {
            // Can retry commit
            if (error.hasOwnProperty("errorLabels") && error.errorLabels.includes("UnknownTransactionCommitResult") ) {
                print("UnknownTransactionCommitResult, retrying commit operation ...");
                continue;
            } else {
                print("Error during commit ...");
                throw error;
            }
       }
    }
}

// Updates two collections in a transactions

function updateEmployeeInfo(session) {
    employeesCollection = session.getDatabase("hr").employees;
    eventsCollection = session.getDatabase("reporting").events;

    session.startTransaction( { readConcern: { level: "snapshot" }, writeConcern: { w: "majority" } } );

    try{
        employeesCollection.updateOne( { employee: 3 }, { $set: { status: "Inactive" } } );
        eventsCollection.insertOne( { employee: 3, status: { new: "Inactive", old: "Active" } } );
    } catch (error) {
        print("Caught exception during transaction, aborting.");
        session.abortTransaction();
        throw error;
    }

    commitWithRetry(session);
}

// Start a session.
session = db.getMongo().startSession( { mode: "primary" } );

try{
   runTransactionWithRetry(updateEmployeeInfo, session);
} catch (error) {
   // Do something with error
} finally {
   session.endSession();
}
Manish Jain
źródło