我们想要缓存一个非常昂贵的计算。所以我们做类似的事情:
my $result = $cache->get( $key );
unless ($result) {
$result = calculate( $key );
$cache->set( $key, $result, '10 minutes' );
}
return $result;
现在,在
calculate($key)
期间,在我们将结果存储到缓存中之前,还有其他几个请求进来,它们也开始运行 calculate($key)
,并且系统性能受到影响,因为许多进程都在计算相同的东西。想法:让我们在缓存中放置一个正在计算一个值的标志,所以其他请求只是等待那个计算完成,所以他们都使用它。就像是:
my $result = $cache->get( $key );
if ($result) {
while ($result =~ /Wait, \d+ is running calculate../) {
sleep 0.5;
$result = $cache->get( $key );
}
} else {
$cache->set( $key, "Wait, $$ is running calculate()", '10 minutes' );
$result = calculate( $key );
$cache->set( $key, $result, '10 minutes' );
}
return $result;
现在,这开辟了一个全新的蠕虫 jar 头。如果 $$ 在设置缓存之前死了怎么办。如果,如果...所有这些都可以解决,但是由于 CPAN 中没有任何内容这样做(CPAN 中的一切都有),我开始想:
有更好的方法吗?是否有特殊原因,例如Perl 的
Cache
和 Cache::Cache
类不提供这样的机制吗?有没有我可以使用的经过验证的真实模式?理想的情况是一个 CPAN 模块,它的 debian 包已经处于挤压状态或 eureka 时刻,在那里我看到了我的方式的错误...... :-)
编辑:我已经知道这被称为 Cache stampede并更新了问题的标题。
最佳答案
flock()
它。
由于您的工作进程都在同一个系统上,您可能可以使用良好的老式文件锁定来序列化昂贵的 calculate()
ionic 。作为奖励,这种技术出现在几个核心文档中。
use Fcntl qw(:DEFAULT :flock); # warning: this code not tested
use constant LOCKFILE => 'you/customize/this/please';
my $result = $cache->get( $key );
unless ($result) {
# Get an exclusive lock
my $lock;
sysopen($lock, LOCKFILE, O_WRONLY|O_CREAT) or die;
flock($lock, LOCK_EX) or die;
# Did someone update the cache while we were waiting?
$result = $cache->get( $key );
unless ($result) {
$result = calculate( $key );
$cache->set( $key, $result, '10 minutes' );
}
# Exclusive lock released here as $lock goes out of scope
}
return $result;
好处: worker 死亡会立即释放
$lock
.风险:LOCK_EX 可以永远阻塞,而且是很长的时间。避免 SIGSTOP,也许对
alarm()
感到满意.扩展:如果不想全部序列化
calculate()
调用,但只是所有调用相同的 $key
或一些 key ,您的 worker 可以 flock()
/some/lockfile.$key_or_a_hash_of_the_key
.
关于perl - 缓存和避免 Cache Stampedes - 多个同时计算,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10710587/