Кеш

Дата оновлення перекладу 2024-06-07

Кеш

Використання кешу - чудовий спосіб прискорити ваш додаток. Компонент 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): string {
    $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

Коли ви налаштовуєте компонент Cache, є декілька концепцій, про які варто знати:

Пул
Це сервіс з яким ви будете взаємодіяти. Кожний пул завжди буде мати свій простір імен і кешовані елементи. Н буває конфліктів між різними пулами.
Адаптер
Адаптер - це шаблон, який ви використовуєте для створення пулу.
Провайдер
Провайдер - це сервіс, який адаптери використовують для підключення до сховища. Прикладами таких адаптерів є 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 постачається з набором сконфігурованих адаптерів:

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;
// ...

// from a controller method
public function listProducts(CacheInterface $customThingCache): Response
{
    // ...
}

// in a service
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  # One year
                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 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'

Caution

Це зашифрує значення обʼєктів кешу, але не ключі кешу. Будьте обережні, щоб не припускати витіку конфіденційних даних у ключах кешу.

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

Асинхронне обчислення значень кешу

Компонент Cache використовує алгоритм імовірнісного дострокового закінчення терміну дії для захисту від проблеми cache stampede . Це означає, що деякі елементи кешу обираються для дострокового закінчення терміну дії, поки вони поки вони ще свіжі.

За замовчуванням, прострочені елементи кешу обчислюються синхронно. Однак, ви можете обчислювати їх асинхронно, делегувавши обчислення значень фоновому робітнику за допомогою Компонента 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

Ось і все! Тепер, щоразу, коли обʼєкт буде запитано з цього пулу кешу, його кешоване значення значення буде повернуто негайно. Якщо його буде обрано для дострокового завершення терміну дії, буде надіслано повідомлення до автобуса, щоб запланувати фонові обчислення для оновлення значення.