html текст
All interests
  • All interests
  • Design
  • Food
  • Gadgets
  • Humor
  • News
  • Photo
  • Travel
  • Video
Click to see the next recommended page
Like it
Don't like
Add to Favorites

Теггирование кеша в Yii recovery mode

В Yii для проверки актуальности кеша предусмотрены зависимости (Dependency). Они конечно позволяют многое, но, как всегда, не достаточно. Захотелось иметь возможность помечать кеш тегами так, чтобы при удалении любого тега, становился не актуальным весь кеш, помеченненый этим тегом.
Погуглив нашел пару статей по этому поводу:

В первой статье кешируют с зависимостью от одного тега.
Во второй статье уже зависимость от нескольких тегов, но использование описанной конструкции вносит некоторый эффект неожиданности в проект. Надеяться что любой тег проживет дольше записи помеченной этим тегом мне кажется слегка легкомысленным.
Не найдя подходящего решения написал своё по мотивам Dklab_Cache_Backend_TagEmuWrapper но в стиле Yii.

Сформулирую задачу, которую нужно было реализовать.
  1. Любое значение можно (но не обязательно) пометить одним или несколькими тегами.
  2. Нужна возможность удалить тег
  3. При удалении тега становится не актуальным весь кеш, помеченный этим тегом

Теперь реализация.
  1. Для того, чтобы иметь возможность проверять актуальность тегов, будем хранить вместе с тегом его версию.
  2. Вместе с теггированной записью, в кеш сохраняется список тегов, которыми она помечена.
  3. При проверке актуальности записи в кеше мы выдёргиваем все теги, которыми помечена запись в кеше, и сравниваем их с сохраненными в кеше тегами

Опишу на пальцах что означают последние три условия.
Допустим мы сохраняем в кеш запись с ключом «key» и значением «value». Помечаем эту запись тегами «tagA», «tagB».
Как то так:
$dependency = new \Cache\Tagged\Dependency(array('tagA', 'tagB'));
Yii::app()->cache->set('key', 'value', 0, $dependency);

при этом в кеш сохраняются три записи:
  • 'key' => array(array('tagA' => version('tagA'), 'tagB'=>version('tagB')), 'value')
  • 'tagA' => version('tagA')
  • 'tagB' => version('tagB')

На самом деле Yii добавляет в массив копию объекта Dependency, чтобы потом проверять актуальность кеша. (Но это здесь не отражено, чтобы не загромождать текст.)

Теперь предположим, что мы читаем запись из кеша.
При этом выполняются следующие шаги:
  1. Читается запись с ключем 'key'
  2. Читаются склеенные с записью теги
  3. Теги считанные из кеша сравниваются с копиями тегов в записи
  4. Если теги не совпали, то делается вывод, что кеш устарел

Вот как оказывается всё просто. А вот и код:
/**
 * protected/components/cache/Tagged/Dependency.php
 */

namespace Cache\Tagged;

class Dependency implements \ICacheDependency
{
	// Список тегов, поступивших в конструкторе
	public $_tags = null;

	// Ссылка на объект реализующий интерфейс \ICache
	public $_backend;

	// Ассоциативный массив версий тегов
	public $_tag_versions = null;

	/**
	 * Принимает на вход кучу тегов, которыми помечается кеш
	 */
	function __construct(array $tags) {
		$this->_tags = $tags;
	}

	function initBackend()
	{
		$this->_backend = \Yii::app()->cache;
	}

	/**
	 * Этот метод вызывается до сохранения данных в кеш.
	 * В нём мы устанавливаем версии тегов указанных в конструкторе и затем сохраненных в property:_tags
	 */
	public function evaluateDependency() {
		$this->initBackend();
		$this->_tag_versions = null;

		if($this->_tags === null || !is_array($this->_tags)) {
			return;
		}

		if (!$this->_backend) return;

		$tagsWithVersion = array();

		foreach ($this->_tags as $tag) {
			$mangledTag = Helper::mangleTag($tag);
			$tagVersion = $this->_backend->get($mangledTag);
			if ($tagVersion === false) {
				$tagVersion = Helper::generateNewTagVersion();
				$this->_backend->set($mangledTag, $tagVersion, 0);
			}
			$tagsWithVersion[$tag] = $tagVersion;
		}

		$this->_tag_versions = $tagsWithVersion;

		return;
	}

	/**
	 * Возвращает true, если данные кеша устарели
	 */
	public function getHasChanged()
	{
		$this->initBackend();

		if ($this->_tag_versions === null || !is_array($this->_tag_versions)) {
			return true;
		}
		
		// Выдергиваем текущие версии тегов сохраненных с записью в кеше
		$allMangledTagValues = $this->_backend->mget(Helper::mangleTags(array_keys($this->_tag_versions)));

		// Перебираем теги сохраненные в dependency. Т.е. здесь
		foreach ($this->_tag_versions as $tag => $savedTagVersion) {

			$mangleTag = Helper::mangleTag($tag);

			// Тег мог "протухнуть", тогда считаем кеш измененным
			if (!isset($allMangledTagValues[$mangleTag])) {
				return true;
			}

			$actualTagVersion = $allMangledTagValues[$mangleTag];

			// Если сменилась версия тега, то кеш изменили
			if ($actualTagVersion !== $savedTagVersion) {
				return true;
			}
		}

		return false;
	}
}


и хелпер к этой зависимости

namespace Cache\Tagged;

/**
 * protected/components/cache/Tagged/Helper.php
 */

class Helper
{
	const VERSION = "0.01";

	static private $_cache = null;

	static public function init(\ICache $cacheId = null)
	{
		if ($cacheId === null)
		{
			if (self::$_cache !== null) {
				return true;
			}

			// По умолчанию берём глобально определенный кеш
			self::$_cache = \Yii::app()->cache;
		}
		else {
			self::$_cache = $cacheId;
		}

		return (self::$_cache !== null);
	}

	/**
	 * Удаление тегов кеша
	 * Вместе с тегами становятся не актиуальным кеш, помеченный этими тегами
	 */
	static public function deleteByTags($tags = array()) {

		if (!self::init()) return false;

		if (is_string($tags)) {
			$tags = array($tags);
		}

		if (is_array($tags)) {
			foreach ($tags as $tag) {
				self::$_cache->delete(self::mangleTag($tag));
			}
		}

		return true;
	}

	/**
	 * Генерит название ключа по имени тега
	 */
	static public function mangleTag($tag) {
		return get_called_class() . "_" . self::VERSION . "_" . $tag;
	}

	/**
	 * Применяет метод mangleTag к списку тегов и возвращает массив ключей
	 * @see self::_mangleTag
	 */
	static public function mangleTags($tags) {
		foreach ($tags as $i => $tag) {
			$tags[$i] = self::mangleTag($tag);
		}
		return $tags;
	}

	/**
	 * Генерит новый уникальный идентификатор для версии тега
	 */
	static public function generateNewTagVersion() {
		static $counter = 0;
		$counter++;
		return md5(microtime() . getmypid() . uniqid('')) . '_' . $counter;
	}
}


т.к. я в коде использовал пространство имён, то в конфиге нужно будет прописать алиас
Yii::setPathOfAlias('Cache', $basepath . DIRECTORY_SEPARATOR . 'components/cache');

и можно использовать новую зависимость, например так:
// Возьмем текущий класс через который осуществляется кеширование
$cache = \Yii::app()->cache;

// Создаем зависимость от тегов
$dependency = new \Cache\Tagged\Dependency(array('c', 'd', 'e'));

// Сохраняем запись в кеш и помечаем её тегами
$cache->set('LetterA', 'A', 0, $dependency);

// Смотрим, что запись в кеше имеется
var_dump($cache->get('LetterA'));

// Удаляем теги (можно интерпретировать как удаление записей по тегу)
\Cache\Tagged\Helper::deleteByTags(array('d'));

// Смотрим, что актуальной записи в кеше нет
var_dump($cache->get('LetterA'));

Для полного счастья сделаем прозрачное кеширование моделей данных CActiveRecord. (Нужно же куда то применить новый класс)
Создаем новый файл protected/components/ActiveRecord.php со следующим содержимым:
class ActiveRecord extends CActiveRecord
{
	// Время кеширования страницы
	const CACHE_DURATION = 0;

	protected function beforeFind()
	{
		$tags = array($this->tableName());
		$this->cache(self::CACHE_DURATION, new \Cache\Tagged\Dependency(array($tags)));
		parent::beforeFind();
	}

	protected function afterSave()
	{
		\Cache\Tagged\Helper::deleteByTags($this->tableName());
		parent::afterSave();
	}

	protected function afterDelete()
	{
		\Cache\Tagged\Helper::deleteByTags($this->tableName());
		parent::afterDelete();
	}

}

наследуем его вместо CActiveRecord и наблюдаем за уменьшением соединений с базой
Читать дальше
Twitter
Одноклассники
Мой Мир

материал с habrahabr.ru

2

      Add

      You can create thematic collections and keep, for instance, all recipes in one place so you will never lose them.

      No images found
      Previous Next 0 / 0
      500
      • Advertisement
      • Animals
      • Architecture
      • Art
      • Auto
      • Aviation
      • Books
      • Cartoons
      • Celebrities
      • Children
      • Culture
      • Design
      • Economics
      • Education
      • Entertainment
      • Fashion
      • Fitness
      • Food
      • Gadgets
      • Games
      • Health
      • History
      • Hobby
      • Humor
      • Interior
      • Moto
      • Movies
      • Music
      • Nature
      • News
      • Photo
      • Pictures
      • Politics
      • Psychology
      • Science
      • Society
      • Sport
      • Technology
      • Travel
      • Video
      • Weapons
      • Web
      • Work
        Submit
        Valid formats are JPG, PNG, GIF.
        Not more than 5 Мb, please.
        30
        surfingbird.ru/site/
        RSS format guidelines
        500
        • Advertisement
        • Animals
        • Architecture
        • Art
        • Auto
        • Aviation
        • Books
        • Cartoons
        • Celebrities
        • Children
        • Culture
        • Design
        • Economics
        • Education
        • Entertainment
        • Fashion
        • Fitness
        • Food
        • Gadgets
        • Games
        • Health
        • History
        • Hobby
        • Humor
        • Interior
        • Moto
        • Movies
        • Music
        • Nature
        • News
        • Photo
        • Pictures
        • Politics
        • Psychology
        • Science
        • Society
        • Sport
        • Technology
        • Travel
        • Video
        • Weapons
        • Web
        • Work

          Submit

          Thank you! Wait for moderation.

          Тебе это не нравится?

          You can block the domain, tag, user or channel, and we'll stop recommend it to you. You can always unblock them in your settings.

          • hexman84
          • домен habrahabr.ru

          Get a link

          Спасибо, твоя жалоба принята.

          Log on to Surfingbird

          Recover
          Sign up

          or

          Welcome to Surfingbird.com!

          You'll find thousands of interesting pages, photos, and videos inside.
          Join!

          • Personal
            recommendations

          • Stash
            interesting and useful stuff

          • Anywhere,
            anytime

          Do we already know you? Login or restore the password.

          Close

          Add to collection

             

            Facebook

            Ваш профиль на рассмотрении, обновите страницу через несколько секунд

            Facebook

            К сожалению, вы не попадаете под условия акции