我正在尝试使用Redis实现基于内存的多进程共享互斥体,该互斥体支持超时。
我需要互斥锁是非阻塞的,这意味着我只需要能够知道是否能够获取互斥锁,如果不能,则只需继续执行后备代码即可。
遵循以下原则:
if lock('my_lock_key', timeout: 1.minute) # Do some job else # exit end
一个 未到期的互斥 可以使用Redis的的实现setnx mutex 1:
setnx mutex 1
if redis.setnx('#{mutex}', '1') # Do some job redis.delete('#{mutex}') else # exit end
但是,如果我需要具有超时机制的互斥锁(为了避免在redis.delete命令之前红宝石代码失败,导致互斥锁被永久锁定的情况,例如,但并非仅出于此原因)。
redis.delete
做这样的事情显然是行不通的:
redis.multi do redis.setnx('#{mutex}', '1') redis.expire('#{mutex}', key_timeout) end
因为即使我无法设置互斥锁,我也会重新设置该互斥锁的过期时间(setnx返回0)。
setnx
自然,我本来希望拥有类似的功能setnxex,即自动使用到期时间设置键的值,但前提是该键尚不存在。不幸的是,据我所知,Redis不支持此功能。
setnxex
但是,我确实找到了find renamenx key otherkey,这使您可以将一个键重命名为另一个键,前提是另一个键不存在。
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
如果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 }