【发布时间】:2012-12-27 09:33:28
【问题描述】:
我需要缓存一些昂贵的查询(5 秒)。我最近开始了解使用 memcache 时的 dogpile 影响。一种解决方案是在缓存键值对上使用锁来防止这种影响。由于 GAE memcached 不支持对 memcached 键值的锁定,因此防止狗堆效应的最佳做法是什么?
【问题讨论】:
标签: google-app-engine memcached google-cloud-datastore
我需要缓存一些昂贵的查询(5 秒)。我最近开始了解使用 memcache 时的 dogpile 影响。一种解决方案是在缓存键值对上使用锁来防止这种影响。由于 GAE memcached 不支持对 memcached 键值的锁定,因此防止狗堆效应的最佳做法是什么?
【问题讨论】:
标签: google-app-engine memcached google-cloud-datastore
这取决于您的应用在做什么,但这里有三种可能有用的方法(没有一种方法适用于所有场景,它们都有缺点):
如果您可以用“稍等一下再试一次”之类的方式来响应请求,那么在您花时间重新处理实际值之前,将代表该请求的标志设置到 memcache 中可能会有所帮助(会有仍然有可能在获取和第一组之间进行比赛,但它会比等待查询完成要短得多):
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
第三种更简单,只需让后端不断地执行查询并让它更新缓存,这样前端请求就不太可能必须这样做。但是,这确实意味着无论任何人使用的值如何,都会执行查询。
【讨论】:
忽略它。
喝下 Google Kool-Aid,相信您不会因为同时查询访问数据存储区而导致数据库严重崩溃。无论如何,狗桩效应是短暂的,基础设施应该吸收它。您只需为更多查询付费。
除非您实际上有足够的流量并且花费大量金钱,否则您的优先级可能会大大降低。
【讨论】:
使用信号量锁可以防止狗堆效应。如果值过期,第一个进程获取锁并开始生成新值。所有后续请求都会检查是否获得了锁并提供过时的内容(如果是的话)。生成新值后,释放锁。
应赋予缓存值更长的生命周期,因此它们在过期时不会被物理删除,并且在需要时仍可提供服务。
这是它在 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/。
【讨论】: