缓存&避免Cache Stampedes - 多个同时计算

时间:2012-05-22 21:40:07

标签: perl caching

我们有一个非常昂贵的计算,我们想缓存。所以我们做类似的事情:

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的CacheCache::Cache类没有提供这样的机制?我可以使用一种经过验证的真实模式吗?

理想的是一个带有debian软件包的CPAN模块已经处于挤压或尤里卡时刻,在那里我看到了我的方式错误......: - )

编辑:我从那时起就知道这被称为Cache stampede,并更新了问题的标题。

4 个答案:

答案 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