Кэш
Дата обновления перевода: 2025-02-03
Кэш
Использование кэша - прекрасный способ ускорить ваше приложение. Компонент Symfony cache поставляется с множеством адаптеров для разных хранилищ. Каждый адаптер разработан для высокой производительности.
Следующий пример показывает типичное использование кэша:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
use Symfony\Contracts\Cache\ItemInterface;
// Вызываемое будет запущено только при отсутствии значения в кэше
$value = $pool->get('my_cache_key', function (ItemInterface $item) {
$item->expiresAfter(3600);
// ... сделать HTTP запрос или сложные вычисления
$computedValue = 'foobar';
return $computedValue;
});
echo $value; // 'foobar'
// ... и удалить ключ кэша
$pool->delete('my_cache_key');
Symfony поддерживает Cache Contracts, PSR-6/16 и интерфейсы Doctrine Cache. Вы можете прочитать больше о них в документации компонента.
Конфигурирование кэша с FrameworkBundle
Когда настраиваете компонент кэша есть несколько концепций, о которых нужно знать:
- Пул
- Это сервис с которым вы будете взаимодействовать. Каждый пул будет всегда иметь своё пространство имён и закэшированные элементы. Не бывает конфликтов между разными пулами.
- Адаптер
- Адаптер - это шаблон, который вы используете для создания пула.
- Провайдер
- Провайдер - это сервис, который адаптеры используют для подключения к хранилищу. Примерами таких адаптеров являются Redis и Memcached. Если как провайдер используется DSN, то автоматически создаётся сервис.
Есть 2 пула, включённые по умолчанию. Это cache.app
и
cache.system
. Системный кэк используется для вещей вроде аннотаций, сериализатора
и валидации. cache.app
может использоваться в вашем коде. Вы можете настроить какой
адаптер (шаблон) они будут использовать используя app
и system
ключи как:
1 2 3 4 5
# config/packages/cache.yaml
framework:
cache:
app: cache.adapter.filesystem
system: cache.adapter.system
Tip
Хотя возможно повторно сконфигурировать кэш system
, рекомендуется оставить
конфиуграцию по умолчанию применённую к нему Symfony.
Компонент Cache поставляется с набором сконфигурированных адаптеров:
- cache.adapter.apcu
- cache.adapter.array
- cache.adapter.doctrine_dbal
- cache.adapter.filesystem
- cache.adapter.memcached
- cache.adapter.pdo
- cache.adapter.psr6
- cache.adapter.redis
- cache.adapter.redis_tag_aware (Redis adapter optimized to work with tags)
Note
Существует также специальный адаптер cache.adapter.system
. Рекомендуется
использовать его для системного кэша . Этот адаптер использует некоторую
логику для динамического выбора наилучшего хранилища в зависимости от вашей системы
(либо файлы PHP, либо APCu).
Некоторые из этих адаптеров могут быть настроены с помощью сокращений. При использовании
этих сокращений создадутся пулы с id сервисов вида cache.[type]
.
1 2 3 4 5 6 7 8 9 10
# config/packages/cache.yaml
framework:
cache:
directory: '%kernel.cache_dir%/pools' # Используется только с cache.adapter.filesystem
default_doctrine_dbal_provider: 'doctrine.dbal.default_connection'
default_psr6_provider: 'app.my_psr6_service'
default_redis_provider: 'redis://localhost'
default_memcached_provider: 'memcached://localhost'
default_pdo_provider: 'pgsql:host=localhost'
7.1
Использование DSN в качестве провайдера для адаптера PDO было представлено в Symfony 7.1.
Создание пользовательских пулов (с пространством имён)
Вы также можете создать пулы с другими настройками:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
# config/packages/cache.yaml
framework:
cache:
default_memcached_provider: 'memcached://localhost'
pools:
# создаёт сервис "custom_thing.cache"
# автоподключаемый через "CacheInterface $customThingCache"
# использует конфигурацию кэша "app"
custom_thing.cache:
adapter: cache.app
# создаёт сервис "my_cache_pool"
# автоподключаемый через "CacheInterface $myCachePool"
my_cache_pool:
adapter: cache.adapter.filesystem
# использует настройку выше default_memcached_provider
acme.cache:
adapter: cache.adapter.memcached
# управление конфигурацией адаптера
foobar.cache:
adapter: cache.adapter.memcached
provider: 'memcached://user:password@example.com'
# использует пул "foobar.cache" как бекенд, но настраивает
# время жизни и как другие пулы выше имеет собственное пространство имён элементов кэша
short_cache:
adapter: foobar.cache
default_lifetime: 60
Каждый пул управляет набором независимых ключей кэша: ключи из разных пулов никогда не пересекаются, даже, если они используют один и тот же бекэнд. Это достигается добавлением префиксов к ключам с пространством имён, которое генерируется хешированием названия пула, имени скомпилированного класса контейнера и конфигурируемым seed который по умолчанию равняется каталогу проекта.
Каждый пользовательский пул становится сервисом, чей id является именем пула
(например, custom_thing.cache
). Алиас для автоподключения также создайтся для каждого пула
используя camel case версию его имени - например custom_thing.cache
может
автовнедряться при названии аргумента $customThingCache
с типом
CacheInterface или
Psr\Cache\CacheItemPoolInterface
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
use Symfony\Contracts\Cache\CacheInterface;
// ...
// в методе контроллера
public function listProducts(CacheInterface $customThingCache): Response
{
// ...
}
// в сервисе
public function __construct(private CacheInterface $customThingCache)
{
// ...
}
Tip
Если вам нужно, чтобы пространтво имён было интероперабельно со сторонним приложением,
то вы можете контролировать авто-генерирование, установив атрибут namespace
сервисного тега cache.pool
. К примеру, вы можете
переопределить сервисное определение адаптера:
1 2 3 4 5 6 7 8
# config/services.yaml
services:
# ...
app.cache.adapter.redis:
parent: 'cache.adapter.redis'
tags:
- { name: 'cache.pool', namespace: 'my_custom_namespace' }
Пользовательские настройки провайдеров
Некоторые провайдеры имеют специфические настройки конфигурации.
RedisAdapter позволяет вам
создавать провайдеров с настройками timeout
, retry_interval
и так далее. Для использования
этих настроек со значениями не по умолчанию нужно создать собственный провайдер \Redis
и использовать его при конфигурации пула.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
# config/packages/cache.yaml
framework:
cache:
pools:
cache.my_redis:
adapter: cache.adapter.redis
provider: app.my_custom_redis_provider
services:
app.my_custom_redis_provider:
class: \Redis
factory: ['Symfony\Component\Cache\Adapter\RedisAdapter', 'createConnection']
arguments:
- 'redis://localhost'
- { retry_interval: 2, timeout: 10 }
Создание цепочки кэша
Разные адаптеры кэша имеют свои сильные и слабые стороны. Некоторые могут быть очень быстрыми, но оптимизированы для хранения небольших элементов, а некоторые могут хранить много данных, но достаточно медленные. Для получения лучшего от каждого вы можете использовать цепочку адаптеров.
Цепочка кэша объединяет несколько пулов кэшей в один. При сохранении элемента в цепочку кэшей Symfony сохраняет его последовательно во все пулы. При получении элемента Symfony пытается получить его из первого пула. Если он не найден, пробует следующие пулы пока не найдёт элемент или не выбросится исключение. Из-за такого поведения рекомендуется определять адаптеры в цепочке начиная с самого быстрого и заканчивая самым медленным.
Если случается ошибка при сохранении элемента в пул, Symfony сохраняет его в других пулах и не выбрасывается исключение. Позже, при получении элемента, Symfony автоматически сохраняет элемент во всех недостающих пулах.
1 2 3 4 5 6 7 8 9 10
# config/packages/cache.yaml
framework:
cache:
pools:
my_cache_pool:
default_lifetime: 31536000 # Один год
adapters:
- cache.adapter.array
- cache.adapter.apcu
- {name: cache.adapter.redis, provider: 'redis://user:password@example.com'}
Использование тегов кэша
В приложениях с большим количеством ключей кэша может быть полезно организовать сохранённые данные чтобы более эффективно инвалидировать кэш. Один из вариантов - использовать теги кэша. К элементу кэша можно добавить один или несколько тегов. Все элементы с тем же тегом могут быть инвалидированы с помощью вызова одной функции:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
use Symfony\Contracts\Cache\ItemInterface;
use Symfony\Contracts\Cache\TagAwareCacheInterface;
class SomeClass
{
// используя автоподключение для внедрения пула кэша
public function __construct(
private TagAwareCacheInterface $myCachePool,
) {
}
public function someMethod(): void
{
$value0 = $this->myCachePool->get('item_0', function (ItemInterface $item): string {
$item->tag(['foo', 'bar']);
return 'debug';
});
$value1 = $this->myCachePool->get('item_1', function (ItemInterface $item): string {
$item->tag('foo');
return 'debug';
});
// Удалить все ключи кэша с тегом "bar"
$this->myCachePool->invalidateTags(['bar']);
}
}
Чтобы эта функция работала, нужно, чтобы адаптер кэша реализовал интерфейс TagAwareCacheInterface. Это можно добавить, используя следующую конфигурацию.
1 2 3 4 5 6 7
# config/packages/cache.yaml
framework:
cache:
pools:
my_cache_pool:
adapter: cache.adapter.redis
tags: true
Теги хранятся в том же пуле по умолчанию. Это хорошо в большинстве случаев. Но иногда может быть лучше хранить теги в другом пуле. Этого можно достичь, указав адаптер.
1 2 3 4 5 6 7 8 9
# config/packages/cache.yaml
framework:
cache:
pools:
my_cache_pool:
adapter: cache.adapter.redis
tags: tag_pool
tag_pool:
adapter: cache.adapter.apcu
Note
Интерфейс TagAwareCacheInterface
при автоподключении использует сервис cache.app
.
Очистка кэша
Для очистки кэша можно использовать команду bin/console cache:pool:clear [pool]
.
Это удалит все записи из вашего хранилища и нужно будет пересчитать
все значения. Вы также можете сгруппировать ваши пулы в "очистители кэша".
По умолчанию есть 3 очистителя кэша:
cache.global_clearer
cache.system_clearer
cache.app_clearer
Глобальный очиститель удалит все элементы кэша в каждом пуле. Системный очиститель кэша
используется при команде bin/console cache:clear
. App clearer - это очиститель по умолчанию.
Для просмотра всех доступных пулов кэша:
1
$ php bin/console cache:pool:list
Очистить один пул:
1
$ php bin/console cache:pool:clear my_cache_pool
Очистить все пользовательские пулы:
1
$ php bin/console cache:pool:clear cache.app_clearer
Очистить все пулы кэша:
1
$ php bin/console cache:pool:clear --all
Очистить все пулы кэша, кроме некоторых:
1
$ php bin/console cache:pool:clear --all --exclude=my_cache_pool --exclude=another_cache_pool
Очистить все кэши везде:
1
$ php bin/console cache:pool:clear cache.global_clearer
Очистить кэш по тегу(ам):
1 2 3 4 5 6 7 8 9 10 11
# инвалидировать tag1 из всех тегируемых пулов
$ php bin/console cache:pool:invalidate-tags tag1
# инвалидировать tag1 и tag2 из всех тегируемых пулов
$ php bin/console cache:pool:invalidate-tags tag1 tag2
# инвалидировать tag1 и tag2 из пула cache.app
$ php bin/console cache:pool:invalidate-tags tag1 tag2 --pool=cache.app
# инвалидировать tag1 и tag2 из пулов cache1 и cache2
$ php bin/console cache:pool:invalidate-tags tag1 tag2 -p cache1 -p cache2
Шифрование кэша
Для того, чтобы зашифровать кэш, используя libsodium
, вы можете использовать
SodiumMarshaller.
Для начала, вам нужно сгенерировать безопасный ключ и добавить его в своё
хранилище секретов в виде CACHE_DECRYPTION_KEY
:
1
$ php -r 'echo base64_encode(sodium_crypto_box_keypair());'
Затем, зарегистрируйте сервис SodiumMarshaller
, используя этот ключ:
1 2 3 4 5 6 7 8 9 10 11
# config/packages/cache.yaml
# ...
services:
Symfony\Component\Cache\Marshaller\SodiumMarshaller:
decorates: cache.default_marshaller
arguments:
- ['%env(base64:CACHE_DECRYPTION_KEY)%']
# использовать несколько ключей, чтобы менять их
#- ['%env(base64:CACHE_DECRYPTION_KEY)%', '%env(base64:OLD_CACHE_DECRYPTION_KEY)%']
- '@Symfony\Component\Cache\Marshaller\SodiumMarshaller.inner'
Danger
Это зашифрует значения объектов кэша, но не ключи кэша. Будьте осторожны, чтобы не допустить утечки конфиденциальных данных в ключах кэша.
При конфигурации нескольких ключей, первый ключ будет использоваться для чтения
и записи, а дополнительный(е) ключ(и) будут использоваться только для чтения.
Когдща все объекты кэша будут зашифрованы, а у старого ключа истечет срок годности,
вы можете полностью удалить OLD_CACHE_DECRYPTION_KEY
.
Асинхронное вычисление значений кэша
Компонент Cache использует алгоритм вероятностного досрочного истечения срока действия для защиты от проблемы давки кэша . Это означает, что некоторые элементы кэша выбираются для досрочного истечения, пока они еще свежие.
По умолчанию элементы кэша с истекшим сроком действия вычисляются синхронно. Однако вы можете вычислять их асинхронно, делегировав вычисление значений фоновому работнику с помощью компонента Messenger. В этом случае, при запросе элемента, его кэшированное значение будет немедленно возвращено, а EarlyExpirationMessage развертывается через автобус Messenger.
Когда это сообщение обрабатывается потребителем сообщений, обновленное значение кэша вычисляется асинхронно. В следующий раз, когда элемент будет запрошен, обновленное значение будет свежим и возвращенным.
Сначала создайте сервис, который будет вычислять значение элемента:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// src/Cache/CacheComputation.php
namespace App\Cache;
use Symfony\Contracts\Cache\ItemInterface;
class CacheComputation
{
public function compute(ItemInterface $item): string
{
$item->expiresAfter(5);
// это просто рандомный пример; тут вы должны провести собственные вычисления
return sprintf('#%06X', mt_rand(0, 0xFFFFFF));
}
}
Это значение кэша будет запрашиваться у контроллера, другого сервиса и т. д. В следующем примере значение запрашивается у контроллера:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
// src/Controller/CacheController.php
namespace App\Controller;
use App\Cache\CacheComputation;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface;
class CacheController extends AbstractController
{
#[Route('/cache', name: 'cache')]
public function index(CacheInterface $asyncCache): Response
{
// передать кэшу метод сервиса, который обновляет объект
$cachedValue = $asyncCache->get('my_value', [CacheComputation::class, 'compute'])
// ...
}
}
Наконец, сконфигурируйте новый пул кэша (например, под названием async.cache
), который
будет использовать автобус сообщений для вычисления значений в работнике:
1 2 3 4 5 6 7 8 9 10 11 12
# config/packages/framework.yaml
framework:
cache:
pools:
async.cache:
early_expiration_message_bus: messenger.default_bus
messenger:
transports:
async_bus: '%env(MESSENGER_TRANSPORT_DSN)%'
routing:
'Symfony\Component\Cache\Messenger\EarlyExpirationMessage': async_bus
Теперь вы можете запустить потребителя:
1
$ php bin/console messenger:consume async_bus
Вот и все! Теперь при запросе элемента из этого пула кэша его кэшированное значение будет возвращено немедленно. Если он выбран для досрочного истечения срока действия, то будет отправлено сообщение в автобус, чтобы запланировать фоновые вычисления для обновления значения.