Ruby-具有到期实现的基于Redis的互斥锁 [英] Ruby - Redis based mutex with expiration implementation

查看:168
本文介绍了Ruby-具有到期实现的基于Redis的互斥锁的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用Redis实现基于内存的多进程共享互斥体,该互斥体支持超时.

I'm trying to implement a memory based, multi process shared mutex, which supports timeout, using Redis.

我需要互斥锁是非阻塞的,这意味着我只需要知道我是否能够获取互斥锁,如果不能,则只需继续执行后备代码即可.

I need the mutex to be non-blocking, meaning that I just need to be able to know if I was able to fetch the mutex or not, and if not - simply continue with execution of fallback code.

遵循这些原则:

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

可以使用Redis的setnx mutex 1来实现未过期的互斥体:

An un-expiring mutex could be implemented using redis's setnx mutex 1:

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

但是如果我需要具有超时机制的互斥锁(为了避免在redis.delete命令之前红宝石代码失败,例如,导致互斥锁被永久锁定的情况,但并非仅出于此原因)

But what if I need a mutex with a timeout mechanism (In order to avoid a situation where the ruby code fails before the redis.delete command, resulting the mutex being locked forever, for example, but not for this reason only).

做这样的事情显然是行不通的:

Doing something like this obviously doesn't work:

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

因为我无法设置互斥锁,所以我要重新设置互斥锁的过期时间(setnx返回0).

since I'm re-setting an expiration to the mutex EVEN if I wasn't able to set the mutex (setnx returns 0).

自然,我希望有类似setnxex这样的东西,它可以自动设置具有到期时间的键值,但前提是该键尚不存在.不幸的是,据我所知,Redis不支持此功能.

Naturally, I would've expected to have something like setnxex which atomically sets a key's value with an expiration time, but only if the key does not exist already. Unfortunately, Redis does not support this as far as I know.

但是,我确实找到了renamenx key otherkey,它可以让您仅在另一个键不存在的情况下将其重命名为其他键.

I did however, find renamenx key otherkey, which lets you rename a key to some other key, only if the other key does not already exist.

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

I came up with something like this (for demonstration purposes, I wrote it down monolithically, and didn't break it down to methods):

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

在这里,我正在为虚拟密钥设置到期时间,并尝试将其重命名为真实密钥(在一次交易中).

Here, i'm setting an expiration for a dummy key, and try to rename it to the real key (in one transaction).

如果renamenx操作失败,则我们将无法获取互斥体,但不会造成任何危害:虚拟密钥将过期(可以选择添加一行代码立即将其删除),而真实密钥到期时间将保持不变.

If the renamenx operation fails, then we weren't able to obtain the mutex, but no harm done: the dummy key will expire (it can be optionally deleted immediately by adding one line of code) and the real key's expiration time will remain intact.

如果renamenx操作成功,则我们可以获得互斥量,并且该互斥量将获得所需的到期时间.

If the renamenx operation succeeded, then we were able to obtain the mutex, and the mutex will get the desired expiration time.

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

Can anyone see any flaw with the above solution? Is there a more standard solution for this problem? I would really hate using an external gem in order to solve this problem...

推荐答案

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

If you're using Redis 2.6+, you can do this much more simply with the Lua scripting engine. The Redis documentation says:

Redis脚本在定义上是事务性的,因此,您可以使用Redis事务进行的所有操作,也可以使用脚本进行的操作,通常该脚本会更简单,更快捷.

A Redis script is transactional by definition, so everything you can do with a Redis transaction, you can also do with a script, and usually the script will be both simpler and faster.

实现起来很简单:

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的互斥锁的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆