Redis 中的事务和观察语句 [英] Transactions and watch statement in Redis

查看:47
本文介绍了Redis 中的事务和观察语句的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

能否请您解释一下The Little Redis Book"中的示例:

<块引用>

使用上面的代码,我们将无法实现我们自己的 incr命令,因为一旦调用 exec ,它们就会一起执行.从代码,我们做不到:

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

<块引用>

Redis 事务不是这样工作的.但是,如果我们添加一个手表powerlevel,我们可以做到:

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

<块引用>

如果另一个客户端在我们调用后更改了 powerlevel 的值注意,我们的交易将失败.如果没有客户端更改值,该集合将起作用.我们可以循环执行这段代码,直到它有效.

为什么我们不能在不能被其他命令中断的事务中执行增量?为什么我们需要迭代并等到没有人更改值事务开始之前?

解决方案

这里有几个问题.

1) 为什么我们不能在不能被其他命令中断的事务中执行增量?

首先请注意,Redis事务"与大多数人认为的经典 DBMS 中的事务完全不同.

# 不起作用redis.multi()current = redis.get('powerlevel')redis.set('powerlevel', 当前 + 1)redis.exec()

您需要了解在服务器端(在 Redis 中)执行的内容以及在客户端(在您的脚本中)执行的内容.上面代码中,GET和SET命令会在Redis端执行,但是分配给current和计算current+1应该在客户端执行.

为了保证原子性,MULTI/EXEC 块将 Redis 命令的执行延迟到 exec.所以客户端只会把GET和SET命令堆在内存里,一枪执行,最后原子化.当然,将 current 分配给 GET 和增量的结果的尝试将在很早之前发生.实际上 redis.get 方法只会返回字符串QUEUED"来表示命令延迟,并且增量不起作用.

在 MULTI/EXEC 块中,您只能使用在块开始之前可以完全知道参数的命令.您可能需要阅读文档以获取更多信息.

2) 为什么我们需要迭代并等到交易开始之前没有人更改值?

这是并发乐观模式的一个例子.

如果我们不使用 WATCH/MULTI/EXEC,就会出现潜在的竞争条件:

# 初始任意值功率等级 = 10会话 A:获取功率级别 ->10会话 B:获取功率级别 ->10会话 A:当前 = 10 + 1会话 B:当前 = 10 + 1会话 A:设置 powerlevel 11会话 B:设置 powerlevel 11# 最后我们有 11 而不是 12 ->错误的

现在让我们添加一个 WATCH/MULTI/EXEC 块.使用 WATCH 子句,MULTI 和 EXEC 之间的命令仅在值未更改时执行.

# 初始任意值功率等级 = 10会话 A:WATCH powerlevel会话 B:WATCH powerlevel会话 A:获取功率级别 ->10会话 B:获取功率级别 ->10会话 A:当前 = 10 + 1会话 B:当前 = 10 + 1会话 A:多会话 B:多会话 A:设置功率级别 11 ->排队会话 B:设置功率级别 11 ->排队会话 A:执行 ->成功!powerlevel 现在是 11会话 B:EXEC ->失败,因为 powerlevel 已更改并已被监视# 最后,我们有 11 个,会话 B 知道它必须再次尝试事务# 希望这次能正常工作.

因此,您不必反复等待直到没有人更改该值,而是要一次又一次地尝试操作,直到 Redis 确定这些值一致并发出成功信号为止.

在大多数情况下,如果事务"足够快并且发生争用的可能性很低,则更新非常有效.现在,如果存在争用,则必须为某些事务"(由于迭代和重试)执行一些额外的操作.但数据将始终保持一致,无需锁定.

Could you please explain me following example from "The Little Redis Book":

With the code above, we wouldn't be able to implement our own incr command since they are all executed together once exec is called. From code, we can't do:

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

That isn't how Redis transactions work. But, if we add a watch to powerlevel, we can do:

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

If another client changes the value of powerlevel after we've called watch on it, our transaction will fail. If no client changes the value, the set will work. We can execute this code in a loop until it works.

Why we can't execute increment in transaction that can't be interrupted by other command? Why we need to iterate instead and wait until nobody changes value before transaction starts?

解决方案

There are several questions here.

1) Why we can't execute increment in transaction that can't be interrupted by other command?

Please note first that Redis "transactions" are completely different than what most people think transactions are in classical DBMS.

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

You need to understand what is executed on server-side (in Redis), and what is executed on client-side (in your script). In the above code, the GET and SET commands will be executed on Redis side, but assignment to current and calculation of current + 1 are supposed to be executed on client side.

To guarantee atomicity, a MULTI/EXEC block delays the execution of Redis commands until the exec. So the client will only pile up the GET and SET commands in memory, and execute them in one shot and atomically in the end. Of course, the attempt to assign current to the result of GET and incrementation will occur well before. Actually the redis.get method will only return the string "QUEUED" to signal the command is delayed, and the incrementation will not work.

In MULTI/EXEC blocks you can only use commands whose parameters can be fully known before the begining of the block. You may want to read the documentation for more information.

2) Why we need to iterate instead and wait until nobody changes value before transaction starts?

This is an example of concurrent optimistic pattern.

If we used no WATCH/MULTI/EXEC, we would have a potential race condition:

# 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

Now let's add a WATCH/MULTI/EXEC block. With a WATCH clause, the commands between MULTI and EXEC are executed only if the value has not changed.

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

So you do not have to iterate to wait until nobody changes the value, but rather to attempt the operation again and again until Redis is sure the values are consistent and signals it is successful.

In most cases, if the "transactions" are fast enough and the probability to have contention is low, the updates are very efficient. Now, if there is contention, some extra operations will have to be done for some "transactions" (due to the iteration and retries). But the data will always be consistent and no locking is required.

这篇关于Redis 中的事务和观察语句的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆