Вопрос по – Транзакции и смотреть заявление в Redis

30

Не могли бы вы объяснить мне следующий пример из "Маленькой книги Redis":

С помощью приведенного выше кода мы не сможем реализовать нашу собственную команду incr, поскольку все они выполняются вместе после вызова exec. Из кода мы не можем сделать:

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

Это не так, как транзакции Redis работают. Но, если мы добавим часы на уровень мощности, мы можем сделать:

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

Если другой клиент изменит значение powerlevel после того, как мы вызовем его, наша транзакция не будет выполнена. Если ни один клиент не изменит значение, набор будет работать. Мы можем выполнить этот код в цикле, пока он не заработает.

Почему мы не можем выполнить инкремент транзакции, который не может быть прерван другой командой? Почему нам нужно итерировать вместо этого и ждать, пока никто не изменит значениед транзакция начинается?

@ polvoazul, я знаю эту команду, спасибо. Это был обычный вопрос, не вызванный реальным делом. Marboni
Вы знаете о Прирастить Команда в Redis не так ли? Он делает именно то, что вы хотите в вашем примере, без использования транзакции. Конечно, это не ответ на сам вопрос, но тем не менее это стоит знать. polvoazul

Ваш Ответ

1   ответ
70

1) Почему мы не можем выполнить инкремент транзакции, который не может быть прерван другой командой?

Пожалуйста, обратите внимание, что «транзакции» Redis полностью отличаются от того, что большинство людей считают транзакциями в классической СУБД.

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

Вам нужно понимать, что выполняется на стороне сервера (в Redis), а что выполняется на стороне клиента (в вашем сценарии). В приведенном выше коде команды GET и SET будут выполняться на стороне Redis, но предполагается, что присвоение current и вычисление current + 1 будут выполняться на стороне клиента.

Чтобы гарантировать атомарность, блок MULTI / EXEC задерживает выполнение команд Redis до исполнения. Таким образом, клиент будет только накапливать команды GET и SET в памяти и выполнять их одним выстрелом и атомарно в конце. Конечно, попытка присвоить ток результату GET и приращению произойдет задолго до этого. На самом деле метод redis.get будет возвращать только строку «QUEUED», указывающую на задержку выполнения команды, и увеличение не будет работать.

В блоках MULTI / EXEC вы можете использовать только те команды, параметры которых могут быть полностью известны до начала блока. Вы можете прочитать документация за дополнительной информацией

2) Почему мы должны вместо этого повторять и ждать, пока никто не изменит значение, прежде чем транзакция начнется?

Это пример одновременной оптимистичной картины.

Если бы мы не использовали WATCH / MULTI / EXEC, у нас было бы потенциальное состояние гонки:

# 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

Теперь давайте добавим блок WATCH / MULTI / EXEC. С предложением WATCH команды между MULTI и EXEC выполняются, только если значение не изменилось.

# 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.

Так что вам не нужно повторять, чтобы подождать, пока никто не изменит значение, а пытаться выполнить операцию снова и снова, пока Redis не удостоверится, что значения согласованы и не сигнализирует об успешност

В большинстве случаев, если «транзакции» выполняются достаточно быстро и вероятность возникновения разногласий невелика, обновления очень эффективны. Теперь, если есть конфликт, некоторые дополнительные операции должны быть выполнены для некоторых «транзакций» (из-за итерации и повторных попыток). Но данные всегда будут согласованы и блокировка не требуется.

Отличное объяснение, большое спасибо, наконец-то я понял! После понимания пункта 1, пункт 2 становится очевидным. Marboni
Так есть ли в этом "справедливость"? Что делать, если я сделал «часы», но это не удалось? Тогда я должен повторить попытку. Что если бы возникли разногласия - могу ли я когда-либо пытаться вечно, не «взяв замок»? Brad
Так что, я думаю, я понимаю из вашего примера - что если бы вы показали пример, который выполнял MULTI / EXEC, но не имел WATCH, обе сессии могли бы прочитать значение 10, а Redis записал бы обратно 11. Это сделало бы обратную запись «атомарной», но не защитит ли клиент от чтения и приращения того же значения локально, что приведет к неправильному ответу? Brad
Нет гарантии справедливости. Однако на практике нередко бывает, что клиент голодает при исполнении watch / multi / exec. Didier Spezia
Второй вопрос: да, точно. Didier Spezia

Похожие вопросы