Кеш

Вступ

Деякі завдання з отримання або обробки даних, які виконує ваш застосунок, можуть бути інтенсивними для процесора або займати кілька секунд для завершення. У таких випадках зазвичай кешують отримані дані на певний час, щоб їх можна було швидко отримати при наступних запитах на ті ж дані. Кешовані дані зазвичай зберігаються у дуже швидкому сховищі даних, такому як Memcached або Redis.

На щастя, Laravel надає виразний, уніфікований API для різних кеш-бекендів, що дозволяє вам скористатися їхнім надзвичайно швидким отриманням даних і прискорити ваш веб-застосунок.

Конфігурація

Файл конфігурації кешу вашого застосунку знаходиться за адресою config/cache.php. У цьому файлі ви можете вказати, яке сховище кешу ви хочете використовувати за замовчуванням у вашому застосунку. Laravel підтримує популярні бекенди кешування, такі як Memcached, Redis, DynamoDB, та реляційні бази даних з коробки. Крім того, доступний драйвер кешу на основі файлів, а драйвери кешу array та null забезпечують зручні бекенди кешу для ваших автоматизованих тестів.

Файл конфігурації кешу також містить різноманітні інші параметри, які ви можете переглянути. За замовчуванням Laravel налаштований на використання драйвера кешу database, який зберігає серіалізовані, кешовані об'єкти в базі даних вашого застосунку.

Вимоги до драйвера

Database

Коли ви використовуєте драйвер кешу database, вам знадобиться таблиця бази даних для зберігання даних кешу. Зазвичай, це включено в стандартну 0001_01_01_000001_create_cache_table.php міграцію бази даних Laravel; однак, якщо ваш застосунок не містить цієї міграції, ви можете скористатися командою Artisan make:cache-table, щоб створити її:

php artisan make:cache-table
 
php artisan migrate

Memcached

Використання драйвера Memcached вимагає встановлення пакету Memcached PECL. Ви можете перелічити всі ваші сервери Memcached у файлі конфігурації config/cache.php. Цей файл вже містить запис memcached.servers, щоб допомогти вам розпочати:

'memcached' => [
    // ...
 
    'servers' => [
        [
            'host' => env('MEMCACHED_HOST', '127.0.0.1'),
            'port' => env('MEMCACHED_PORT', 11211),
            'weight' => 100,
        ],
    ],
],

Якщо потрібно, ви можете встановити опцію host на шлях UNIX-сокета. Якщо ви це зробите, опція port повинна бути встановлена на 0:

'memcached' => [
    // ...
 
    'servers' => [
        [
            'host' => '/var/run/memcached/memcached.sock',
            'port' => 0,
            'weight' => 100
        ],
    ],
],

Redis

Перш ніж використовувати Redis кеш з Laravel, вам потрібно або встановити PHP-розширення PhpRedis через PECL, або встановити пакет predis/predis (~2.0) через компонувальник. Laravel Sail вже включає це розширення. Крім того, офіційні платформи застосунків Laravel, такі як Laravel Cloud та Laravel Forge, мають розширення PhpRedis, встановлене за замовчуванням.

Для отримання додаткової інформації про налаштування Redis зверніться до сторінки документації Laravel.

DynamoDB

Перш ніж використовувати драйвер кешу DynamoDB, ви повинні створити таблицю DynamoDB для зберігання всіх кешованих даних. Зазвичай ця таблиця повинна називатися cache. Однак, ви повинні назвати таблицю на основі значення конфігурації stores.dynamodb.table у файлі конфігурації cache. Назва таблиці також може бути встановлена через змінну середовища DYNAMODB_CACHE_TABLE.

Ця таблиця також повинна мати рядковий ключ розділу з назвою, що відповідає значенню елемента конфігурації stores.dynamodb.attributes.key у файлі конфігурації cache вашого застосунку. За замовчуванням, ключ розділу повинен називатися key.

Зазвичай, DynamoDB не буде проактивно видаляти прострочені елементи з таблиці. Тому вам слід увімкнути Time to Live (TTL) на таблиці. Під час налаштування параметрів TTL таблиці, вам слід встановити ім'я атрибута TTL як expires_at.

Далі, встановіть AWS SDK, щоб ваш Laravel застосунок міг взаємодіяти з DynamoDB:

composer require aws/aws-sdk-php

Крім того, ви повинні переконатися, що значення надані для параметрів конфігурації сховища кешу DynamoDB. Зазвичай ці параметри, такі як AWS_ACCESS_KEY_ID та AWS_SECRET_ACCESS_KEY, повинні бути визначені у файлі конфігурації .env вашого застосунку:

'dynamodb' => [
    'driver' => 'dynamodb',
    'key' => env('AWS_ACCESS_KEY_ID'),
    'secret' => env('AWS_SECRET_ACCESS_KEY'),
    'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
    'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
    'endpoint' => env('DYNAMODB_ENDPOINT'),
],

MongoDB

Якщо ви використовуєте MongoDB, драйвер кешу mongodb надається офіційним пакетом mongodb/laravel-mongodb і може бути налаштований за допомогою з'єднання з базою даних mongodb. MongoDB підтримує індекси TTL, які можуть бути використані для автоматичного очищення прострочених елементів кешу.

Для отримання додаткової інформації про налаштування MongoDB, будь ласка, зверніться до документації з кешування та блокувань MongoDB.

Використання кешу

Отримання екземпляра кешу

Щоб отримати екземпляр сховища кешу, ви можете використовувати фасад Cache, який ми будемо використовувати протягом цієї документації. Фасад Cache забезпечує зручний, лаконічний доступ до базових реалізацій контрактів кешу Laravel:

<?php
 
namespace App\Http\Controllers;
 
use Illuminate\Support\Facades\Cache;
 
class UserController extends Controller
{
/**
* Показати список усіх користувачів застосунку.
*/
public function index(): array
{
$value = Cache::get('key');
 
return [
// ...
];
}
}

Доступ до декількох сховищ кешу

Використовуючи фасад Cache, ви можете отримати доступ до різних сховищ кешу через метод store. Ключ, переданий методу store, повинен відповідати одному з сховищ, перелічених у конфігураційному масиві stores у вашому конфігураційному файлі cache:

$value = Cache::store('file')->get('foo');
 
Cache::store('redis')->put('bar', 'baz', 600); // 10 хвилин

Отримання елементів з кешу

Метод get фасаду Cache використовується для отримання елементів з кешу. Якщо елемент не існує в кеші, буде повернено null. Якщо ви бажаєте, ви можете передати другий аргумент методу get, вказуючи значення за замовчуванням, яке ви хочете отримати, якщо елемент не існує:

$value = Cache::get('key');
 
$value = Cache::get('key', 'default');

Ви навіть можете передати замикання як значення за замовчуванням. Результат замикання буде повернуто, якщо вказаний елемент не існує в кеші. Передача замикання дозволяє відкласти отримання значень за замовчуванням з бази даних або іншої зовнішньої служби:

$value = Cache::get('key', function () {
    return DB::table(/* ... */)->get();
});

Визначення Існування Елемента

Метод has може бути використаний для визначення, чи існує елемент у кеші. Цей метод також поверне false, якщо елемент існує, але його значення є null:

if (Cache::has('key')) {
    // ...
}

Збільшення / Зменшення Значень

Методи increment та decrement можуть бути використані для зміни значення цілих чисел у кеші. Обидва ці методи приймають необов'язковий другий аргумент, що вказує на величину, на яку потрібно збільшити або зменшити значення елемента:

// Ініціалізуйте значення, якщо воно не існує...
Cache::add('key', 0, now()->addHours(4));
 
// Збільшити або зменшити значення...
Cache::increment('key');
Cache::increment('key', $amount);
Cache::decrement('key');
Cache::decrement('key', $amount);

Отримати та Зберегти

Іноді ви можете захотіти отримати елемент з кешу, але також зберегти значення за замовчуванням, якщо запитуваний елемент не існує. Наприклад, ви можете захотіти отримати всіх користувачів з кешу або, якщо вони не існують, отримати їх з бази даних і додати до кешу. Ви можете зробити це за допомогою методу Cache::remember:

$value = Cache::remember('users', $seconds, function () {
    return DB::table('users')->get();
});

Якщо елемент не існує в кеші, замикання, передане методу remember, буде виконано, і його результат буде поміщено в кеш.

Ви можете використовувати метод rememberForever, щоб отримати елемент з кешу або зберегти його назавжди, якщо він не існує:

$value = Cache::rememberForever('users', function () {
    return DB::table('users')->get();
});

Застаріле під час повторної перевірки

Коли використовується метод Cache::remember, деякі користувачі можуть відчувати повільний час відгуку, якщо кешоване значення застаріло. Для певних типів даних може бути корисним дозволити частково застарілі дані бути наданими, поки кешоване значення перераховується у фоновому режимі, запобігаючи тому, щоб деякі користувачі відчували повільний час відгуку під час обчислення кешованих значень. Це часто називають шаблоном "stale-while-revalidate", і метод Cache::flexible надає реалізацію цього шаблону.

Метод flexible приймає масив, який визначає, як довго кешоване значення вважається "свіжим" і коли воно стає "застарілим". Перше значення в масиві представляє кількість секунд, протягом яких кеш вважається свіжим, тоді як друге значення визначає, як довго його можна подавати як застарілі дані, перш ніж буде необхідно перерахування.

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

$value = Cache::flexible('users', [5, 10], function () {
    return DB::table('users')->get();
});

Отримання та Видалення

Якщо вам потрібно отримати елемент з кешу, а потім видалити його, ви можете використовувати метод pull. Як і метод get, буде повернено null, якщо елемент не існує в кеші:

$value = Cache::pull('key');
 
$value = Cache::pull('key', 'default');

Зберігання елементів у кеші

Ви можете використовувати метод put на фасаді Cache для зберігання елементів у кеші:

Cache::put('key', 'value', $seconds = 10);

Якщо час зберігання не передано методу put, елемент буде зберігатися безстроково:

Cache::put('key', 'value');

Замість передачі кількості секунд як цілого числа, ви також можете передати екземпляр DateTime, що представляє бажаний час закінчення терміну дії кешованого елемента:

Cache::put('key', 'value', now()->addMinutes(10));

Зберегти, якщо відсутній

Метод add додасть елемент до кешу лише в тому випадку, якщо він ще не існує в сховищі кешу. Метод поверне true, якщо елемент дійсно додано до кешу. В іншому випадку метод поверне false. Метод add є атомарною операцією:

Cache::add('key', 'value', $seconds);

Зберігання елементів назавжди

Метод forever може бути використаний для постійного зберігання елемента в кеші. Оскільки ці елементи не будуть закінчуватися, їх потрібно вручну видаляти з кешу за допомогою методу forget:

Cache::forever('key', 'value');

Якщо ви використовуєте драйвер Memcached, елементи, що зберігаються "назавжди", можуть бути видалені, коли кеш досягає свого лімітного розміру.

Видалення елементів з кешу

Ви можете видалити елементи з кешу, використовуючи метод forget:

Cache::forget('key');

Ви також можете видалити елементи, вказавши нульову або від'ємну кількість секунд до закінчення терміну дії:

Cache::put('key', 'value', 0);
 
Cache::put('key', 'value', -5);

Ви можете очистити весь кеш, використовуючи метод flush:

Cache::flush();

Очищення кешу не враховує ваш налаштований "префікс" кешу і видалить всі записи з кешу. Ретельно обміркуйте це, коли очищуєте кеш, який використовується іншими застосунками.

Запам'ятовування кешу

Laravel's memo драйвер кешу дозволяє тимчасово зберігати розв'язані значення кешу в пам'яті під час одного запиту або виконання завдання. Це запобігає повторним зверненням до кешу в межах одного виконання, значно покращуючи продуктивність.

Щоб використовувати кеш з пам'яттю, викличте метод memo:

use Illuminate\Support\Facades\Cache;
 
$value = Cache::memo()->get('key');

Метод memo за бажанням приймає назву сховища кешу, яке вказує на базове сховище кешу, яке буде декорувати memoized-драйвер:

// Використання сховища кешу за замовчуванням...
$value = Cache::memo()->get('key');
 
// Використання сховища кешу Redis...
$value = Cache::memo('redis')->get('key');

Перший виклик get для заданого ключа отримує значення з вашого сховища кешу, але наступні виклики в межах того ж запиту або завдання отримають значення з пам'яті:

// Звертається до кешу...
$value = Cache::memo()->get('key');
 
// Не потрапляє в кеш, повертає запам'ятоване значення...
$value = Cache::memo()->get('key');

Коли викликаються методи, що змінюють значення кешу (такі як put, increment, remember тощо), кеш з пам'яттю автоматично забуває запам'ятоване значення і передає виклик методу, що змінює, базовому сховищу кешу:

Cache::memo()->put('name', 'Taylor'); // Записує у базовий кеш...
Cache::memo()->get('name'); // Зчитує з базового кешу...
Cache::memo()->get('name'); // Кешовано в пам’яті, не звертається до кешу...
 
Cache::memo()->put('name', 'Tim'); // Скидає кешоване значення в пам’яті, записує нове...
Cache::memo()->get('name'); // Знову зчитує з базового кешу...

Помічник Cache

На додаток до використання фасаду Cache, ви також можете використовувати глобальну функцію cache для отримання та зберігання даних через кеш. Коли функція cache викликається з одним аргументом-рядком, вона поверне значення заданого ключа:

$value = cache('key');

Якщо ви надасте масив пар ключ / значення та час закінчення дії функції, вона зберігатиме значення в кеші протягом вказаного періоду:

cache(['key' => 'value'], $seconds);
 
cache(['key' => 'value'], now()->addMinutes(10));

Коли функція cache викликається без аргументів, вона повертає екземпляр реалізації Illuminate\Contracts\Cache\Factory, що дозволяє викликати інші методи кешування:

cache()->remember('users', $seconds, function () {
    return DB::table('users')->get();
});

Коли тестуєте виклик глобальної функції cache, ви можете використовувати метод Cache::shouldReceive так само, як якщо б ви тестували фасад.

Атомарні блокування

Щоб використовувати цю функцію, ваш застосунок повинен використовувати memcached, redis, dynamodb, database, file або array драйвер кешу як драйвер кешу за замовчуванням вашого застосунку. Крім того, всі сервери повинні спілкуватися з тим самим центральним сервером кешу.

Управління блокуваннями

Атомарні блокування дозволяють маніпулювати розподіленими блокуваннями без турбот про умови гонки. Наприклад, Laravel Cloud використовує атомарні блокування, щоб гарантувати, що на сервері виконується лише одне віддалене завдання одночасно. Ви можете створювати та керувати блокуваннями за допомогою методу Cache::lock:

use Illuminate\Support\Facades\Cache;
 
$lock = Cache::lock('foo', 10);
 
if ($lock->get()) {
    // Блокування отримано на 10 секунд...
 
    $lock->release();
}

Метод get також приймає замикання. Після виконання замикання Laravel автоматично звільнить блокування:

Cache::lock('foo', 10)->get(function () {
    // Блокування отримано на 10 секунд і автоматично звільнено...
});

Якщо блокування недоступне в момент запиту, ви можете вказати Laravel чекати певну кількість секунд. Якщо блокування не може бути отримане протягом вказаного часу, буде викинуто Illuminate\Contracts\Cache\LockTimeoutException:

use Illuminate\Contracts\Cache\LockTimeoutException;
 
$lock = Cache::lock('foo', 10);
 
try {
    $lock->block(5);
 
    // Блокування отримано після очікування максимум 5 секунд...
} catch (LockTimeoutException $e) {
    // Неможливо отримати блокування...
} finally {
    $lock->release();
}

Приклад вище може бути спрощений шляхом передачі замикання в метод block. Коли замикання передається цьому методу, Laravel спробує отримати блокування на вказану кількість секунд і автоматично звільнить блокування після виконання замикання:

Cache::lock('foo', 10)->block(5, function () {
    // Блокування отримано на 10 секунд після очікування максимум 5 секунд...
});

Управління блокуваннями між процесами

Іноді ви можете захотіти отримати блокування в одному процесі та звільнити його в іншому процесі. Наприклад, ви можете отримати блокування під час веб-запиту і захотіти звільнити блокування в кінці поставленої в чергу задачі, яка запускається цим запитом. У цьому сценарії ви повинні передати "токен власника" блокування, обмежений областю, до поставленої в чергу задачі, щоб задача могла повторно створити блокування, використовуючи даний токен.

У наведеному нижче прикладі ми відправимо завдання в чергу, якщо блокування буде успішно отримано. Крім того, ми передамо токен власника блокування до завдання в черзі через метод owner блокування:

$podcast = Podcast::find($id);
 
$lock = Cache::lock('processing', 120);
 
if ($lock->get()) {
    ProcessPodcast::dispatch($podcast, $lock->owner());
}

У завданні ProcessPodcast нашого застосунку ми можемо відновити та звільнити блокування, використовуючи токен власника:

Cache::restoreLock('processing', $this->owner)->release();

Якщо ви хочете звільнити блокування без врахування його поточного власника, ви можете використовувати метод forceRelease:

Cache::lock('processing')->forceRelease();

Додавання Користувацьких Драйверів Кешу

Написання Драйвера

Щоб створити наш власний драйвер кешу, спочатку потрібно реалізувати Illuminate\Contracts\Cache\Store контракт. Отже, реалізація кешу для MongoDB може виглядати приблизно так:

<?php
 
namespace App\Extensions;
 
use Illuminate\Contracts\Cache\Store;
 
class MongoStore implements Store
{
    public function get($key) {}
    public function many(array $keys) {}
    public function put($key, $value, $seconds) {}
    public function putMany(array $values, $seconds) {}
    public function increment($key, $value = 1) {}
    public function decrement($key, $value = 1) {}
    public function forever($key, $value) {}
    public function forget($key) {}
    public function flush() {}
    public function getPrefix() {}
}

Нам просто потрібно реалізувати кожен з цих методів, використовуючи з'єднання з MongoDB. Для прикладу того, як реалізувати кожен з цих методів, подивіться на Illuminate\Cache\MemcachedStore у вихідному коді фреймворку Laravel. Після завершення нашої реалізації ми можемо завершити реєстрацію нашого користувацького драйвера, викликавши метод extend фасаду Cache:

Cache::extend('mongo', function (Application $app) {
    return Cache::repository(new MongoStore);
});

Якщо ви замислюєтеся, де розмістити код вашого власного драйвера кешу, ви можете створити простір імен Extensions у вашій директорії app. Однак майте на увазі, що Laravel не має жорсткої структури застосунку, і ви можете організувати свій застосунок відповідно до ваших уподобань.

Реєстрація драйвера

Щоб зареєструвати власний драйвер кешу в Laravel, ми будемо використовувати метод extend на фасаді Cache. Оскільки інші сервіс-провайдери можуть намагатися зчитувати кешовані значення в межах свого методу boot, ми зареєструємо наш власний драйвер у зворотному виклику booting. Використовуючи зворотний виклик booting, ми можемо забезпечити, що власний драйвер буде зареєстровано безпосередньо перед тим, як метод boot буде викликано на сервіс-провайдерах нашого застосунку, але після того, як метод register буде викликано на всіх сервіс-провайдерах. Ми зареєструємо наш зворотний виклик booting у методі register класу App\Providers\AppServiceProvider нашого застосунку:

<?php
 
namespace App\Providers;
 
use App\Extensions\MongoStore;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\ServiceProvider;
 
class AppServiceProvider extends ServiceProvider
{
/**
* Зареєструвати будь-які сервіси застосунку.
*/
public function register(): void
{
$this->app->booting(function () {
Cache::extend('mongo', function (Application $app) {
return Cache::repository(new MongoStore);
});
});
}
 
/**
* Ініціалізувати будь-які сервіси застосунку.
*/
public function boot(): void
{
// ...
}
}

Перший аргумент, переданий методу extend, це ім'я драйвера. Це буде відповідати вашій опції driver у конфігураційному файлі config/cache.php. Другий аргумент — це замикання, яке повинно повернути екземпляр Illuminate\Cache\Repository. У замикання буде передано екземпляр $app, який є екземпляром Сервіс-контейнера.

Після реєстрації вашого розширення оновіть змінну середовища CACHE_STORE або опцію default у файлі конфігурації config/cache.php вашого застосунку на ім'я вашого розширення.

Події

Щоб виконувати код при кожній операції кешування, ви можете прослуховувати різні події, які відправляються кешем:

Назва події
Illuminate\Cache\Events\CacheFlushed
Illuminate\Cache\Events\CacheFlushing
Illuminate\Cache\Events\CacheHit
Illuminate\Cache\Events\CacheMissed
Illuminate\Cache\Events\ForgettingKey
Illuminate\Cache\Events\KeyForgetFailed
Illuminate\Cache\Events\KeyForgotten
Illuminate\Cache\Events\KeyWriteFailed
Illuminate\Cache\Events\KeyWritten
Illuminate\Cache\Events\RetrievingKey
Illuminate\Cache\Events\RetrievingManyKeys
Illuminate\Cache\Events\WritingKey
Illuminate\Cache\Events\WritingManyKeys

Щоб підвищити продуктивність, ви можете вимкнути події кешу, встановивши параметр конфігурації events у значення false для певного сховища кешу у файлі конфігурації config/cache.php вашого застосунку:

'database' => [
    'driver' => 'database',
    // ...
    'events' => false,
],