7

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

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 中的所有东西都有),我开始想知道:

有更好的方法吗?是否有特殊原因,例如 PerlCacheCache::Cache类不提供这样的机制?有没有我可以使用的经过验证的真实模式?

理想的情况是一个 CPAN 模块,它的 debian 包已经处于挤压状态或 eureka 时刻,在那里我看到了我的方式的错误...... :-)

编辑:我已经知道这被称为缓存踩踏,并更新了问题的标题。

4

4 回答 4

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 可以永远阻塞,而且是很长的时间。避免 SIGSTOPs,也许对alarm().

扩展:如果您不想序列化所有calculate()调用,而只是对相同$key或某组键的所有调用,则您的工作人员可以flock() /some/lockfile.$key_or_a_hash_of_the_key.

于 2012-05-23T15:43:33.030 回答
1

使用锁?或者这可能是一个矫枉过正?或者如果可能的话,离线预先计算结果然后在线使用?

于 2012-05-22T23:53:36.080 回答
1

尽管对于您的用例可能(或可能不会)过度杀伤,但您是否考虑过使用消息队列进行处理? RabbitMQ目前似乎是 Perl 社区中的一个流行选择,并且通过AnyEvent::RabbitMQ模块得到支持。

在这种情况下,基本策略是在需要calculate新密钥时向消息队列提交请求。如果这是您可以可靠处理的全部,则队列可以一次设置为calculate一个键(按请求的顺序)。或者,如果您可以安全地同时计算多个密钥,队列也可以用于合并对同一密钥的多个请求,计算一次并将结果返回给请求该密钥的所有客户端。

当然,这会增加一些复杂性,并且 AnyEvent 需要一种与您可能习惯的编程风格有些不同的编程风格(我会提供一个示例,但我自己从未真正掌握它),但它可能会提供在效率和可靠性方面获得足够的收益,以使这些成本物有所值。

于 2012-05-23T13:40:27.957 回答
-1

我普遍同意上述pilcrow的方法。我要添加一件事:调查memoize()函数的使用以潜在地加速calculate()代码中的操作。

有关详细信息,请参阅http://perldoc.perl.org/Memoize.html

于 2012-05-28T07:12:23.320 回答