假设我们的php脚本每秒有10K个请求。
每个请求都在检查memcached中的缓存(或任何其他缓存存储)。如果找到缓存-一切正常,并返回缓存值。如果没有找到缓存,我们将执行缓慢的SQL查询来填充缓存。这是最常见和最简单的缓存方案:
$result = $this->loadFromCache($key);
if (empty($result)) {
$result = $this->makeSlowSqlQuery();
$this->writeToCache($key, $result);
}
//do something with $result;
这个方案在我们没有太多的请求之前工作得很好。一旦我们有太多的请求,我们就会面临这样的情况:大量的请求在缓存中找不到任何东西,并试图重新填充它。因此,它们都将开始执行缓慢的SQL查询,这将导致高负载影响。解决方案是什么?
作为可能的解决方案,我看到以下场景:第一个发现缓存无效的请求应该创建一些触发器,表明缓存重新填充已经开始,另一个请求应该等待新的缓存或使用旧的(以前的)版本。
你如何解决类似的问题?
你真正想要的是一个锁模式:
$lockPrefix = "!lock__";
$result = $this->loadFromCache($key);
if (empty($result)) {
$sleepLimit = 2000; // 2s timeout
$sleepCount = 0;
$cacheBlocked = 0;
while ($this->loadFromCache($lockPrefix . $key) == 1) {
// signal that something else is updating the cache
$cacheBlocked = 1;
// sleep for 1ms
usleep(1000);
// timeout logic...
$sleepCount++
if ($sleepCount == $sleepLimit) {
die("Cache read timeout.");
}
}
if ($cacheBlocked == 1) {
// something else updated the cache while we were waiting
// so we can just read that result now
$result = $this->loadFromCache($key);
} else {
$this->writeToCache($lockPrefix . $key, 1); // lock
$result = $this->makeSlowSqlQuery();
$this->writeToCache($key, $result);
$this->writeToCache($lockPrefix . $key, 0); // release
}
}
这个想法是缓存是全局的,所以可以用来在请求之间保持锁模式。您实际上是在缓存项上创建一个互斥锁,并添加了一些逻辑,以确保只启动一个慢速查询。