1. Глубже в детали
  2. Кеш

Присоединяйся к нашему Telegram сообществу @webblend!

Здесь ты найдешь сниппеты по Laravel и полезные советы по веб-разработке.

Введение

Некоторые из задач по извлечению или обработке данных, выполняемых вашим приложением, могут быть ресурсоемкими в плане использования процессора или занимать несколько секунд для завершения. В таких случаях обычно кешируют извлеченные данные на какое-то время, чтобы они могли быть быстро получены при последующих запросах к тем же данным. Кешированные данные обычно хранятся в очень быстром хранилище данных, таком как Memcached или Redis.

К счастью, Laravel предоставляет выразительный, унифицированный API для различных кеш-бэков, позволяя вам воспользоваться их мгновенным извлечением данных и ускорить ваше веб-приложение.

Настройка

Конфигурационный файл кеша вашего приложения находится в config/cache.php. В этом файле вы можете указать, какой драйвер кеша вы хотели бы использовать по умолчанию в вашем приложении. Laravel поддерживает популярные кеш-бэкинды, такие как Memcached, Redis, DynamoDB, а также реляционные базы данных. Кроме того, доступны драйверы кеша на основе файлов, а драйверы array и "null" предоставляют удобные бекенды кеша для ваших автоматизированных тестов.

Файл конфигурации кеша также содержит различные другие параметры, которые документированы внутри файла, так что убедитесь, что вы прочитали эти параметры. По умолчанию Laravel настроен на использование драйвера кеша file, который хранит сериализованные объекты кеша на файловой системе сервера. Для крупных приложений рекомендуется использовать более надежный драйвер, такой как Memcached или Redis. Вы даже можете настроить несколько конфигураций кеша для одного и того же драйвера.

Необходимые предварительные условия для драйвера

База данных

При использовании драйвера кеша database вам потребуется настроить таблицу для хранения элементов кеша. Приведен пример объявления схемы для этой таблицы ниже:

Schema::create('cache', function (Blueprint $table) {
$table->string('key')->unique();
$table->text('value');
$table->integer('expiration');
});

Примечание Вы также можете использовать команду Artisan php artisan cache:table для создания миграции с правильной схемой.

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' => [
[
'host' => '/var/run/memcached/memcached.sock',
'port' => 0,
'weight' => 100
],
],

Redis

Перед использованием кеша Redis с Laravel вам необходимо установить расширение PhpRedis для PHP через PECL или установить пакет predis/predis (~1.0) через Composer. Laravel Sail уже включает это расширение. Кроме того, официальные платформы развертывания Laravel, такие как Laravel Forge и Laravel Vapor, по умолчанию устанавливают расширение PhpRedis.

Дополнительную информацию по настройке Redis можно найти на его странице документации Laravel.

DynamoDB

Перед использованием драйвера кеша DynamoDB вы должны создать таблицу DynamoDB для хранения всех кешированных данных. Обычно эта таблица должна иметь имя cache. Однако вы должны назвать таблицу в соответствии со значением конфигурационного параметра stores.dynamodb.table в файле конфигурации cache вашего приложения.

Эта таблица также должна иметь строковый разделительный ключ с именем, соответствующим значению конфигурационного параметра stores.dynamodb.attributes.key в файле конфигурации cache вашего приложения. По умолчанию разделительный ключ должен иметь имя key.

Использование кеша

Получение экземпляра кеша

Для получения экземпляра кеша вы можете использовать фасад 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();
});

Получение и удаление

Если вам нужно извлечь элемент из кеша, а затем удалить его, вы можете использовать метод pull. Как и метод get, он вернет null, если элемент не существует в кеше:

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

Сохранение элементов в кеше

Метод 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();

Внимание Очистка кеша не учитывает ваш настроенный кеш "prefix" и удалит все записи из кеша. Внимательно обдумайте это при очистке кеша, который используется другими приложениями.

Вспомогательная программа кеша

Помимо использования фасада 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 в качестве драйвера кеша по умолчанию. Кроме того, все серверы должны взаимодействовать с одним и тем же центральным сервером кеша.

Необходимые предварительные условия для драйвера

База данных

При использовании драйвера кеша database вам нужно настроить таблицу для хранения блокировок кеша вашего приложения. Приведен пример объявления схемы для этой таблицы ниже:

Schema::create('cache_locks', function (Blueprint $table) {
$table->string('key')->primary();
$table->string('owner');
$table->integer('expiration');
});

Примечание Если вы использовали команду Artisan cache:table для создания таблицы кеша драйвера базы данных, миграция, созданная этой командой, уже включает определение таблицы cache_locks.

Управление блокировками

Атомарные блокировки позволяют манипулировать распределенными блокировками, не беспокоясь о гонках. Например, Laravel Forge использует атомарные блокировки, чтобы гарантировать, что на сервере одновременно выполняется только одна удаленная задача. Вы можете создавать и управлять блокировками с использованием метода 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 () {
// Блокировка получена после ожидания максимум 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, который является экземпляром контейнера зависимостей.

После регистрации вашего расширения обновите параметр driver файла конфигурации config/cache.php на имя вашего расширения.

События

Чтобы выполнить код при каждой операции кеша, вы можете прослушивать события, вызываемые кешем. Обычно вы должны размещать эти слушатели событий в классе App\Providers\EventServiceProvider вашего приложения:

use App\Listeners\LogCacheHit;
use App\Listeners\LogCacheMissed;
use App\Listeners\LogKeyForgotten;
use App\Listeners\LogKeyWritten;
use Illuminate\Cache\Events\CacheHit;
use Illuminate\Cache\Events\CacheMissed;
use Illuminate\Cache\Events\KeyForgotten;
use Illuminate\Cache\Events\KeyWritten;
 
/**
* Сопоставления слушателей событий для приложения.
*
* @var array
*/
protected $listen = [
CacheHit::class => [
LogCacheHit::class,
],
 
CacheMissed::class => [
LogCacheMissed::class,
],
 
KeyForgotten::class => [
LogKeyForgotten::class,
],
 
KeyWritten::class => [
LogKeyWritten::class,
],
];