Скажи кэшированию… иногда. Часть 2: Memcache

by Larin

В своей первой статье о кэшировании, я рассказывал о кэшировании информации в файлы. Данный тип кэширования можно использовать на всех хостингах, для него не требуется установки какого-либо специфического ПО (разумеется хостинг должен поддерживать PHP).

Сегодня я расскажу о кешировании при помощи MemCache (и еще ссылочка).

В предыдущей статье мы создали 2 класса: Cache & File. В этой статье нам снова пригодится класс Cache, только на этот раз мы заменим его на интерфейс (FX Poster, спасибо за поправку) и будет называться CACHE_ICache (почему, думаю, поймете позже).

Таким образом интерфейс CACHE_ICache будет выглядеть следующим образом:


<?php
interface CACHE_ICache
{
	public function save($value, $valueID);
	public function load($valueID, $timeLife);
	public function delete($valueID);
}
?>

Установка memcache под Windows

  1. Скачиваем сервер memcached (memcached-1.2.1-win32.zip)
  2. Распаковываем полученный архив на диск, например, в c:\memcache\
  3. Далее запускаем из командной строки: c:\memcache\memcached.exe -d install, тем самым устанавливая memcached как сервис.
  4. Теперь, осталось только запустить этот сервис: c:\memcache\memcached.exe -d start
  5. Все, memcache установнен на вашей машине и уже работает. Список всех доступных команд получаем так: c:\memcache\memcached.exe -h

Установка модуля для PHP

Здесь тоже все предельно просто.

  1. Скачиваем расширение для установленной у вас версии PHP
  2. Копируем полученный файл php_memcache.dll в директорию расширений для PHP. Например, у меня это C:\server\usr\local\php5\ext\
  3. Добавляем в файл php.ini строку extension = php_memcache.dll, в раздел Dynamic Extensions.
  4. Перезапускаем сервер. Все, memcache установлен! Данный факт можно проверить при помощи функции phpinfo();

Установка под Linux

А линуксоиды и сами справятся с установкой. Один совет, устанавливайте из репозитария – меньше гемороя будет. :)

Например, для Debian: apt-get install memcached libmemcache0 php5-memcache и будет вам счастье.

Любителям гемороя могу посоветовать How to install memcache on Debian Etch.

Создание класса

Подготовительный этап окончен, теперь приступаем к программированию. У нас уже есть интерфейс CACHE_ICache описывающий необходимый интерфейс классов кэширования. Теперь создадим класс кэширования для memcached и назовем его CACHE_MemCache.


<?php
class CACHE_MemCache implements CACHE_ICache
{
	private $memcache;
	private $timeLife;
	private $compress;

	/**
	 *
	 * @param string $host - хост сервера memcached
	 * @param int $port - порт сервера memcached
	 * @param int $compress - [0,1], сжимать или нет данные перед
	 * помещением в память
	 */
	public function __construct($host, $port = 11211, $compress = 0)
	{
		$this->memcache = memcache_connect($host, $port);
		$this->compress = ($compress) ? MEMCACHE_COMPRESSED : 0;
	}

	public function load($valueID, $timeLife)
	{
		$this->timeLife = $timeLife;
		return memcache_get($this->memcache, $valueID);
	}

	public function save($value, $valueID)
	{
		return memcache_set($this->memcache, $valueID, $value, $this->compress, $this->timeLife);
	}

	public function delete($valueID)
	{
		memcache_delete($this->memcache, $valueID);
	}

	public function __destruct()
	{
		memcache_close($this->memcache);
	}
}
?>

Как видите, код намного проще чем код кэширования в файлы.

Теперь у нас есть возможность кэшировать при помощи файлов и при помощи memcache. Но что же делать если мы вначале будем использовать файловое кэширование, а потом захотим быстро перейти на memcache? Не перелапачивать же все файлы в которых используется кэширование?!

Выход есть! Нам помогут 2 паттерна проектирования: Strategy и Registry.

Паттерны спешат на помощь!

Создадим класс CACHE_Manager он поможет нам управлять всем многообразием (мы же не остановимся на 2-х классах) классов для кэширования.


<?php
class CACHE_Manager
{
	private $_cache;

	public function __construct(CACHE_ICache $cache)
	{
		$this->_cache = $cache;
	}

	public function load($valueID, $timeLife)
	{
		return $this->_cache->load($valueID, $timeLife);
	}

	public function save($value, $valueID)
	{
		$this->_cache->save($value, $valueID);
	}

	public function delete($valueID)
	{
		$this->_cache->delete($valueID);
	}
}
?>

Код очень прост и я думаю не требует объяснений. Если же объяснения все же нужны, вам поможет Google, по запросу: паттерн Strategy. (Я бы с радостью пояснил, но боюсь, что статья получится очень большой и ее просто никто не станет читать :) )

Пример использования данного класса:


<?php
//Кэширование на файлах
$cache = new CACHE_Manager(new CACHE_File('cache'));
if (!$var = $cache->load('key', 60)) { //60 секунд
	$var = mega_function();
	$cache->save($var, 'key');
}

//Кеширование memcache
$cache = new CACHE_Manager(new CACHE_MemCache('127.0.0.1', 11211));
if (!$var = $cache->load('key', 60)) { //60 секунд
	$var = mega_function();
	$cache->save($var, 'key');
}
?>

Как видно из примера, что бы сменить тип кэширования нам достаточно сменить аргумент в вызове конструктора класса CACHE_Manager.

Теперь сделаем, чтобы вызов класса кэширования выполнялся централизованно, в одном месте. Таким образом, для перевода всего проекта на новый тип кэширования, нам понадобится заменить всего одну строку. В этом нам поможет паттерн Registry.


<?php
class BASE_Registry
{
	private static $_vars = array();

	public function __construct() {}

	public static function set($key, $var)
	{
		if (isset(self::$_vars[$key]) == true) {
			throw new Exception('Данная переменная [' . $key . '] уже существует!');
		}
		self::$_vars[$key] = $var;
		return true;
	}

	public static function get($key)
	{
		if (isset(self::$_vars[$key]) == false) { return null; }
		return self::$_vars[$key];
	}

	public static function remove($var)
	{
		unset(self::$_vars[$key]);
	}
}
?>

А теперь пример использования всего этого добра :).


<?php
/* ... */

//Добавляем в реестр экземпляр кэширования в файлы
BASE_Registry::set('cache', new CACHE_Manager(new CACHE_File('cache')));

//Получаем экземпляр класса кеширования из реестра
$cache = BASE_Registry::get('cache');

//И работаем с ним как и раньше
if (!$var = $cache->load('key', 60)) { //60 секунд
	$var = mega_function();
	$cache->save($var, 'key');
}

/* ... */
?>

Теперь для того чтоб сменить тип кэширования нам нужно будет заменить строку:
BASE_Registry::set(‘cache’, new CACHE_Manager(new CACHE_File(‘cache’)));
на
BASE_Registry::set(‘cache’, new CACHE_Manager(new CACHE_MemCache(’127.0.0.1′, 11211)));

И все. Конечно же это все будет верно, при условии, что ваша система построена по MVC принципу и все вызовы идут через один файл, например, index.php.

Странные имена

В конце, я расскажу почему использовал такие странные имена классов. Опять же, все просто. Имя класса состоит из названия модуля и названия класса, разделенных символом нижнего подчеркивания. Это позволяет структурировать классы, что облегчает их повторное использование. А так же позволяет создать простой autoload:


<?php
define ('DIRSEP', DIRECTORY_SEPARATOR);
define ('site_path', dirname(dirname(__FILE__)) . DIRSEP);

function __autoload($class_name)
{
	$fname = str_replace('_', DIRSEP, $class_name) . '.php';
	if(!file_exists(site_path . 'include' . DIRSEP . $fname)) {
		throw new Exception('Файла [' . site_path . 'include' . DIRSEP . $fname . '] не существует!');
	}
	require_once(site_path . 'include' . DIRSEP . $fname);
}
?>

Спасибо за внимание!

Надеюсь, что мои статьи помогут вам лучше понять кэширование и некоторые принципы ООП.