2012-05-22 125 views
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東西一切),我開始懷疑:

有沒有更好的方法嗎?是否有特殊原因,例如Perl的CacheCache::Cache類不提供這樣的機制?有沒有一個我可以使用的嘗試和真正的模式?

理想的情況是一個CPAN模塊與Debian軟件包已經在擠壓或靈光一現,在這裏我看到了我的方式錯誤... :-)

編輯:我後來瞭解到,這就是所謂的一個Cache stampede並更新了問題的標題。

+0

[IPC :: ShareLite](http://search.cpan.org/~andya/IPC-ShareLite-0.17/lib/IPC/ShareLite.pm)提供的SysV OO接口共享內存。它與** Cache **類似,提供排他鎖。 – tuxuday

+0

有[緩存踩踏 - 維基百科文章](https://en.wikipedia.org/wiki/Cache_stampede)和[Perl緩存討論>避免踩踏主題](https://groups.google.com/d/topic/perl-cache-discuss/jDdBQliwlP4/discussion)。 –

+0

還有一個[djangosnippets:MintCache](https://www.djangosnippets.org/snippets/155/)策略。 –

回答

2

flock()它。

由於您的工作進程都在同一個系統上,因此您可以使用良好的老式文件鎖定功能來序列化昂貴的離子。作爲獎勵,這種技術出現在幾個核心文檔中。

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

+0

我很擔心會發生什麼,例如工人死亡期間。 '#獨佔鎖在這裏發佈爲$ lock超出範圍'是很漂亮的!謝謝! –

+0

同意,其中'flock()'是適當的,這是非常漂亮的。所有的魔法都在底層的文件描述符中,並且進程死亡和perl的最後引用文件句柄的範圍末尾銷燬完全符合要求。 – pilcrow

1

使用鎖?或者,也許這將是一個矯枉過正的?或者如果可能的話,預先離線計算結果然後在線使用它?

1

雖然它可能(或可能不會)爲你的用例矯枉過正,你是否考慮過使用消息隊列進行處理? RabbitMQ目前似乎是Perl社區的熱門選擇,它通過AnyEvent::RabbitMQ模塊提供支持。

在這種情況下,基本策略是在需要calculate新密鑰時向消息隊列提交請求。然後可以將隊列設置爲calculate一次只有一個鍵(按請求的順序),如果這是您可以可靠地處理的所有內容。或者,如果您可以同時安全地計算多個密鑰,則隊列還可以用於合併對同一個密鑰的多個請求,計算一次並將結果返回給請求該密鑰的所有客戶端。

當然,這會增加一點複雜性,AnyEvent調用的編程風格有點不同於你可能會習慣的(我會舉一個例子,但我從來沒有真正掌握過它),但它可以提供足夠的效率和可靠性,以使這些成本值得您享受。

+0

絕對也是一個優點大道。但我認爲,它需要融入更大的圖景中,而我們還沒有做好準備。感謝您的提醒。 –

-1

我一般同意上面提到的pilcrow的方法。我會添加一件事:調查memoize()函數的使用可能會加速代碼中的calculate()操作。

詳見http://perldoc.perl.org/Memoize.html

+0

記憶在一個過程中正常工作。正如OP所述,這個問題是關於多個進程都想要計算相同的事情。除非我誤解了某些內容,否則Memoize不會將緩存的值用於不同的進程,因此在此情況下無用。是的,它可以使用捆綁哈希,但是我猜測它會經歷OP的問題。 –