Pytanie w sprawie redis – Transakcje i oświadczenie obserwacyjne w Redis

30

Czy możesz mi wyjaśnić następujący przykład z „The Little Redis Book”:

Z powyższym kodem nie bylibyśmy w stanie zaimplementować naszego własnego polecenia incr, ponieważ wszystkie są wykonywane razem po wywołaniu exec. Z kodu nie możemy zrobić:

redis.multi() 
current = redis.get('powerlevel') 
redis.set('powerlevel', current + 1) 
redis.exec()

Nie tak działają transakcje Redis. Ale jeśli dodamy zegarek do powerlevel, możemy zrobić:

redis.watch('powerlevel') 
current = redis.get('powerlevel') 
redis.multi() 
redis.set('powerlevel', current + 1) 
redis.exec()

Jeśli inny klient zmieni wartość powerlevel po wywołaniu go, nasza transakcja zakończy się niepowodzeniem. Jeśli żaden klient nie zmieni wartości, zestaw będzie działać. Możemy wykonać ten kod w pętli, dopóki nie zadziała.

Dlaczego nie możemy wykonać przyrostu w transakcji, której nie można przerwać innym poleceniem? Dlaczego musimy zamiast tego iterować i czekać, aż nikt nie zmieni wartościprzed zaczyna się transakcja?

Jesteś świadomy tegoincr tak, prawda? W swoim przykładzie robi dokładnie to, czego chcesz, bez użycia transakcji. Oczywiście nie jest to odpowiedź na samo pytanie, ale mimo to warto to wiedzieć. polvoazul
@polvoazul, znam to polecenie, dzięki. Było to częste pytanie, które nie było spowodowane prawdziwym przypadkiem. Marboni

Twoja odpowiedź

1   odpowiedź
70

1) Dlaczego nie możemy wykonać przyrostu transakcji, którego nie można przerwać innym poleceniem?

Pamiętaj, że „transakcje” Redis są zupełnie inne niż to, co większość ludzi uważa za transakcje w klasycznym DBMS.

# Does not work
redis.multi() 
current = redis.get('powerlevel') 
redis.set('powerlevel', current + 1) 
redis.exec()

Musisz zrozumieć, co jest wykonywane po stronie serwera (w Redis) i co jest wykonywane po stronie klienta (w twoim skrypcie). W powyższym kodzie polecenia GET i SET będą wykonywane po stronie Redis, ale przypisanie do prądu i obliczenie bieżącego + 1 mają być wykonywane po stronie klienta.

Aby zagwarantować atomowość, blok MULTI / EXEC opóźnia wykonywanie poleceń Redis do momentu wykonania polecenia. Klient będzie więc gromadził tylko polecenia GET i SET w pamięci, a następnie wykonał je jednym strzałem i atomowo na końcu. Oczywiście próba przypisania prądu do wyniku GET i przyrostu nastąpi wcześniej. W rzeczywistości metoda redis.get zwróci tylko ciąg „QUEUED”, aby zasygnalizować, że polecenie jest opóźnione, a przyrost nie będzie działać.

W blokach MULTI / EXEC można używać tylko poleceń, których parametry mogą być w pełni znane przed rozpoczęciem bloku. Możesz przeczytaćdokumentacja po więcej informacji.

2) Dlaczego musimy zamiast tego iterować i czekać, aż nikt nie zmieni wartości przed rozpoczęciem transakcji?

Jest to przykład równoległego optymistycznego wzorca.

Gdybyśmy nie używali WATCH / MULTI / EXEC, mielibyśmy potencjalne warunki wyścigu:

# Initial arbitrary value
powerlevel = 10
session A: GET powerlevel -> 10
session B: GET powerlevel -> 10
session A: current = 10 + 1
session B: current = 10 + 1
session A: SET powerlevel 11
session B: SET powerlevel 11
# In the end we have 11 instead of 12 -> wrong

Dodajmy teraz blok WATCH / MULTI / EXEC. W klauzuli WATCH komendy między MULTI i EXEC są wykonywane tylko wtedy, gdy wartość nie uległa zmianie.

# Initial arbitrary value
powerlevel = 10
session A: WATCH powerlevel
session B: WATCH powerlevel
session A: GET powerlevel -> 10
session B: GET powerlevel -> 10
session A: current = 10 + 1
session B: current = 10 + 1
session A: MULTI
session B: MULTI
session A: SET powerlevel 11 -> QUEUED
session B: SET powerlevel 11 -> QUEUED
session A: EXEC -> success! powerlevel is now 11
session B: EXEC -> failure, because powerlevel has changed and was watched
# In the end, we have 11, and session B knows it has to attempt the transaction again
# Hopefully, it will work fine this time.

Więc nie musisz powtarzać, aby czekać, aż nikt nie zmieni wartości, ale raczej spróbować ponownie, aż Redis będzie pewien, że wartości są spójne i sygnalizuje, że się powiodło.

W większości przypadków, jeśli „transakcje” są wystarczająco szybkie, a prawdopodobieństwo wystąpienia konfliktu jest niskie, aktualizacje są bardzo wydajne. Teraz, jeśli istnieje spór, niektóre dodatkowe operacje będą musiały być wykonane dla niektórych „transakcji” (z powodu iteracji i ponownych prób). Jednak dane będą zawsze spójne i nie będzie wymagane blokowanie.

Musisz ponownie sprawdzić poziom mocy, ponieważ nie masz gwarancji, że trzecia sesja nie będzie w tym samym czasie mieszać z poziomem mocy. Didier Spezia
Świetne wyjaśnienie, wielkie dzięki, w końcu to dostałem! Po zrozumieniu punktu 1 pozycja 2 staje się oczywista. Marboni
Myślę więc, że rozumiem na podstawie twojego przykładu - że jeśli pokazałeś przykład, który wykonał MULTI / EXEC, ale nie WATCH, obie sesje mogą odczytać wartość 10, a Redis zapisuje z powrotem 11. To spowodowałoby, że writeback byłby „atomowy”, ale nie chroniłby przed odczytem klienta i zwiększaniem tej samej wartości lokalnie, co skutkowałoby błędną odpowiedzią? Brad
@DidierSpezia Czy mogę zrobić WATCH po GET? Nie widzę powodu, by zacząć oglądać klucz podczas odczytywania wartości mtkachenko

Powiązane pytania