Получите бесплатно 4 курса для лёгкого старта работы в IT
Получить бесплатно
Главная БлогУскоряем PHP-проект с помощью кэширования
php

Ускоряем PHP-проект с помощью кэширования

Дата публикации: 29.09.2021
13 840
Время чтения: 8 минут
Дата обновления: 06.12.2023
Автор статьи:
Илья Зобенько
В статье рассказывается:

В статье рассказывается:

  1. Какие бывают подходы?
  2. Стандарт PSR-16
  3. Состояние гонки и обновление данных
  4. Заключение
  5. Пройди тест и узнай, какая сфера тебе подходит:
    айти, дизайн или маркетинг.
    Бесплатно от Geekbrains

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

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

Кэширование — это сохранение данных в высоко доступных местах на временной основе для того, чтобы их можно было получать быстрее, чем из оригинального источника. Самый распространенный пример применения кэша — получение данных из базы. При первом получении, допустим, продукта из базы данных, он сохраняется в кэш на определённое время, поэтому каждый следующий запрос к этому продукту уже не будет тревожить БД: данные будут получены из другого хранилища.

Какие бывают подходы?

Существует множество подходов к кэшированию. Список совместимых с PHP инструментов можно посмотреть на странице PHP-cache. Самые распространенные из них:

  • Apcu
  • Array
  • Memcached
  • Redis

Давайте разберемся, какие особенности есть у каждого из них и чем они отличаются друг от друга.

APCu

Один из самых распространённых и простых в настройке инструментов кэширования, сохраняет нужные нам данные в оперативную память. (Ещё умеет кэшировать промежуточный код, но это уже совсем другая история) Чтобы начать работу с APCu, необходимо убедиться, что он установлен. Для этого в командной строке запустите следующую команду:

php -i | grep 'apc.enabled'
# Ожидаем увидеть:
# apc.enabled => On => On

Другой способ проверки: создайте файл index.php и поместите в него вызов функции phpinfo(). Убедитесь, что у вас настроен веб-сервер для используемой директории и откройте скрипт в браузере через адрес сервера. Нас интересует секция APCu: если внутри неё есть пункт APCu Support: Enabled, значит всё хорошо, мы можем идти дальше.

APCu

Если APCu у вас не установлен, сделать это можно следующим способом:

  1. Запустите окно терминала (Linux/MacOS) или командную строку (Windows. Введите в поиске «cmd»).
  2. Выполните команду:
pecl install apcu apcu_bc

3. Откройте в любом текстовом редакторе файл конфигурации php.ini и убедитесь в наличии следующих строк:

# Windows
extension=php_apcu.dll
extension=php_apcu_bc.dll
 
apc.enabled=1
apc.enable_cli=1
 
#Linux / MacOS
extension="apcu.so"
extension="apc.so"
 
apc.enabled=1
apc.enable_cli=1
  1. Если указанных строк нет, добавьте их и сохраните файл конфигурации.
  2. Повторите проверку наличия установленного APCu.
Узнай, какие ИТ - профессии
входят в ТОП-30 с доходом
от 210 000 ₽/мес
Павел Симонов - исполнительный директор Geekbrains
Павел Симонов
Исполнительный директор Geekbrains
Команда GeekBrains совместно с международными специалистами по развитию карьеры подготовили материалы, которые помогут вам начать путь к профессии мечты.
Подборка содержит только самые востребованные и высокооплачиваемые специальности и направления в IT-сфере. 86% наших учеников с помощью данных материалов определились с карьерной целью на ближайшее будущее!

Скачивайте и используйте уже сегодня:

Павел Симонов - исполнительный директор Geekbrains
Павел Симонов
Исполнительный директор Geekbrains
pdf иконка

Топ-30 самых востребованных и высокооплачиваемых профессий 2023

Поможет разобраться в актуальной ситуации на рынке труда

doc иконка

Подборка 50+ бесплатных нейросетей для упрощения работы и увеличения заработка

Только проверенные нейросети с доступом из России и свободным использованием

pdf иконка

ТОП-100 площадок для поиска работы от GeekBrains

Список проверенных ресурсов реальных вакансий с доходом от 210 000 ₽

pdf 3,7mb
doc 1,7mb
Уже скачали 30006 pdf иконка

Для использования этого подхода кэширования нам понадобятся основные функции. Вот пример их применения:

$cacheKey = 'product_1';
$ttl = 600; // 10 минут.
 
// Проверка доступности APCu
$isEnabled = apcu_enabled();
 
// Проверяет, есть ли данные в кэше по ключу
$isExisted = apcu_exists($cacheKey);
 
// Сохраняет данные в кэш. В случае успеха возвращает true
// Аргумент $ttl определяет, как долго будет храниться кэш (секунды)
$isStored = apcu_store($cacheKey, ['name' => 'Demo product'], $ttl);
 
// Получает данные из кэша по ключу. В случае их отсутствия, вернет false
$data = apcu_fetch($cacheKey);
 
// Удаляет данные из кэша по ключу
$isDeleted = apcu_delete($cacheKey);
 
var_dump([
    'is_enabled'   => $isEnabled,
    'is_existed'   => $isExisted,
    'is_stored'    => $isStored,
    'is_deleted'   => $isDeleted,
    'fetched_data' => $data,
]);

Любой кэш работает по принципу key-value хранилища: это значит, что данные сохраняются со специальным ключом, по которому и происходит обращение. В данном случае ключ хранится в переменной $cacheKey.

Важно! Этот подход работает только при работе в режиме веб-сайта, то есть при запуске из командной строки вы не будете получать данные из кэша, а всё, что вы в него сохранили, будет очищено по завершению работы скрипта. Однако это не вызовет никаких ошибок.

Array-кэш

Более простой, но не всегда применимый метод кэширования. Если APCu сохраняет данные и делает их доступными для последующих выполнений всеми процессами, то Array-кэш хранит их только в рамках обрабатываемого запроса.

Что это значит? Представим, что у вас есть страница с комментариями пользователей. Один пользователь может оставить несколько сообщений, и когда мы будем собирать массив этих данных, нам не захочется несколько раз ходить в базу данных за одним и тем же пользователем. Что мы можем сделать, так это сохранить полученные данные в массив, чтобы при его наличии не делать повторный запрос.

Этот принцип очень прост и так же просто реализуется. Давайте напишем класс, который будет выполнять подобное сохранение:

class CustomArrayCache
{
    /**
     * Массив приватный и статический
     * - приватный — чтобы обращаться к нему можно было только
     * из методов класса.
     * - статический — чтобы свойство было доступно во всех экземплярах
     */
    private static array $memory = [];
 
    // Метод сохранения данных в памяти
    public function store(string $key, $value): bool
    {
        self::$memory[$key] = $value;
 
        return true;
    }
 
    // Метод получения данных из памяти
    public function fetch(string $key)
    {
        return self::$memory[$key] ?? null;
    }
 
    // Метод удаления данных из памяти
    public function delete(string $key): bool
    {
        unset(self::$memory[$key]);
 
        return true;
    }
 
    // Метод проверки наличия данных по ключу
    public function exists(string $key): bool
    {
        return array_key_exists($key, self::$memory);
    }
}

Из-за своей ограниченности этот подход применяется редко, однако знать о нём полезно.

Memcached и Redis

Наиболее продвинутые подходы кэширования. Подразумевают наличие запущенного отдельно сервера Memcached или Redis. Из PHP мы подключаемся к этому серверу по адресу и порту. Конфигурация этих решений сложнее, чем настройка APCu, но метод хранения данных очень похож: оперативная память. Самыми главными их преимуществами являются

  • изолированность от PHP: за кэш отвечают отдельные сервисы;
  • возможность кластеризации: если нагрузка на ваш проект очень велика, кластеризация сервисов кэширования поможет с ней справиться.

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

Стандарт PSR-16

В PSR есть два стандарта, посвящённых кэшированию: PSR-6 (обычный интерфейс кэширования) и PSR-16 (простой интерфейс кэширования) — мы сосредоточимся на PSR-16.

Этот стандарт предлагает специальный интерфейс (CacheInterface), которому могут удовлетворять классы, выполняющие функцию кэширования. Согласно ему, такие классы должны реализовывать следующие методы:

  • get($key, $default) — получение данных из кэша: вторым аргументом передаётся значение, которое будет возвращено в случае отсутствия этих данных;
  • set($key, $value, $ttl = null) — сохранение данных в кэш: как мы уже видели ранее, третьим параметром передаётся время хранения в секундах. Если оставить его пустым (null), значение будет подставлено по умолчанию из конфигурации кэша;
  • delete($key) — удаляет данные по ключу;
  • clear() — очищает все хранилище;
  • getMultiple($keys, $default) — позволяет получить данные сразу по нескольким ключам;
  • setMultiple($values, $ttl = null) — позволяет записать сразу несколько значений. В качестве $value мы передаем ассоциативный массив, где ключ — $key для кэша, а значение — данные для сохранения;
  • deleteMultiple($keys) — удаляет данные по нескольким ключам;
  • has($key) — проверяет наличие данных по ключу.

Как вы можете заметить, интерфейс очень прост, и даже тех функций, что мы рассмотрели в примере с APCu, достаточно для того, чтобы написать свой сервис кэша в соответствии с PSR-16. Но зачем это нужно?

Главные преимущества соблюдения стандартов PSR заключаются в том, что:

  • они поддерживаются большинством популярных библиотек;
  • многие PHP-программисты придерживаются PSR и с легкостью освоятся в вашем коде;
  • благодаря интерфейсу, мы можем легко подменять используемый сервис на любой другой, поддерживающий PSR-16.

Давайте подробнее рассмотрим последний пункт и его преимущества.

Дарим скидку от 60%
на курсы от GeekBrains до 22 сентября
Уже через 9 месяцев сможете устроиться на работу с доходом от 150 000 рублей
Забронировать скидку

Подключение PSR-16 библиотек

Библиотеки, создающие «обертку» над существующими инструментами кэширования для соответствия интерфейсу называются адаптерами. Для примера, рассмотрим адаптеры тех методов, что мы уже обсудили:

Все они удовлетворяют PSR-16 и поэтому применяются одинаково, однако логика «под капотом» у каждого своя.

Для примера давайте загрузим APCu- и Array-адаптеры в наш проект с помощью Composer.

composer require cache/array-adapter
composer require cache/apcu-adapter
# Или
composer req cache/apcu-adapter cache/array-adapter

Давайте представим, что у нас есть специальный класс для получения продуктов из базы данных. Назовем его ProductRepository, у него есть метод find($id), который возвращает продукт по его идентификатору, а если такого продукта нет — null.

class ProductRepository
{
    /**
     * Чтобы не усложнять пример, обусловимся, что в качестве продукта
     * возвращается массив, а если его нет — null
     */
    public function find(int $id): ?array
    {
        // ...
        // Получаем данные из БД
        return $someProduct;
    }
}

Если мы хотим подключить кэширование, мы не должны делать это внутри репозитория, потому что его ответственность — возвращать данные из базы данных. Куда же мы тогда добавим кэш?

Есть несколько популярных решений, самое простое — дополнительный класс-провайдер. Всё, что он будет делать — пробовать получить данные из кэша, а если это не получится — обратится в репозиторий. Для этого в конструкторе такого класса определим две зависимости — наш репозиторий и CacheInterface. Почему именно интерфейс?

Потому что так мы сможем использовать абсолютно любой из упомянутых адаптеров или других классов, удовлетворяющих PSR-16.

class ProductDataProvider
{
   private ProductRepository $productRepository;
   private CacheInterface $cache;
 
   public function __construct(ProductRepository $productRepository, CacheInterface $cache)
   {
       $this->productRepository = $productRepository;
       $this->cache             = $cache;
   }
 
   public function get(int $productId): ?array
   {
       $cacheKey = sprintf('product_%d', $productId);
 
       // Пробуем получить продукт из кэша
       $product = $this->cache->get($cacheKey);
       if ($product !== null) {
           // Если продукт есть, возвращаем
           // Временно выведем echo, чтобы понять, что данные из кэша
           echo 'Данные из кэша' . PHP_EOL; // PHP_EOL - перенос строки
           return $product;
       }
       // Если продукта нет, получаем его из репозитория
       $product = $this->productRepository->find($productId);
 
       if ($product !== null) {
           // Теперь сохраним полученный продукт в кэш для будущих запросов
           // Также временно выведем echo
           echo 'Данные из БД' . PHP_EOL;
           $this->cache->set($cacheKey, $product);
       }
 
       return $product;
   }
}

Наш класс готов. Теперь давайте рассмотрим его применение в сочетании с APCu-адаптером.

use Cache\\Adapter\\Apcu\\ApcuCachePool;
 
// Подключаем автозагрузчик Composer
require_once 'vendor/autoload.php';
 
// Наш репозиторий
$productRepository = new ProductRepository();
// APCu-кэш адаптер. Не требует никаких дополнительных настроек
$cache = new ApcuCachePool();
 
// Создаем провайдер, передаем зависимости
$productDataProvider = new ProductDataProvider(
    $productRepository,
    $cache
);
 
// Если в БД есть такой продукт, он к нам вернется
$product = $productDataProvider->get(1);
var_dump($product);

Если же мы захотим, заменить APCu-кэширование на Array-адаптер или любой другой, мы просто передадим новый подход в провайдер вместо старого, потому что все они реализуют CacheInterface.

use Cache\\Adapter\\PHPArray\\ArrayCachePool;
// ...
$productRepository = new ProductRepository();
//$cache = new ApcuCachePool();
$cache = new ArrayCachePool();
$productDataProvider = new ProductDataProvider(
    $productRepository,
    $cache
);
// ...
Только до 23.09
Скачай подборку материалов, чтобы гарантированно найти работу в IT за 14 дней
Список документов:
ТОП-100 площадок для поиска работы от GeekBrains
20 профессий 2023 года, с доходом от 150 000 рублей
Чек-лист «Как успешно пройти собеседование»
Чтобы получить файл, укажите e-mail:
Введите e-mail, чтобы получить доступ к документам
Подтвердите, что вы не робот,
указав номер телефона:
Введите телефон, чтобы получить доступ к документам
Уже скачали 52300

Состояние гонки и обновление данных

Кэш работает до тех пор, пока мы содержим его в актуальном состоянии. Это значит, что, если пользователь хочет обновить продукт, то продукт должен обновиться и в базе данных, и в нашем кэше. Однако здесь есть один важный нюанс.

Представим, что нашим проектом пользуется очень большое количество пользователей, и двое из них одновременно обновляют одну и ту же сущность. В этом случае, может возникнуть такая ситуация:

  • пользователь 1 получил сущность из кэша;
  • пользователь 1 обновил сущность в БД;
  • пользователь 2 получил сущность из кэша;
  • пользователь 1 обновил данные в кэше;
  • пользователь 2 обновил сущность в БД, но перезаписал её старыми данными, потому что сущность была неактуальна на момент получения и т. д.

Такая ситуация называется состоянием гонки, когда несколько процессов обращаются одновременно к одному и тому же ресурсу, и может возникнуть конфликт версий. Чтобы избежать такой проблемы, следует придерживаться одного простого правила:

Когда вы получаете любую сущность в коде с целью её обновления, всегда используйте данные из БД.

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

Вы можете либо обращаться в нужных местах к ProductRepository вместо ProductDataProvider, либо добавить аргумент к методу DataProvider. Например, такой ($fromCache):

class ProductDataProvider
{
    // ...
    public function get(int $productId, bool $fromCache = true): ?array
    {
        $cacheKey = sprintf('product_%d', $productId);
 
        $product = $fromCache ? $this->cache->get($cacheKey) : null;
        if ($product !== null) {
            return $product;
        }
        $product = $this->productRepository->find($productId);
 
        if ($product !== null) {
            $this->cache->set($cacheKey, $product);
        }
 
        return $product;
    }
}

Заключение

Кэширование требует от разработчика дополнительных усилий при разработке проекта, и его применение не всегда может быть целесообразно. Решение применять его или нет должно быть основано на предполагаемой (или фактической) нагрузке и ваших ожиданиях от скорости отклика пользователю.

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

Подводя итог, повторим ключевые идеи статьи:

  • Соблюдение PSR-16 (или PSR-6) позволит вам с легкостью подключить для кэширования стороннюю библиотеку и сделает ваш код понятным другим разработчикам.
  • Для небольших проектов хорошим решением для кэширования станет APCu, т. к. он прост в настройке и использует оперативную память, доступ к которой очень высокий.
  • Для всех совместимых с PHP-инструментов кэширования есть адаптеры, которые можно посмотреть на сайте php-cache.com.
  • Кэширование — отдельная ответственность. Старайтесь реализовывать работу с кэшем в отдельных классах.
  • Если мы собираемся обновить сущность, её следует получать из БД. Если сущность нужна нам только для просмотра — мы можем запросить её из кэша.
  • В крупных проектах для получения возможности масштабирования применяются Memcached или Redis.
Автор статьи:
Илья Зобенько
Оцените статью:
5
Добавить комментарий

Сортировать:
По дате публикации
По рейтингу
Читайте также
prev
next
Бесплатные вебинары:
prev
next
Как работает дизайн-студия на примере одного кейса 

Как работает дизайн-студия на примере одного кейса 

Узнать подробнее
Инновационные подходы к обучению информационным технологиям

Инновационные подходы к обучению информационным технологиям

Узнать подробнее
Как стать Python-разработчиком

Как стать Python-разработчиком

Узнать подробнее
Что нужно знать разработчику

Что нужно знать разработчику

Узнать подробнее
Кто такой тестировщик и как им стать

Кто такой тестировщик и как им стать

Узнать подробнее
Чем занимается программист и как им стать

Чем занимается программист и как им стать

Узнать подробнее
Как искусственный интеллект помогает и мешает задачам кибербезопасности

Как искусственный интеллект помогает и мешает задачам кибербезопасности

Узнать подробнее
Бесплатный вебинар про внедрение искусственного интеллекта

Бесплатный вебинар про внедрение искусственного интеллекта

Узнать подробнее
Какие есть профессии в ИТ

Какие есть профессии в ИТ

Узнать подробнее
Смените профессию,
получите новые навыки,
запустите карьеру
Поможем подобрать обучение:
Забрать подарок

Получите подробную стратегию для новичков на 2023 год, как с нуля выйти на доход 200 000 ₽ за 7 месяцев

Подарки от Geekbrains из закрытой базы:
Осталось 17 мест

Поздравляем!
Вы выиграли 4 курса по IT-профессиям.
Дождитесь звонка нашего менеджера для уточнения деталей

Иван Степанин
Иван Степанин печатает ...