【发布时间】:2013-07-24 22:57:40
【问题描述】:
我正在尝试使用 Redis 实现基于内存的多进程共享互斥锁,它支持超时。
我需要互斥锁是非阻塞的,这意味着我只需要能够知道我是否能够获取互斥锁,如果不能 - 只需继续执行回退代码。
类似的东西:
if lock('my_lock_key', timeout: 1.minute)
# Do some job
else
# exit
end
一个un-expiring mutex可以使用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 不支持这一点。
但我找到了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 来解决这个问题......
【问题讨论】:
标签: ruby multithreading locking redis mutex