Кеш

Дата обновления перевода: 2023-01-20

Кеш

Использование кеша - прекрасный способ ускорить ваше приложение. Компонент 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 ключи как:

  • YAML
  • XML
  • PHP
1
2
3
4
5
# config/packages/cache.yaml
framework:
    cache:
        app: cache.adapter.filesystem
        system: cache.adapter.system

Tip

Хотя возможно повторно сконфигурировать кеш system, рекомендуется оставить конфиуграцию по умолчанию применённую к нему Symfony.

Компонент Cache поставляется с набором сконфигурированных адаптеров:

Некоторые из этих адаптеров могут быть настроены с помощью сокращений. При использовании этих сокращений создадутся пулы с id сервисов вида cache.[type].

  • YAML
  • XML
  • PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
# config/packages/cache.yaml
framework:
    cache:
        directory: '%kernel.cache_dir%/pools' # Используется только с cache.adapter.filesystem

        # service: cache.psr6
        default_psr6_provider: 'app.my_psr6_service'
        # service: cache.redis
        default_redis_provider: 'redis://localhost'
        # service: cache.memcached
        default_memcached_provider: 'memcached://localhost'
        # service: cache.pdo
        default_pdo_provider: 'doctrine.dbal.default_connection'

Создание пользовательских пулов (с пространством имён)

Вы также можете создать пулы с другими настройками:

  • YAML
  • XML
  • PHP
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
use Symfony\Contracts\Cache\CacheInterface;

// в методе контроллера
public function listProducts(CacheInterface $customThingCache)
{
    // ...
}

// в сервисе
public function __construct(CacheInterface $customThingCache)
{
    // ...
}

Tip

Если вам нужно, чтобы пространтво имён было интероперабельно со сторонним приложением, то вы можете контролировать авто-генерирование, установив атрибут namespace сервисного тега cache.pool. К примеру, вы можете переопределить сервисное определение адаптера:

  • YAML
  • XML
  • PHP
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 и использовать его при конфигурации пула.

  • YAML
  • XML
  • PHP
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 автоматически сохраняет элемент во всех недостающих пулах.

  • YAML
  • XML
  • PHP
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
30
31
use Symfony\Contracts\Cache\ItemInterface;
use Symfony\Contracts\Cache\TagAwareCacheInterface;

class SomeClass
{
    private $myCachePool;

    // используя автоподключение для внедрения пула кеша
    public function __construct(TagAwareCacheInterface $myCachePool)
    {
        $this->myCachePool = $myCachePool;
    }

    public function someMethod()
    {
        $value0 = $this->myCachePool->get('item_0', function (ItemInterface $item) {
            $item->tag(['foo', 'bar']);

            return 'debug';
        });

        $value1 = $this->myCachePool->get('item_1', function (ItemInterface $item) {
            $item->tag('foo');

            return 'debug';
        });

        // Удалить все ключи кеша с тегом "bar"
        $this->myCachePool->invalidateTags(['bar']);
    }
}

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

  • YAML
  • XML
  • PHP
1
2
3
4
5
6
7
# config/packages/cache.yaml
framework:
    cache:
        pools:
            my_cache_pool:
                adapter: cache.adapter.redis
                tags: true

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

  • YAML
  • XML
  • PHP
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 cache.global_clearer

Очистить кеш по тегу(ам):

6.1

Команда cache:pool:invalidate-tags была представлена в Symfony 6.1.

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, используя этот ключ:

  • YAML
  • XML
  • PHP
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'

Caution

Это зашифрует значения объектов кеша, но не ключи кеша. Будьте осторожны, чтобы не допустить утечки конфиденциальных данных в ключах кеша.

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