Jak zapobiec warunkom wyścigowym w aplikacji internetowej?

31

Zastanów się nad witryną e-commerce, w której Alice i Bob edytują listę produktów. Alice poprawia opisy, a Bob aktualizuje ceny. Jednocześnie rozpoczynają edycję widgetu Acme Wonder. Bob kończy jako pierwszy i zapisuje produkt w nowej cenie. Alice potrzebuje nieco więcej czasu na aktualizację opisu, a kiedy skończy, zapisuje produkt z nowym opisem. Niestety, ona również zastępuje cenę starą, która nie była zamierzona.

Z mojego doświadczenia wynika, że ​​te problemy są niezwykle powszechne w aplikacjach internetowych. Niektóre oprogramowanie (np. Oprogramowanie wiki) ma ochronę przed tym - zwykle drugie zapisywanie kończy się niepowodzeniem z informacją, że „strona została zaktualizowana podczas edycji”. Ale większość stron internetowych nie ma tej ochrony.

Warto zauważyć, że metody kontrolerów same w sobie są bezpieczne dla wątków. Zwykle używają transakcji bazy danych, co czyni je bezpiecznymi w tym sensie, że jeśli Alice i Bob spróbują zaoszczędzić dokładnie w tym samym momencie, nie spowoduje to uszkodzenia. Wyścig wynika z faktu, że Alice lub Bob mają nieaktualne dane w przeglądarce.

Jak możemy zapobiec takim warunkom wyścigowym? W szczególności chciałbym wiedzieć:

  • Jakie techniki można zastosować? np. śledzenie czasu ostatniej zmiany. Jakie są zalety i wady każdego z nich.
  • Jaka jest wygodna obsługa?
  • Jakie ramy mają wbudowaną tę ochronę?
paj28
źródło
Podałeś już odpowiedź: śledząc datę zmiany obiektów i porównując ją z wiekiem danych, które inne zmiany próbują zaktualizować. Chcesz wiedzieć coś jeszcze, np. Jak to zrobić skutecznie?
Kilian Foth
@KilianFoth - Dodałem kilka informacji o tym, co szczególnie chciałbym wiedzieć
paj28,
1
Twoje pytanie nie jest w żaden sposób specjalne dla aplikacji internetowych, aplikacje komputerowe mogą mieć dokładnie ten sam problem. Typowe strategie rozwiązań są opisane tutaj: stackoverflow.com/questions/129329/…
Doc Brown
2
Do Twojej wiadomości, forma blokowania, o której wspomniałeś w swoim pytaniu, znana jest jako „ optymistyczna kontrola współbieżności
TehShrike
Trochę dyskusji dotyczącej Django tutaj
paj28,

Odpowiedzi:

17

Musisz „czytać swoje zapisy”, co oznacza, że ​​zanim zanotujesz zmianę, musisz ponownie przeczytać zapis i sprawdzić, czy dokonano w nim zmian od czasu ostatniego odczytu. Możesz to zrobić pole po polu (drobnoziarnisty) lub na podstawie znacznika czasu (gruboziarnisty). Podczas wykonywania tej kontroli potrzebujesz wyłącznej blokady rekordu. Jeśli nie wprowadzono żadnych zmian, możesz zapisać zmiany i zwolnić blokadę. Jeśli w międzyczasie rekord się zmienił, przerywasz transakcję, zwalniasz blokadę i powiadamiasz użytkownika.

Phil
źródło
To brzmi jak najbardziej praktyczne podejście. Czy znasz jakieś ramy, które to implementują? Myślę, że największym problemem w tym schemacie jest to, że prosty komunikat „konflikt edycji” frustruje użytkowników, ale próba scalenia zestawów zmian (ręcznie lub automatycznie) jest trudna.
paj28,
Niestety nie znam żadnych frameworków, które obsługują to od razu po wyjęciu z pudełka. Nie sądzę, aby komunikat o błędzie edycji konfliktu był postrzegany jako frustrujący, o ile nie pojawia się często. Ostatecznie zależy to od obciążenia użytkownika systemu, po prostu sprawdzasz znacznik czasu lub wdrażasz bardziej złożoną funkcję scalania.
Phil
Utrzymałem rozproszony produkt bazodanowy na komputery PC, który korzystał z precyzyjnego podejścia (w porównaniu z kopią lokalnej bazy danych): jeśli jeden użytkownik zmienił cenę, a drugi zmienił opis - nie ma problemu! Tak jak w prawdziwym życiu. Jeśli dwóch użytkowników zmieniło cenę - drugi użytkownik otrzymuje przeprosiny i próbuje ich zmiany ponownie. Nie ma problemu! Nie wymaga to blokad, z wyjątkiem momentu zapisywania danych w bazie danych. Nie ma znaczenia, czy jeden użytkownik pójdzie na lunch, gdy jego zmiana jest widoczna na ekranie, i prześle go później. W przypadku zdalnych zmian w bazie danych oparto się na rekordowych znacznikach czasu.
1
Dataflex miał funkcję o nazwie „reread ()”, która robi to, co opisujesz. W późniejszych wersjach był bezpieczny w środowisku wielu użytkowników. I rzeczywiście był to jedyny sposób, aby takie przeplecione aktualizacje działały.
czy możesz podać przykład, jak to zrobić z serwerem sql? \
l
10

Widziałem 2 główne sposoby:

  1. Dodaj znacznik czasu ostatniej aktualizacji strony, którą edytuje użytkownik, w ukrytych danych wejściowych. Przy zatwierdzaniu znacznik czasu jest porównywany z bieżącym, a jeśli się nie zgadzają, został zaktualizowany przez kogoś innego i zwraca błąd.

    • pro: wielu użytkowników może edytować różne części strony. Strona błędu może prowadzić do strony różnicowej, na której drugi użytkownik może scalić swoje zmiany na nowej stronie.

    • con: czasami duża część wysiłku zostaje zmarnowana podczas dużych jednoczesnych edycji.

  2. Gdy użytkownik rozpoczyna edycję strony, blokuje ją na odpowiedni czas, a następnie inny użytkownik próbuje edytować stronę, wyświetla stronę błędu i musi poczekać, aż wygaśnie blokada lub pierwszy użytkownik się na nią zgodzi.

    • pro: wysiłki edycyjne nie są marnowane.

    • con: pozbawiony skrupułów użytkownik może zablokować stronę na czas nieokreślony. Strona z wygasłą blokadą może nadal być w stanie dokonać zatwierdzenia, chyba że postępuje się inaczej (przy użyciu techniki 1)

maniak zapadkowy
źródło
7

Użyj optymistycznej kontroli współbieżności .

Dodaj kolumnę versionNumber lub versionTimestamp do odpowiedniej tabeli (liczba całkowita jest najbezpieczniejsza).

Użytkownik 1 czyta rekord:

{id:1, status:unimportant, version:5}

Użytkownik 2 czyta rekord:

{id:1, status:unimportant, version:5}

Użytkownik 1 zapisuje rekord, co zwiększa wersję:

save {id:1, status:important, version:5}
new value {id:1, status:important, version:6}

Użytkownik 2 próbuje zapisać odczytany rekord:

save {id:1, status:unimportant, version:5}
ERROR

Hibernacja / JPA może to zrobić automatycznie z @Versionadnotacją

Musisz gdzieś utrzymać stan odczytanego rekordu, na ogół w sesji (jest to bezpieczniejsze niż w zmiennej o ukrytej formie).

Neil McGuigan
źródło
Dzięki ... szczególnie pomocne, aby wiedzieć o @Version. Jedno pytanie: dlaczego bezpiecznie jest przechowywać stan w sesji? W takim razie martwiłbym się, że użycie przycisku Wstecz może wprowadzić zamieszanie.
paj28,
Sesja jest bezpieczniejsza niż ukryty element formularza, ponieważ użytkownik nie będzie mógł zmienić wartości wersji. Jeśli to nie dotyczy, zignoruj ​​część dotyczącą sesji
Neil McGuigan
Ta technika nazywa Optymistyczne nieaktywny zamka i jest w SQLAlchemy jak również
paj28
@ paj28 - ten link SQLAlchemydo niczego nie wskazuje na optymistyczne blokady offline i nie mogę tego znaleźć w dokumentacji. Czy masz bardziej pomocny link lub po prostu kierujesz ludzi do SQLAlchemy w ogóle?
dwanderson
@dwanderson Miałem na myśli licznik wersji tego linku.
paj28
1

Niektóre systemy mapowania obiektowego (ORM) wykryją, które pola obiektu uległy zmianie od czasu załadowania z bazy danych, i utworzą instrukcję aktualizacji SQL, aby ustawić tylko te wartości. ActiveRecord dla Ruby on Rails to jedna z takich ORM.

Efektem netto jest to, że pola, których użytkownik nie zmienił, nie są uwzględnione w poleceniu UPDATE wysłanym do bazy danych. Ludzie, którzy aktualizują różne pola w tym samym czasie, nie zastępują się nawzajem zmianami.

W zależności od używanego języka programowania sprawdź, które ORM są dostępne i sprawdź, czy którykolwiek z nich zaktualizuje tylko kolumny w bazie danych oznaczone jako „brudne” w Twojej aplikacji.

Greg Burghardt
źródło
Cześć Greg. Niestety nie pomaga to w takich warunkach wyścigowych. Jeśli weźmiesz pod uwagę mój oryginalny przykład, kiedy Alice zapisuje, ORM zobaczy kolumnę cen jako brudną i zaktualizuje ją - nawet jeśli aktualizacja nie jest pożądana.
paj28,
1
@ paj28 Kluczowym punktem w odpowiedzi Grega jest „ pola, których użytkownik nie zmienił ”. Alice nie zmieniła ceny, więc ORM nie będzie próbował zapisać wartości „cena” w bazie danych.
Ross Patterson
@RossPatterson - w jaki sposób ORM rozpoznaje różnicę między polami zmienionymi przez użytkownika a nieaktualnymi danymi z przeglądarki? Nie robi tego, przynajmniej bez dodatkowego śledzenia. Jeśli chcesz edytować odpowiedź Grega, aby uwzględnić takie śledzenie, lub prześlij inną odpowiedź, to byłoby pomocne.
paj28,
@ paj28 pewna część systemu musi wiedzieć, co zrobił użytkownik, i przechowywać tylko zmiany, które wprowadził użytkownik. Jeśli użytkownik zmienił cenę, a następnie zmienił ją z powrotem, a następnie przesłał, nie powinno to być liczone jako „coś, co użytkownik zmienił”, ponieważ tego nie zrobił. Jeśli masz system, który wymaga tego poziomu kontroli współbieżności, musisz go zbudować w ten sposób. Jeśli nie, nie.
@nocomprende - Pewna część, jasne - ale nie ORM, jak mówi ta odpowiedź
paj28,