4

我正在尝试使用 Redis 实现一个基于内存的多进程共享互斥锁,它支持超时。

我需要互斥锁是非阻塞的,这意味着我只需要能够知道我是否能够获取互斥锁,如果不能 - 只需继续执行回退代码。

这些方面的东西:

if lock('my_lock_key', timeout: 1.minute)
  # Do some job
else
  # exit
end

可以使用 redis 实现一个未过期的互斥锁setnx mutex 1

if redis.setnx('#{mutex}', '1')
  # Do some job
  redis.delete('#{mutex}')
else
  # exit
end

但是,如果我需要一个具有超时机制的互斥锁怎么办(例如,为了避免 ruby​​ 代码在redis.delete命令之前失败,导致互斥锁永远被锁定,但不仅仅是因为这个原因)。

这样做显然是行不通的:

redis.multi do  
  redis.setnx('#{mutex}', '1')
  redis.expire('#{mutex}', key_timeout)
end

因为即使我无法设置互斥锁(setnx返回 0),我也会重新设置互斥锁的过期时间。

自然地,我希望有类似的东西setnxex,它可以原子地设置键的值和过期时间,但前提是键不存在。不幸的是,据我所知,Redis 不支持这一点。

但是,我确实做到了 find renamenx key otherkey,它允许您将一个键重命名为其他键,前提是另一个键不存在。

我想出了这样的东西(出于演示目的,我将其单独写下来,并没有将其分解为方法):

result = redis.multi do
  dummy_key = "mutex:dummy:#{Time.now.to_f}#{key}"
  redis.setex dummy_key, key_timeout, 0
  redis.renamenx dummy_key, key
end
if result.length > 1 && result.second == 1
  # do some job
  redis.delete key
else
  # exit
end

在这里,我设置了一个虚拟密钥的过期时间,并尝试将其重命名为真实密钥(在一个事务中)。

如果renamenx操作失败,那么我们无法获得互斥锁,但没有造成任何伤害:虚拟密钥将过期(可以通过添加一行代码选择立即删除),而真实密钥的过期时间将保持不变。

如果renamenx操作成功,那么我们就可以获取到互斥量,并且互斥量会得到想要的过期时间。

任何人都可以看到上述解决方案的任何缺陷吗?这个问题有更标准的解决方案吗?我真的很讨厌使用外部 gem 来解决这个问题......

4

2 回答 2

4

如果您使用的是 Redis 2.6+,则可以使用 Lua 脚本引擎更简单地完成此操作。Redis 文档说:

Redis 脚本在定义上是事务性的,所以你可以用 Redis 事务做的所有事情,你也可以用脚本做,通常脚本会更简单更快。

实现它很简单:

LUA_ACQUIRE = "return redis.call('setnx', KEYS[1], 1) == 1 and redis.call('expire', KEYS[1], KEYS[2]) and 1 or 0"
def lock(key, timeout = 3600)
  if redis.eval(LUA_ACQUIRE, key, timeout) == 1
    begin
      yield
    ensure
      r.del key
    end
  end
end

用法:

lock("somejob") { do_exclusive_job }
于 2013-07-25T08:23:02.793 回答
2

redis 2.6.12开始,您可以执行以下操作:redis.set(key, 1, nx: true, ex: 3600)实际上是SET key 1 NX EX 3600.

我受到 Chris 和 Mickey 解决方案的简单性的启发,并使用以下代码(以及一些功能和 rspec)创建了 gem - simple_redis_lock :

def lock(key, timeout)
  if @redis.set(key, Time.now, nx: true, px: timeout)
    begin
      yield
    ensure
      release key
    end
  end
end

我探索了其他一些很棒的选择:

  1. mlanett/redis-lock
  2. PatrickTulskie/redis-lock
  3. 莱德罗莫雷拉/redlock-rb
  4. dv/redis-信号量

但是他们有太多的阻塞特性来获取锁并且没有使用这个单一的SET KEY 1 NX EX 3600原子redis语句。

于 2016-10-12T05:29:56.273 回答