Ruby-具有到期实现的基于Redis的互斥锁
我正在尝试使用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
但是,如果我需要具有超时机制的互斥锁(为了避免在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来解决这个问题。
回答:
如果您使用的是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 }
以上是 Ruby-具有到期实现的基于Redis的互斥锁 的全部内容, 来源链接: utcz.com/qa/402615.html