如何实现过期的非分布式锁



意思是,它们不必被分发。我正在考虑为此使用memcachedredis。可能是后者。我担心的是"我们必须释放一些内存,所以我们会在过期之前删除这个键/值"的事情。但我也愿意接受其他建议。

tl;dr 使用开发人员建议的现成解决方案。

所以,我决定不把memcached用于这个目的。因为它是缓存服务器。我没有看到确保它不会删除我的密钥的方法,因为它内存不足。有了,redis只要maxmemory-policy = noeviction就不是问题.

我想与您分享3个链接。我现在知道,它们基本上是解决问题的 3 种方法。只要你有redis >= 2.6.0就是这样。

红>= 2.6.12

如果你有redis >= 2.6.12,你很幸运,可以简单地使用setnx命令及其新选项exnx

$redis->set($name, <whatever>, array('nx', 'ex' => $ttl));

但是我们不能最终删除锁,如果我们要允许关键部分花费更长的时间,那么我们预期(>= ttl(。请考虑以下情况:

C1 acquires the lock
lock expires
C2 acquires the lock
C1 deletes C2's lock

为了不发生这种情况,我们将当前时间戳存储为锁的值。然后,知道 Lua 脚本是原子的(参见脚本的原子性(:

$redis->eval('
    if redis.call("get", KEYS[1]) == KEYS[2] then
        redis.call("del", KEYS[1])
    end
', array($name, $now));

但是,两个客户端是否可以具有相等的now值?为此,上述所有操作都应在一秒钟内发生,并且ttl必须等于 0

生成的代码:

function get_redis() {
    static $redis;
    if ( ! $redis) {
        $redis = new Redis;
        $redis->connect('127.0.0.1');
    }
    return $redis;
}
function acquire_lock($name, $ttl) {
    if ( ! $ttl)
        return FALSE;
    $redis = get_redis();
    $now = time();
    $r = $redis->set($name, $now, array('nx', 'ex' => $ttl));
    if ( ! $r)
        return FALSE;
    $lock = new RedisLock($redis, $name, $now);
    register_shutdown_function(function() use ($lock) {
        $r = $lock->release();
        # if ( ! $r) {
            # Here we can log the fact that lock has expired too early
        # }
    });
    return $lock;
}
class RedisLock {
    var $redis;
    var $name;
    var $now;
    var $released;
    function __construct($redis, $name, $now) {
        $this->redis = get_redis();
        $this->name = $name;
        $this->now = $now;
    }
    function release() {
        if ($this->released)
            return TRUE;
        $r = $this->redis->eval('
            if redis.call("get", KEYS[1]) == KEYS[2] then
                redis.call("del", KEYS[1])
                return 1
            else
                return 0
            end
        ', array($this->name, $this->now));
        if ($r)
            $this->released = TRUE;
        return $r;
    }
}
$l1 = acquire_lock('l1', 4);
var_dump($l1 ? date('H:i:s', $l1->expires_at) : FALSE);
sleep(2);
$l2 = acquire_lock('l1', 4);
var_dump($l2 ? date('H:i:s', $l2->expires_at) : FALSE);   # FALSE
sleep(4);
$l3 = acquire_lock('l1', 4);
var_dump($l3 ? date('H:i:s', $l3->expires_at) : FALSE);

到期

我在这里找到的另一个解决方案。您只需使用以下命令使值过期expire

$redis->eval('
    local r = redis.call("setnx", ARGV[1], ARGV[2])
    if r == 1 then
        redis.call("expire", ARGV[1], ARGV[3])
    end
', array($name, $now, $ttl));

因此,只有acquire_lock函数更改:

function acquire_lock($name, $ttl) {
    if ( ! $ttl)
        return FALSE;
    $redis = get_redis();
    $now = time();
    $r = $redis->eval('
        local r = redis.call("setnx", ARGV[1], ARGV[2])
        if r == 1 then
            redis.call("expire", ARGV[1], ARGV[3])
        end
        return r
    ', array($name, $now, $ttl));
    if ( ! $r)
        return FALSE;
    $lock = new RedisLock($redis, $name, $now);
    register_shutdown_function(function() use ($lock) {
        $r = $lock->release();
        # if ( ! $r) {
            # Here we can log that lock as expired too early
        # }
    });
    return $lock;
}

获取集

最后一个在文档中再次描述。标有"因历史原因留下"的注释。

这次我们存储锁到期时刻的时间戳。我们使用setnx命令存储它。如果成功,我们就获得了锁。否则,要么是其他人持有锁,要么锁已过期。无论是后者,我们使用getset来设置新值,如果旧值没有改变,我们就获得了锁:

$r = $redis->setnx($name, $expires_at);
if ( ! $r) {
    $cur_expires_at = $redis->get($name);
    if ($cur_expires_at > time())
        return FALSE;
    $cur_expires_at_2 = $redis->getset($name, $expires_at);
    if ($cur_expires_at_2 != $cur_expires_at) 
        return FALSE;
}

让我感到不舒服的是,我们似乎改变了别人的expires_at价值,不是吗?

附带说明一下,您可以检查您以这种方式使用哪种redis

function get_redis_version() {
    static $redis_version;
    if ( ! $redis_version) {
        $redis = get_redis();
        $info = $redis->info();
        $redis_version = $info['redis_version'];
    }
    return $redis_version;
}
if (version_compare(get_redis_version(), '2.6.12') >= 0) {
    ...
}

一些调试函数:

function redis_var_dump($keys) {
    foreach (get_redis()->keys($keys) as $key) {
        $ttl = get_redis()->ttl($key);
        printf("%s: %s%s%s", $key, get_redis()->get($key),
            $ttl >= 0 ? sprintf(" (ttl: %s)", $ttl) : '',
            nl());
    }
}
function nl() {
    return PHP_SAPI == 'cli' ? "n" : '<br>';
}

相关内容

  • 没有找到相关文章

最新更新