我需要缓存一些代价高昂的查询(5秒)。我最近了解了使用memcache时的dogpile影响。一种解决方案是使用缓存键值对上的锁来防止这种影响。由于GAE memcached不支持对memcached键值的锁定,防止dogpile效果的最佳做法是什么?
答案 0 :(得分:3)
这取决于你的应用程序在做什么,但这里有三种方法可能很有用(没有一种方法适用于所有方案,它们都有缺点):
如果您可以使用“稍等一会儿再试一次”来回复请求,那么在您花费时间重新处理实际值之前,设置一个表示未完成的内存缓存的标记可能会有所帮助(会有所帮助)仍然是get和第一组之间竞争的可能性,但它比等待查询完成要短得多):
value = memcache.get(key)
if value is None:
memcache.set(key, 'recalculating')
// do the slow thing
memcache.set(key, actual_result)
return value
或者,如果您在缓存结果上设置了生命周期,但是可以处理接收稍微陈旧数据的客户端,然后使用超时和密钥缓存您的值,则缓存没有超时的副本和不同的密钥,然后错过使用此副本重新填充缓存,同时重新处理一个新的值(再次,仍然是获取/设置之间的竞争的机会,并且也可能驱逐该副本。):
value = memcache.get(key)
if value is None:
memcache.set(key, cache.get(key+'copy'))
// do the slow thing
memcache.set(key, actual_result, 30)
memcache.set(key+'copy', actual_result)
return value
第三个更简单,只需让后端不断进行查询并让它更新缓存,这样前端请求就不太可能必须这样做。但这确实意味着,无论任何人使用什么值,查询都会完成。
答案 1 :(得分:1)
忽略它。
喝Google Kool-Aid并相信您不会因同时查询到数据存储区而遭受重大数据库冲击。无论如何,堆垛效应是暂时的,基础设施应该吸收它。您只需支付更多查询费用。
除非你真的有足够的流量并且花费了大量的金钱,否则你的优先事项可能差不多。
答案 2 :(得分:0)
使用信号量锁可以防止Dogpile效应。如果值已过期,则第一个进程获取锁定并开始生成新值。所有后续请求都会检查是否已获取锁定并提供过时内容(如果是)。生成新值后,将释放锁定。
缓存值应该延长使用寿命,因此它们在过期时不会被物理删除,如果有需要,它们仍然可以提供。
以下是它在PHP中的工作原理(应该很容易在Python中复制)。
从缓存存储中获取缓存值。
$value = $this->store->get($key);
$ value是一个值对象。
检查缓存值是否已过期。如果没有过期,请提供服务。
if ($value && !$value->isStale()) {
return $value->getResult();
}
否则,获取锁定,因此只有一个进程重新生成新值。
$lock_acquired = $this->acquireLock($key, $grace_ttl);
如果无法获取锁定,则表示已经有其他进程重新生成它,所以让我们只提供当前(陈旧)值。
if (!$lock_acquired) {
return $value->getResult();
}
否则(已获取锁定),重新生成新值。
$result = ...
在缓存存储中保存重新生成的值。添加宽限期,如果其他进程需要,可以提供过时的结果。
$expiration_timestamp = time() + $ttl;
$value = new Value($result, $expiration_timestamp);
$real_ttl = $ttl + $grace_ttl;
$this->store->set($key, $value, $real_ttl);
释放锁定。
$this->releaseLock($key);
完整的PHP实施:https://github.com/sobstel/metaphore/blob/master/src/Cache.php。您也可以尝试使用MintCache:https://djangosnippets.org/snippets/155/。