意思是,他们不必分发。我正在考虑使用memcached
或redis
。可能是后者。我关心的是"我们必须释放一些内存,所以我们会在它过期之前删除这个键/值。"事情。但是,我也可以接受其他建议。
答案 0 :(得分:0)
tl; dr 使用开发人员建议的ready-made solution。
所以,我决定不将memcached
用于此目的。因为它是一个缓存服务器。我没有看到一种方法来确保它不会删除我的密钥,因为它是内存不足。使用redis
只要maxmemory-policy = noeviction
就不会出现问题。
There are 3 links我想与您分享。它们基本上是我现在所知道的三种解决问题的方法。只要你有redis >= 2.6.0
即可。
如果您有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脚本是原子的(参见Atomicity of scripts):
$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);
我找到的另一个解决方案here。您只需使用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;
}
最后一个在documentation中再次描述。标有“留下历史原因”的说明。
这次我们存储锁定到期时刻的时间戳。我们用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>';
}