Кэширование
Кэш ускоряет работу вашего приложения, сохраняя данные — однажды с трудом извлеченные — для использования в будущем. Мы покажем вам:
- Как использовать кэш
- Как изменить хранилище кэша
- Как правильно аннулировать кэш
Использование кэша в Nette очень простое, при этом он также покрывает очень сложные потребности в кэшировании. Он разработан для обеспечения производительности и 100% долговечности. В основном, вы найдете адаптеры для наиболее распространенных внутренних хранилищ. Позволяет аннулирование на основе тегов, защиту кэш-памяти, истечение времени и т. д.
Установка
Загрузите и установите пакет с помощью Composer:
Использование
Центром работы с кэшем является объект Nette\Caching\Cache. Мы создаем его
экземпляр и передаем конструктору в качестве параметра так называемое
хранилище. Это объект, представляющий место, где данные будут
физически храниться (база данных, Memcached, файлы на диске, …). Вы получаете
объект хранилища, передавая его с помощью внедрения зависимостей с типом
Nette\Caching\Storage
. Всё самое необходимое вы найдете в разделе Хранилища.
Для следующих примеров предположим, что у нас есть псевдоним
Cache
и хранилище в переменной $storage
.
Кэш фактически является хранилищем типа ключ-значение, поэтому мы читаем и записываем данные по ключам так же, как и ассоциативные массивы. Приложения состоят из нескольких независимых частей, и если бы все они использовали одно хранилище (например, один каталог на диске), рано или поздно произойдет столкновение ключей. Nette Framework решает эту проблему путем разделения всего пространства на пространства имен (подкаталоги). В этом случае каждая часть программы использует свое собственное пространство с уникальным именем, и коллизии не возникают.
Имя пространства указывается в качестве второго параметра конструктора класса Cache:
Теперь мы можем использовать объект $cache
для чтения и записи из
кэша. Для обоих используется метод load()
. Первый аргумент — ключ,
а второй — обратный вызов PHP, который вызывается, когда ключ не найден
в кэше. Обратный вызов генерирует значение, возвращает его и
кэширует:
Если второй параметр не указан ($value = $cache->load($key)
),
возвращается null
, если элемента нет в кэше.
Замечательно то, что кэшировать можно любые сериализуемые структуры, а не только строки. То же самое относится и к ключам.
Элемент удаляется из кэша с помощью метода remove()
:
Вы также можете кэшировать элементы с помощью метода
$cache->save($key, $value, array $dependencies = [])
. Однако вышеописанный метод с
использованием load()
предпочтительнее.
Мемоизация
Мемоизация означает кэширование результата функции или метода, чтобы вы могли использовать его в следующий раз вместо того, чтобы вычислять одно и то же снова и снова.
Методы и функции могут быть вызваны мемоизированно с помощью
call(callable $callback, ...$args)
:
Функция gethostbyaddr()
вызывается только один раз для каждого
параметра $ip
и в следующий раз будет возвращено значение
из кэша.
Также можно создать мемоизированную обёртку для метода или функции, которая может быть вызвана позже:
Истечение срока действия и аннулирование
При кэшировании необходимо решить вопрос о том, что некоторые из ранее сохраненных данных со временем станут недействительными. Nette Framework предоставляет механизм, как ограничить действительность данных и как удалить их контролируемым образом («сделать их недействительными», используя терминологию фреймворка).
Действительность данных устанавливается в момент сохранения с
помощью третьего параметра метода save()
, например:
Или используя параметр $dependencies
, переданный по ссылке в
обратный вызов в методе load()
, например:
Или используя 3-й параметр в методе load()
, например:
В следующих примерах мы будем предполагать второй вариант и,
следовательно, существование переменной $dependencies
.
Срок действия
Самое простое исключение — это ограничение по времени. Вот как кэшировать данные, действительные в течение 20 минут:
Если мы хотим увеличивать срок действия при каждом чтении, этого можно добиться таким образом, но учтите, что это увеличит накладные расходы кэша:
Удобной опцией является возможность разрешить истечение срока действия данных при изменении конкретного файла или одного из нескольких файлов. Это можно использовать, например, для кэширования данных, полученных в результате обработки этих файлов. Используйте абсолютные пути:
Мы можем позволить элементу в кэше истечь, когда истекает срок
действия другого элемента (или одного из нескольких других). Это можно
использовать, когда мы кэшируем всю HTML-страницу и её фрагменты под
другими ключами. Как только сниппет изменяется, вся страница
становится недействительной. Если у нас есть фрагменты, хранящиеся под
такими ключами, как frag1
и frag2
, мы будем использовать:
Срок действия также можно контролировать с помощью пользовательских
функций или статических методов, которые при чтении всегда решают,
действителен ли ещё элемент. Например, мы можем позволить элементу
истекать всякий раз, когда меняется версия PHP. Мы создадим функцию,
которая сравнивает текущую версию с параметром, и при сохранении
добавим массив в виде [имя функции, ...аргументы]
к
зависимостям:
Конечно, все критерии могут быть объединены. Срок действия кэша истекает, если хотя бы один критерий не выполнен.
Инвалидация с использованием тегов
Теги являются очень полезным инструментом признания недействительности. Мы можем назначить список тегов, которые являются произвольными строками, каждому элементу, хранящемуся в кэше. Например, предположим, что у нас есть HTML-страница со статьей и комментариями, которую мы хотим кэшировать. Поэтому мы указываем теги при сохранении в кэш:
Теперь перейдем к администрированию. Здесь у нас есть форма для
редактирования статьи. Вместе с сохранением статьи в базе данных мы
вызываем команду clean()
, которая удаляет кэшированные элементы
по тегам:
Аналогичным образом, в месте добавления нового комментария (или редактирования комментария) мы не забудем аннулировать соответствующий тег:
Чего мы достигли? Что наш HTML-кэш будет аннулирован (удален) при каждом
изменении статьи или комментариев. При редактировании статьи с ID =
10 тег article/10
принудительно аннулируется, а HTML-страница,
содержащая этот тег, удаляется из кэша. То же самое происходит при
вставке нового комментария под соответствующей статьей.
Тегам требуется Журнал.
Инвалидация по приоритету
Мы можем установить приоритет для отдельных элементов в кэше, и их можно будет удалять контролируемым образом, когда, например, кэш превысит определенный размер:
Удаляем все элементы с приоритетом, равным или меньшим 100:
Приоритетам также требуется Журнал.
Очистка кэша
Параметр Cache::All
очищает всё:
Массовое чтение
Для массового чтения и записи в кэш используется метод bulkLoad()
,
в котором мы передаем массив ключей и получаем массив значений:
Метод bulkLoad()
работает аналогично load()
со вторым
параметром обратного вызова, которому передается ключ
сгенерированного элемента:
Использование с PSR-16
Чтобы использовать Nette Cache с интерфейсом PSR-16, вы можете
воспользоваться сайтом PsrCacheAdapter
. Он позволяет легко
интегрировать Nette Cache с любым кодом или библиотекой, которая ожидает
наличия кэша, совместимого с PSR-16.
Теперь вы можете использовать $psrCache
в качестве кэша PSR-16:
Адаптер поддерживает все методы, определенные в PSR-16, включая
getMultiple()
, setMultiple()
и deleteMultiple()
.
Кэширование вывода
Выходные данные можно перехватывать и кэшировать очень элегантно:
В случае если вывод уже присутствует в кэше, метод capture()
печатает его и возвращает null
, поэтому условие не будет
выполнено. В противном случае он начинает буферизацию вывода и
возвращает объект $capture
, с помощью которого мы окончательно
сохраняем данные в кэш.
Кэширование в Latte
Кэширование в шаблонах Latte очень легко
настраивается, достаточно обернуть часть шаблона тегами
{cache}...{/cache}
. Кэш автоматически аннулируется при изменении
исходного шаблона (включая любые включенные шаблоны в тегах
{cache}
). Теги {cache}
могут быть вложенными, и когда вложенный
блок аннулируется (например, тегом), родительский блок также
аннулируется.
В теге можно указать ключи, к которым будет привязан кэш (здесь
переменная $id
) и установить срок действия и теги аннулирования.
Все параметры являются необязательными, поэтому вам не нужно указывать срок действия, теги или ключи.
Использование кэша также может быть обусловлено if
—
содержимое будет кэшироваться только при выполнении условия:
Хранилища
Хранилище — это объект, который представляет собой место физического хранения данных. Мы можем использовать базу данных, сервер Memcached или наиболее доступное хранилище, которым являются файлы на диске.
Хранение | Описание |
---|---|
FileStorage | хранение по умолчанию с сохранением в файлы на диске |
MemcachedStorage | используется сервер `Memcached |
MemoryStorage | данные временно находятся в памяти |
SQLiteStorage | данные хранятся в базе данных SQLite |
DevNullStorage | данные не хранятся — в целях тестирования |
Вы получаете объект хранилища, передавая его с помощью внедрения зависимостей с типом
Nette\Caching\Storage
. По умолчанию Nette предоставляет объект FileStorage,
который хранит данные в подпапке cache
в каталоге для временных файлов .
Вы можете изменить хранилище в конфигурации:
FileStorage
Записывает кэш в файлы на диске. Хранилище Nette\Caching\Storages\FileStorage
очень хорошо оптимизировано для производительности и, прежде всего,
обеспечивает полную атомарность операций. Что это значит? Чтобы при
использовании кэша не получилось так, что мы читаем файл, который ещё
не был полностью записан другим потоком, или чтобы кто-то удалил его
«из-под руки». Поэтому использование кэша полностью безопасно.
Это хранилище также имеет важную встроенную функцию, которая предотвращает экстремальное увеличение загрузки процессора, когда кэш очищается или охлаждается (т. е. не создается). Это «профилактика» cache stampede. Бывает так, что в один момент поступает несколько одновременных запросов, которые хотят получить из кэша одно и то же (например, результат большого SQL-запроса), а поскольку он не кэшируется, все процессы начинают выполнять один и тот же SQL-запрос. Нагрузка на процессор увеличивается в несколько раз, и может даже случиться так, что ни один поток не сможет ответить в отведенное время, кэш не будет создан, и приложение аварийно завершит работу. К счастью, кэш в Nette работает таким образом, что при наличии нескольких одновременных запросов на один элемент, он генерируется только первым потоком, остальные ждут и затем используют сгенерированный результат.
Пример создания FileStorage:
MemcachedStorage
Сервер Memcached — это высокопроизводительная
распределенная система хранения данных, адаптером которой является
Nette\Caching\Storages\MemcachedStorage
. В конфигурации укажите IP-адрес и порт,
если он отличается от стандартного 11211.
Требуется PHP-расширение memcached
.
MemoryStorage
Nette\Caching\Storages\MemoryStorage
это хранилище, которое хранит данные в
массиве PHP и, таким образом, теряется при завершении запроса.
SQLiteStorage
The SQLite database and adapter Nette\Caching\Storages\SQLiteStorage
offer a way to cache in a single file on
disk. The configuration will specify the path to this file.
Requires PHP extensions pdo
and pdo_sqlite
.
DevNullStorage
Особой реализацией хранилища является Nette\Caching\Storages\DevNullStorage
,
которая на самом деле не хранит данные вообще. Поэтому она подходит для
тестирования, если мы хотим исключить влияние кэша.
Использование кэша в коде
При использовании кэширования в коде у вас есть два способа, как это
сделать. Первый заключается в том, что вы получаете объект хранилища,
передавая его с помощью внедрения
зависимостей, а затем создаете объект Cache
:
Второй способ заключается в том, что вы получаете объект хранения
Cache
:
Затем объект Cache
создается непосредственно в конфигурации
следующим образом:
Журнал
Nette хранит теги и приоритеты в так называемом журнале. По умолчанию
для этого используются SQLite и файл journal.s3db
. Кроме того,
требуются PHP расширения pdo
и pdo_sqlite
.
Вы можете изменить журнал в конфигурации:
Услуги DI
Эти сервисы добавляются в контейнер DI:
Имя | Тип | Описание |
---|---|---|
cache.journal |
Nette\Caching\Storages\Journal | journal |
cache.storage |
Nette\Caching\Storage | репозиторий |
Отключение кэша
Одним из способов отключения кэширования в приложении является установка для хранилища значения DevNullStorage:
Данная настройка не влияет на кэширование шаблонов в Latte или контейнере DI, так как эти библиотеки не используют сервисы nette/caching и управляют своим кэшем самостоятельно. Более того, их кэш не нужно отключать в режиме разработки.