2

我需要缓存一些昂贵的查询(5 秒)。我最近开始了解使用 memcache 时的 dogpile 影响。一种解决方案是在缓存键值对上使用锁来防止这种影响。由于 GAE memcached 不支持对 memcached 键值的锁定,防止狗堆效应的最佳做法是什么?

4

3 回答 3

3

这取决于您的应用程序在做什么,但这里有三种可能有用的方法(没有一种方法适用于所有场景,它们都有缺点):

如果您可以使用“稍等一下再试一次”之类的方式响应请求,那么在未命中时将代表该请求的标志设置到 memcache 中,然后再花时间重新处理实际值可能会有所帮助(仍然会有get 和第一组之间的比赛的可能性,但它比等待查询完成要短得多):

value = memcache.get(key)
if value is None:
  memcache.set(key, 'recalculating')
  // do the slow thing
  memcache.set(key, actual_result)

return value

或者,如果您在缓存结果上设置生命周期,但可以处理客户端接收稍微陈旧的数据,那么除了使用超时和键缓存您的值之外,您还可以缓存没有超时和不同键的副本,然后在错过时使用此副本重新填充缓存,同时重新处理一个新值(同样,仍然有可能在 get/set 之间发生竞争,并且副本可能会被驱逐。):

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

第三个更简单,只需让后端不断执行查询并让它更新缓存,这样前端请求就不太可能必须这样做。但是,这确实意味着无论任何人使用的值如何,都会进行查询。

于 2013-01-13T12:38:49.410 回答
1

忽略它。

喝下 Google Kool-Aid,相信您不会因为同时查询访问数据存储区而导致数据库严重崩溃。无论如何,狗桩效应是短暂的,基础设施应该吸收它。您只需为更多查询付费。

除非您实际上有足够的流量并且它会花费您大量的金钱,否则它可能远远低于您的优先级。

于 2013-01-13T08:29:11.417 回答
0

使用信号量锁可以防止狗堆效应。如果值过期,第一个进程获取锁并开始生成新值。所有后续请求都会检查是否获得了锁并提供过时的内容(如果是的话)。生成新值后,释放锁。

缓存值应该被赋予更长的生命周期,因此它们在过期时不会被物理删除,并且如果需要,它们仍然可以被提供。

这是它在 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/ 。

于 2014-07-27T07:44:07.793 回答