40

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

使用上面的代码,我们将无法实现我们自己的 incr 命令,因为一旦调用了 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()

如果另一个客户端在我们调用 watch 后更改了 powerlevel 的值,我们的事务将失败。如果没有客户端更改该值,则该设置将起作用。我们可以循环执行这段代码,直到它工作。

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

4

1 回答 1

95

这里有几个问题。

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

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

# 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 命令的执行延迟到 exec。所以客户端只会把 GET 和 SET 命令堆积在内存中,一键执行,最后原子化执行。当然,将 current 分配给 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 确定值一致并发出成功信号。

在大多数情况下,如果“事务”足够快并且发生争用的概率很低,则更新非常有效。现在,如果存在争用,则必须对某些“事务”进行一些额外的操作(由于迭代和重试)。但数据将始终保持一致,不需要锁定。

于 2012-05-25T08:41:18.037 回答