我正在尝试使用 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 来解决这个问题......