Кеш
Дата оновлення перекладу 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 постачається з набором сконфігурованих адаптерів:
- 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;
// ...
// 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
Ось і все! Тепер, щоразу, коли обʼєкт буде запитано з цього пулу кешу, його кешоване значення значення буде повернуто негайно. Якщо його буде обрано для дострокового завершення терміну дії, буде надіслано повідомлення до автобуса, щоб запланувати фонові обчислення для оновлення значення.