Pergunta sobre redis – Transações e declaração de relógio no Redis

30

Você poderia por favor me explicar seguindo o exemplo do "The Little Redis Book":

Com o código acima, não poderíamos implementar nosso próprio comando incr, já que todos eles são executados juntos assim que exec é chamado. Do código, não podemos fazer:

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

Não é assim que as transações do Redis funcionam. Mas, se adicionarmos um relógio ao powerlevel, podemos fazer:

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

Se outro cliente alterar o valor do powerlevel após chamarmos o relógio, nossa transação falhará. Se nenhum cliente alterar o valor, o conjunto funcionará. Podemos executar este código em um loop até que ele funcione.

Por que não podemos executar um incremento na transação que não pode ser interrompido por outro comando? Por que precisamos fazer uma iteração e esperar até que ninguém mude o valorantes transação começa?

Você está ciente doincr comand em redis certo? Ele faz exatamente o que você quer no seu exemplo, sem usar uma transação. Claro que isso não é uma resposta para a pergunta em si, mas, no entanto, vale a pena conhecer. polvoazul
@polvoazul, eu conheço este comando, obrigado. Foi uma questão comum não causada por caso real. Marboni

Sua resposta

1   a resposta
70

Existem várias perguntas aqui.

1) Por que não podemos executar incremento na transação que não pode ser interrompido por outro comando?

Observe primeiro que as "transações" do Redis são completamente diferentes do que a maioria das pessoas acha que as transações são no DBMS clássico.

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

Você precisa entender o que é executado no lado do servidor (no Redis) e o que é executado no lado do cliente (no seu script). No código acima, os comandos GET e SET serão executados no lado do Redis, mas a atribuição à corrente e o cálculo da corrente + 1 devem ser executados no lado do cliente.

Para garantir a atomicidade, um bloco MULTI / EXEC atrasa a execução dos comandos do Redis até o exec. Assim, o cliente só acumulará os comandos GET e SET na memória e os executará em um único disparo e atomicamente no final. Naturalmente, a tentativa de atribuir corrente ao resultado de GET e incrementação ocorrerá bem antes. Na verdade, o método redis.get retornará apenas a string "QUEUED" para sinalizar que o comando está atrasado, e a incremento não funcionará.

Nos blocos MULTI / EXEC, você só pode usar comandos cujos parâmetros possam ser totalmente conhecidos antes do início do bloco. Você pode querer lera documentação Para maiores informações.

2) Por que precisamos fazer uma iteração e esperar até que ninguém mude o valor antes do início da transação?

Este é um exemplo de padrão otimista simultâneo.

Se não usássemos WATCH / MULTI / EXEC, teríamos uma possível condição de corrida:

# 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

Agora vamos adicionar um bloco WATCH / MULTI / EXEC. Com uma cláusula WATCH, os comandos entre MULTI e EXEC são executados apenas se o valor não tiver sido alterado.

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

Portanto, você não precisa repetir até que ninguém altere o valor, mas tente executar a operação várias vezes até que Redis tenha certeza de que os valores estão consistentes e sinaliza que ela é bem-sucedida.

Na maioria dos casos, se as "transações" são rápidas o suficiente e a probabilidade de ter contenção é baixa, as atualizações são muito eficientes. Agora, se houver contenção, algumas operações extras terão que ser feitas para algumas "transações" (devido à iteração e novas tentativas). Mas os dados serão sempre consistentes e nenhum bloqueio será necessário.

@DidierSpezia Posso fazer o WATCH depois de GET? Não vejo motivo para começar a ver a chave enquanto lê o valor mtkachenko
Então, existe alguma "justiça" nisso? E se eu fizesse o "relógio", mas falhou? Então eu tenho que tentar novamente. E se houvesse contenção - eu poderia tentar para sempre sem "pegar a fechadura"? Brad
Nenhuma garantia de justiça. No entanto, na prática, ter um cliente faminto em uma execução de relógio / multi / exec não é tão comum. Didier Spezia
@DidierSpezia: Temos que assistirpowerlevel novamente na sessão B? Ou apenas repita a parte da transação? cold_coder

Perguntas relacionadas