我们有一个非常昂贵的计算,我们想缓存。所以我们做类似的事情:
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;
现在开辟了一整套新的蠕虫病毒。如果$$在设置缓存之前死亡,该怎么办?如果......如果......所有这些都可以解决,但由于CPAN中没有任何内容可以做到这一点(CPAN中有一些内容用于所有内容),我开始想知道:
有更好的方法吗?是否有特殊原因,例如Perl的Cache
和Cache::Cache
类没有提供这样的机制?我可以使用一种经过验证的真实模式吗?
理想的是一个带有debian软件包的CPAN模块已经处于挤压或尤里卡时刻,在那里我看到了我的方式错误......: - )
编辑:我从那时起就知道这被称为Cache stampede,并更新了问题的标题。
答案 0 :(得分:2)
flock()
它。
由于您的工作进程都在同一系统上,因此您可以使用良好的旧式文件锁定来序列化昂贵的calculate()
离子。作为奖励,这种技术出现在几个核心文档中。
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;
好处:工人死亡将立即释放$lock
。
风险:LOCK_EX可以永久阻止,这是很长一段时间。避免使用SIGSTOP,或许对alarm()
感到满意。
扩展程序:如果您不希望序列化所有calculate()
次来电,而只是要求拨打同一$key
或某组密钥,则您的工作人员可以flock()
{ {1}}。
答案 1 :(得分:1)
使用锁?或者那可能是一种矫枉过正?或者如果可能的话,离线预先计算结果然后在线使用?
答案 2 :(得分:1)
虽然您的用例可能(或可能不是)过度,但您是否考虑过使用消息队列进行处理? RabbitMQ目前在Perl社区中似乎是一个受欢迎的选择,它通过AnyEvent::RabbitMQ模块得到支持。
在这种情况下,基本策略是在需要calculate
新密钥时向消息队列提交请求。然后可以将队列一次只设置为calculate
一个(按请求的顺序),如果这是您可以可靠处理的全部。或者,如果您可以安全地同时计算多个密钥,则该队列还可用于合并对同一密钥的多个请求,计算一次并将结果返回给请求该密钥的所有客户端。
当然,这会增加一些复杂性,而AnyEvent会调用一种与你习惯的编程风格略有不同的编程风格(我会提供一个例子,但我自己从来没有真正掌握它),但是它可以提供足够的效率和可靠性,使这些成本值得你花时间。
答案 3 :(得分:-1)
我一般同意上面的pilcrow方法。我想补充一点:调查使用memoize()
函数来加速代码中的calculate()
操作。
有关详细信息,请参阅http://perldoc.perl.org/Memoize.html