如何在 Ruby 的 MULTI 块中读取 Redis?
Posted
技术标签:
【中文标题】如何在 Ruby 的 MULTI 块中读取 Redis?【英文标题】:How can I read from Redis inside a MULTI block in Ruby? 【发布时间】:2012-07-03 07:08:14 【问题描述】:我在 MULTI 事务中封装了一组复杂的 Redis 命令,但事务中的逻辑取决于 Redis 中已有的值。但是事务中的所有读取似乎都返回nil
这是一个演示问题的示例:
[Dev]> $redis.set("foo", "bar")
=> "OK"
[Dev]> $redis.multi $redis.set("foo", "baz") if $redis.get("foo") == "bar"
=> ["bar"]
[Dev]> $redis.get("foo")
=> "bar"
显然我希望最后一个返回值是'baz'
——我怎样才能做到这一点?
【问题讨论】:
恐怕你做不到。 【参考方案1】:您不能,因为所有命令(包括 get)实际上都是在 exec 时执行的。在这种情况下,get 命令只返回一个未来对象,而不是实际值。
有两种方式来实现这样的交易。
使用 WATCH 子句
watch 子句用于防止并发更新。如果变量的值在 watch 和 multi 子句之间更新,则不会应用 multi 块中的命令。由客户决定再次尝试交易。
loop do
$redis.watch "foo"
val = $redis.get("foo")
if val == "bar" then
res = $redis.multi do |r|
r.set("foo", "baz")
end
break if res
else
$redis.unwatch "foo"
break
end
end
这里的脚本有点复杂,因为块的内容可以是空的,所以没有简单的方法可以知道交易是否被取消,或者它是否根本没有发生。除非交易被取消,否则在所有情况下多块返回结果通常会更容易。
使用 Lua 服务器端脚本
使用 Redis 2.6 或更高版本,Lua scripts can be executed on the server。整个脚本的执行是原子的。它可以在 Ruby 中轻松实现:
cmd = <<EOF
if redis.call('get',KEYS[1]) == ARGV[1] then
redis.call('set',KEYS[1],ARGV[2] )
end
EOF
$redis.eval cmd, 1, "foo", "bar", "baz"
这通常比使用 WATCH 子句简单得多。
【讨论】:
在使用 Lua 而不是 WATCH 命令或反之亦然时是否有任何性能权衡? Lua 更快,因为:1- 它避免了客户端和 redis 服务器之间的多次往返;2- 您不必循环来覆盖潜在的故障 然后 Lua 的缺点是它会锁定 Redis 在脚本执行期间无法接收其他命令,对吗? 是的,任何 Lua 脚本都是以原子方式执行的——在处理 Lua 脚本时不能处理其他命令。【参考方案2】:正如 Sergio 在他的评论中所说,您不能像在 Redis 中那样选择性地执行 MULTI
块。见documentation on transactions:
要么处理所有命令,要么不处理。
但是,您可以使用 WATCH
来使用 check-and-set(伪代码)实现乐观锁定:
SET foo bar
WATCH foo
$foo = GET foo
MULTI
if $foo == 'bar'
SET foo baz
EXEC
GET foo
使用WATCH
,只有在被监视的键没有改变的情况下才会执行事务。如果改了watch键,EXEC
会失败,你可以重试。
另一种可能性是使用脚本功能,但这仅在 2.6 候选版本中可用。
【讨论】:
以上是关于如何在 Ruby 的 MULTI 块中读取 Redis?的主要内容,如果未能解决你的问题,请参考以下文章
Cloudera Impala:它如何从 HDFS 块中读取数据?