Проблема одновременного перестроения кэша
В одном из своих нагруженных проектов я столкнулся с проблемой, когда при истечении срока кеша или при удалении ключа. Большой поток запросов устремлялся к БД. Вследствие чего БД не выдерживала и ложилась.
Для решения этой проблемы нужно:
Так как проект был написан на yii, мы расширили родной CMemCache. И вот что получилось.
Для решения этой проблемы нужно:
- Создавать блокировочный ключ (что бы остальные потоки не перестраивали кеш)
- Время ожидания для генерации кеша (что бы остальные потоки ждали)
- После сохранения данных в кеш сбросить блокировочный ключ
Так как проект был написан на yii, мы расширили родной CMemCache. И вот что получилось.
<?php
/**
* Class AMemCached
*/
class AMemCached extends CMemCache
{
/**
* @var bool
*/
public $hashKey = false;
/**
* @var string
*/
public $keyPrefix = '';
/**
* Return lock key
* @param string $key
* @return string
*/
protected function getLockKey($key)
{
return $key . '_block';
}
/**
*
*/
public function waitingUnlock($key, $timeout = 3)
{
$i = 1;
while ($i <= $timeout && ($unlock = $this->get($this->getLockKey($key))) === true) {
sleep(1);
$i++;
}
return $unlock === false;
}
/**
* Lock record
* @param string $key
* @param int $timeout
* @return bool
*/
protected function lock($key, $timeout)
{
return $this->add($this->getLockKey($key), true, $timeout);
}
/**
* Unlock record
* @param string $key
* @return bool
*/
protected function unlock($key)
{
return $this->delete($this->getLockKey($key));
}
/**
* Set value with lock
* @param string $key
* @param int $expire
* @param callable $updater
* @param int $waitTimeout
* @param int $lockTimeout
* @return bool|string
*/
public function setWithWaiting($key, $expire, Closure $updater, $waitTimeout = 3, $lockTimeout = 5)
{
if ($this->lock($key, $lockTimeout)) {
$value = call_user_func($updater);
$this->set($key, $value, $expire);
$this->unlock($key);
} else {
$value = $this->get($key);
if (false === $value && $this->waitingUnlock($key, $waitTimeout)) {
$value = $this->get($key);
}
}
return $value;
}
}
Для удобства использование создал метод в расширив CActiveRecord:
/**
* Check and save result on cache
* @param string $key
* @param string $expire
* @param callable $callback
* @return mixed
*/
protected function cacheResult($key, $expire, Closure $callback)
{
$result = Yii::app()->cache->get($key);
if ($result === false) {
$result = Yii::app()->cache->setWithWaiting($key, $expire, $callback);
}
return $result;
}
Теперь им удобно пользоваться:
public function someMethod($limit = 5) {
// Cache key
$key = 'cache_' . $limit;
return $this->cacheResult($key, 43200, function() use($limit, $query) {
$c = new CDbCriteria();
$c->order = 't.created ASC';
$c->limit = $limit;
return new CActiveDataProvider($this, $c);
});
P.S. Надеюсь кому нибудь поможет это решение.
Комментарии
Отправить комментарий