Кэширование данных на PHP, сохраняем массивы и объекты, результаты SQL запросов

Кэширование данных на PHP, сохраняем массивы и объекты, результаты SQL запросов

Вот и пришел момент, когда надо задуматься об ускорении работы PHP скриптов на сайте и об уменьшении общего времени генерации контента на сайте. Для решения этой задачи во-первых надо выяснить, а что именно забирает львиную долю процессорного времени при работе скрипта PHP?

Вы должны представлять, что всякого рода вычисления типа $c = $a + $b выполняются довольно быстро, и даже если у вас производятся тысячи таких или подобных вычислений, то с учётом производительности современных серверов, на такие вычисления затрачивается очень мало времени процессора, как правило около тысячи таких операций выполняться за сотую долю секунды, а может даже за тысячную. Выходит так, что «грузят процессор» не такие операции, а какие-то другие. Я вам сразу скажу, что может «грузить процессор». В моём случае, это:

  • Сложные SQL запросы к базе данных (БД);
  • Генерация больших кусков HTML кода с использованием результатов из БД;
  • Избыточность одинаковых циклических операций, результат которых почти всегда одинаков.

Сложные SQL запросы к базе данных (БД). Особенно к большим базам данных, у которых от 100 тысяч записей, а иногда и миллионы. А иногда эти данные собираются не из одной таблицы, а из 3-ёх и более. Вот пример одного из запроса:

SELECT DISTINCT a.*, 
(SELECT GROUP_CONCAT(DISTINCT k.`word` SEPARATOR ", ") FROM `articles_keywords` AS k WHERE k.`article_id`=a.`id`) as `keywords`, 
(SELECT count(id) FROM `articles_views` AS v WHERE v.`article_id`=a.`id`) as `views_sum` 
FROM `articles_articles` AS a 
WHERE a.`time_publish` 

В результате этого запроса возвращаються данные по списку статей, которые формируются из трёх таблиц базы данных, одна из которых, «articles_views» содержит более миллиона записей со статистикой просмотров каждой статьи. Время выполнения такого запроса иногда достигает двух секунд. И это только один из запросов, который выполняется при формировании страницы сайта. Благодаря ему, на сайте формируется один из списков статей. Дальше эти данные обрабатываются непосредственно средствами PHP и в итоге формируется один из кусочков страницы сайта. И на это уходит до 2 секунд. Таких кусочков на разных страницах несколько, они иногда одинаковые, и что самое интересное, они же не так часто изменяются, долгое время они остаются всегда одинаковыми. Так зачем же для каждой страницы мы снова и снова заставляем «перерывать» объёмную базу данных и тратить на это несколько секунд драгоценнейшего процессорного времени. Может стоит эти данные, а может даже сразу куски HTML кода, можно как-то использовать повторно, какое-то время, например несколько минут, а может быть час. А есть такие данные, которые не меняються неделями или днями, например меню сайта и списки разделов.

Например, сохранив данные результата SQL запроса к БД, мы повторно уже не будем тратить 2 секунды для того же запроса, мы же знаем, что за 5 минут эти данные скорее всего не поменяються. Следовательно, например, если мы получили данные результата SQL запроса и поместили их в переменную PHP, назовём её, $resultSQL, то надо сохранить как-то эту переменную в файл и в течении 5-и минут, если нам потребуются данные по такому же запросу, то мы просто считаем файл и запишем данные сразу в переменную $resultSQL. На чтение файла потратится 0,001 секунда, но никак не 2 как на SQL запрос к базе данных. Тем самым мы ускоряем вцелом генерацию страниц.

Для реализации этой идеи я создал PHP класс DataCache, задача которого сохранять данные в файлы на диске, а потом считывать их когда это необходимо.

Исходный код PHP файла с классом DataCache:

<?php
##################################################
# Класс КЭШИРОВАНИЯ данных (массивов и объектов)
##################################################
#
#	Можно кэшировать результаты SQL запросов или любые данные в виде массивов или объектов
#
#	Пример использования класса для кеширования данных
#
#		// Создаём объект для кэширования данных с уникальным для этих данных ID - findXXX
#		$dataCache = new DataCache('findXXX');
#		// Запрашиваем инициализацию кэша
#		$getDataFromCache = $dataCache->initCacheData();
#		if ($getDataFromCache) {
#			// Получаем кэшированные данные из кэша
#			$data = $dataCache->getCacheData();
#		} else {
#			// Исполняем код если кеширование отключено или данные в кеше старые
#			$data = generateNewData();
#			.......
#			// Обновляем данные в кэше
#			$dataCache->updateCacheData($data);
#		}
#

class DataCache {
	
	private $cache;
	private $cacheFileName;
	private $cacheFolder;
	private $cacheFullFileName;
	private $cacheFullFilePath;
	private $cacheEnabled;
	private $cacheTime;
	private $dataName;
	
	const DEFAULT_CACHE_PATH = '/cache_data/'; # путь к папке с файлами для хранения кешированных данных
	const DEFAULT_CACHE_TIME = 600;	# время кеширования данных, секунды

	#-------------------------------------
	public function __construct($dataName)
	{
		if (defined('SiteSettings::dataCacheEnabled') && SiteSettings::dataCacheEnabled === true) {
			$this->setCacheOn();
		} else {
			$this->setCacheOff();
		}
		if (defined('SiteSettings::dataCacheTime') && !empty(SiteSettings::dataCacheTime)) {
			$this->setCacheTime(SiteSettings::dataCacheTime);
		} else {
			$this->setCacheTime(self::DEFAULT_CACHE_TIME);
		}
		if (defined('SiteSettings::dataCacheFolder') && !empty(SiteSettings::dataCacheFolder)) {
			$this->cacheFolder = $_SERVER['DOCUMENT_ROOT'] . SiteSettings::dataCacheFolder;
		} else {
			$this->cacheFolder = $_SERVER['DOCUMENT_ROOT'] . self::DEFAULT_CACHE_PATH;
		}
		$this->dataName = $dataName;
		$md5hash = md5($this->dataName);
		$this->cacheFileName = substr($md5hash, 2, 30);
		$this->cacheFullFilePath = $this->cacheFolder . substr($md5hash, 0, 1) . '/' . substr($md5hash, 1, 1) . '/';
		$this->cacheFullFileName = $this->cacheFullFilePath . $this->cacheFileName;
				
		return true;
	}
	
	#---------------------------
	public function setCacheOn()
	{
		$this->cacheEnabled = true;
	}

	#---------------------------
	public function setCacheOff()
	{
		$this->cacheEnabled = false;
	}

	#--------------------------------
	public function getCacheEnabled()
	{
		return $this->cacheEnabled;
	}
	
	#-------------------------------------------
	public function setCacheTime(int $seconds)
	{
		$this->cacheTime = $seconds;
	}

	#------------------------------
	public function initCacheData()
	{
		if (!$this->cacheEnabled) { return false; }
		$cacheOld = time() - @filemtime($this->cacheFullFileName);
		if($cacheOld < $this->cacheTime) {
			$fp = @fopen($this->cacheFullFileName, "r");
			$this->cache = @fread($fp, filesize($this->cacheFullFileName));
			@fclose($fp);
			return true;
		}
		
		return false;
	}

	#-----------------------------
	public function getCacheData()
	{
		if (!$this->cacheEnabled) { return false; }
		
		if (empty($this->cache)) {
			return false;
		} else {
			return unserialize($this->cache);
		}
	}

	#----------------------------------------
	public function updateCacheData($newData)
	{
		if (!$this->cacheEnabled) { return false; }
		
		$this->cache = $newData;
		$output = serialize($this->cache);
		if(!@file_exists($this->cacheFullFilePath)) { @mkdir($this->cacheFullFilePath, 0777, true); }
		$fp = @fopen($this->cacheFullFileName, "w");
		@fwrite($fp, $output);
		@fclose($fp);
		
		return true;
	}	
}
?>

Класс DataCache можно использовать и самостоятельно, но у меня есть ещё класс с настройками. Класс SiteSettings также можно подключить и задавать в нём настройки не только для класса DataCache, но и для всего сайта вцелом.

Исходный код PHP файла с классом SiteSettings:

<?php
class SiteSettings
{

 const dataCacheEnabled = true; # Включить кэшированние данных
 const dataCacheTime = 10*60; # время кеширования данных, секунды
 const dataCacheFolder = '/cache_data/'; # путь к папке с файлами для хранения кешированных данных

}
?>

Итак, как использовать DataCache. Вот пример PHP файла, который использует кеширование:

<?php
	include_once('./classes/classDataCache.php');
	include_once('./classes/classSiteSettings.php');

	# засикаем время начала работы скрипта
	$start = microtime(true);
	
	// Создаём объект для кэширования данных с УНИКАЛЬНЫМ для этих данных ID - array1000md5i
	$dataCache = new DataCache('array1000hashsha512');
	# $dataCache->setCacheOff(); - отключение кэширования, если надо
	# $dataCache->setCacheTime(1000); - установка времени кэширования, секунд, если надо
	// Запрашиваем инициализацию кэша
	$getDataFromCache = $dataCache->initCacheData();
	if ($getDataFromCache) {
		// Получаем кэшированные данные из кэша
		$data = $dataCache->getCacheData();
	} else {
		// Исполняем этот код, если кеширование отключено или данные в кеше старые
		
		// Создание каких-то данных или какая-то ресурсоёмкая задача
		$data = Array();
		$str = '';
		for ($i = 0; $i < 10000; $i++) {
			$str .= $i;
			$data[] = hash('sha512', $str);
		}
		
		// Обновляем данные в кэше
		$dataCache->updateCacheData($data);
	}
	
	var_dump($data);
	
	echo "<br>\n";
	echo 'Время выполнения скрипта: '.round(microtime(true) - $start, 4).' сек.';
	
?>

Полный комплект PHP файлов примера и классов можно скачать по ссылке datacache_examlpe.zip.

Если запустить скрипт на сервере, то в результате получаем время выполнения скрипта - около 0.8 секунд. Это при первом запуске.

.....~~~~~......
.....~~~~~......
Время выполнения скрипта: 0.8024 сек.

Повторный запуск, если он был втечении установленного времени кеширования (по умолчанию 600 секунд) приводит к чтению данных из файла, что намного быстрее. В результате те же данные мы получаем за 0.08 секунды. А это в 10 раз быстрее! Иногда, в других примерах, разница во времени может быть и в 100 раз. Смотря какие данные вы кэшируете.

.....~~~~~......
.....~~~~~......
Время выполнения скрипта: 0.0879 сек.

Вывод При помощи класса DataCache можно организовать простое кэширование как объектов так и любых других данных, строк, массивов и т.д. Единственное, что необходимо, это уникальное название (id) для этих данных и свободное место на диске, чтобы DataCache мог повторно найти информацию по этим данным и вернуть её вам.

На этом всё. Можете использовать мой опыт и наработки в своих целяц, пишите комментарии и пожелания, если они есть.

Компания Counterpoint Technology Market Research прогнозирует быстрый рост востребованности услуг так называемого фиксированного беспроводного интернет-доступа — Fixed Wireless Access, или FWA. Технология...

Производитель смартфонов Realme только что анонсировала в Китае смартфон V5 5G в новой серии V. Он предлагает 6,5-дюймовый ЖК-экран Full HD+ с частотой 90 Гц (обновление сенсора — 180 Гц), занимающий...

Ассортимент компьютеров небольшого форм-фактора Zotac пополнился моделью Zbox Edge C1341, которая заключена в корпус с габаритами всего 147,2 × 147,2 × 32,1 мм. В новинке соседствуют аппаратная...

Компьютерный мирSector

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

Частичное или полное использование материалов сайта разрешается только при условии добавления ссылки на непосредственный адрес материала на нашем сайте.