redis-rb multi仅在键设置时递增

时间:2015-05-11 17:11:14

标签: ruby-on-rails ruby ruby-on-rails-4 redis race-condition

我想在redis中存储一个计数。我想只在密钥存在时递增计数。我究竟做错了什么? exists返回false并且正在执行incr。

key = "blah"
result = REDIS_DB.multi do
  exists = REDIS_DB.exists(key)
  REDIS_DB.incr(key) if exists
end

# result: [false, 1]

我是redis的新手。我刚看了the redis transactions doc。根据我的理解,multi中的命令应该一个接一个地执行?

Rails 4.0.2,Redis 3.0.1,redis-rb(Redis的Ruby客户端库)

2 个答案:

答案 0 :(得分:1)

这可能就是我想要的:

result = REDIS_DB.watch(key) do
  if REDIS_DB.exists(key)
    REDIS_DB.incr(key)
  else
    REDIS_DB.unwatch
  end
end

答案 1 :(得分:1)

从redis 2.6开始支持lua脚本编写,它是transactional by definition,因此也可以使用以下代码。

redis_script = <<SCRIPT
    local exists = redis.call('exists', KEYS[1])
    if exists == 1 then
        return redis.call('incr', KEYS[1])
    end
SCRIPT

# returns incremented value if key exists otherwise nil
REDIS_DB.eval(redis_script, ['testkey']) 

详细了解scripting and eval command

为什么incr执行实际代码是因为

REDIS_DB块中的每个multi函数调用将返回Redis::Future对象而不是实际值,因为redis-rb将命令缓存在多块中。 e.g。

REDIS_DB.multi do 
    return_value = REDIS_DB.exists('tesstt')
    puts return_value.inspect
end

将打印

<Redis::Future [:exists, "tesstt"]>

现在,如果我们通过你的例子中的块

exists = REDIS_DB.exists(key) # returns Redis::Future object
REDIS_DB.incr(key) if exists  # evaluates to true as value of exists is an object

那么result: [false, 1]来自哪里。

这是因为REDIS_DB.multi产生你的块(并缓存命令)后最终将它转换为redis查询并将其发送到redis,最终运行它并返回结果。所以你的代码实际上转换成了以下查询。

MULTI
exists 'blah'
incr 'blah'
EXEC

并在一次调用redis时一起提交,返回0 existsredis-rb转换为布尔false)和1 incr

需要注意的是,这种行为是可以理解的,就好像你将每个命令单独发送到redis然后redis本身将在MULTI之后对所有内容进行排队,并在收到对exec的调用时对其进行处理