Perl 高速缓存&;避免缓存踩踏-多个同时计算

Perl 高速缓存&;避免缓存踩踏-多个同时计算,perl,caching,Perl,Caching,我们有一个非常昂贵的计算,我们想缓存。所以我们做了一些类似的事情: my $result = $cache->get( $key ); unless ($result) { $result = calculate( $key ); $cache->set( $key, $result, '10 minutes' ); } return $result; 现在,在calculate($key)过程中,在我们将结果存储到缓存之前,其他几个请求进入,它们也开始运行ca

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

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中有一些东西可以解决所有问题),我开始怀疑:

有更好的方法吗?是否有特殊原因,例如Perl的
Cache
Cache::Cache
类不提供这样的机制?是否有一种经过验证的模式可以替代

理想的情况是一个CPAN模块,其debian软件包已经处于压缩状态,或者是一个eureka时刻,在这里我看到了我的错误…:-)


编辑:我知道这叫做a,并更新了问题的标题。

使用锁?或者这可能是一种过度的杀伤力?或者,如果可能的话,离线预计算结果,然后在线使用?

尽管对于您的用例来说,这可能(也可能不会)是过分的,但您是否考虑过使用消息队列进行处理?目前似乎是Perl社区中的一个流行选择,它通过模块得到支持

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

当然,这会增加一点复杂性,而且任何事件都需要一种与您可能习惯的编程风格有所不同的编程风格(我会提供一个例子,但我自己从来没有真正掌握过它的诀窍),但它可以在效率和可靠性方面提供足够的收益,使这些成本值得您花费。

flock()
it

由于您的工作进程都在同一个系统上,因此您可能可以使用良好的老式文件锁定来序列化昂贵的
calculate()
ion。作为奖励,这种技术出现在一些核心文档中

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可能会永远阻塞,这是一段很长的时间。避免信号停止,也许可以适应
报警()


扩展名:如果您不想序列化所有
calculate()
调用,而只想序列化相同
$key
或某组键的所有调用,那么您的工作人员可以
flock()
/some/lockfile.\u key的$key\u或\u hash\u
我大体上同意皮尔克罗的上述方法。我想补充一点:研究
memoize()
函数的使用,以潜在地加速代码中的
calculate()
操作


有关详细信息,请参见

为SysV共享内存提供OO接口。它有点类似于提供独占锁的缓存。这有一个和一个关于策略。还有一个策略。我担心会发生什么,例如,在工人死亡期间。当$lock超出范围时,这里发布的
#独占锁非常漂亮!谢谢同意,在
flock()
合适的地方,它非常漂亮。所有的魔力都在底层的文件描述符中,进程死亡和perl在作用域结束时对文件句柄的销毁的最后一次引用都完全符合我们的需要。这无疑也是一条有价值的途径。但我认为,它需要融入一个更大的图景,而我们还没有做好准备。感谢您的提醒。Memoize可以在一个过程中正常工作。正如OP中提到的,这个问题涉及到多个进程,所有进程都希望计算相同的东西。除非我误解了什么,否则Memoize不会使缓存的值可用于不同的进程,因此在本文中没有任何用处。是的,它可以使用绑定散列,但是我猜它会遇到OP中的问题。