Подтвердить что ты не робот

Сделки и выписки в Redis

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

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

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

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

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

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

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

4b9b3361

Ответ 1

Здесь есть несколько вопросов.

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

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

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

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

Чтобы гарантировать атомарность, блок MULTI/EXEC задерживает выполнение команд Redis до exec. Таким образом, клиент будет накапливать команды 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 не будет уверен в том, что значения согласованы и сигнализирует об успешном завершении.

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