意思是,它们不必被分发。我正在考虑为此使用memcached
或redis
。可能是后者。我担心的是"我们必须释放一些内存,所以我们会在过期之前删除这个键/值"的事情。但我也愿意接受其他建议。
tl;dr 使用开发人员建议的现成解决方案。
所以,我决定不把memcached
用于这个目的。因为它是缓存服务器。我没有看到确保它不会删除我的密钥的方法,因为它内存不足。有了,redis
只要maxmemory-policy = noeviction
就不是问题.
我想与您分享3个链接。我现在知道,它们基本上是解决问题的 3 种方法。只要你有redis >= 2.6.0
就是这样。
红>= 2.6.12
如果你有redis >= 2.6.12
,你很幸运,可以简单地使用setnx
命令及其新选项ex
和nx
:
$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>';
}