缓存无效导致高负载


Cache invalidation causing high load

假设我们的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
     }
}

这个想法是缓存是全局的,所以可以用来在请求之间保持锁模式。您实际上是在缓存项上创建一个互斥锁,并添加了一些逻辑,以确保只启动一个慢速查询。