Кэш
- Введение
- Конфигурация
- Использование кеша
- Теги кеша
- Атомарные блокировки
- Добавление собственных драйверов кэша
- События
Введение
Некоторые задачи извлечения или обработки данных, выполняемые вашим приложением, могут потребовать больших ресурсов ЦП или занять несколько секунд. В этом случае извлеченные данные обычно кэшируют на какое-то время, чтобы их можно было быстро извлечь при последующих запросах тех же данных. Кэшированные данные обычно хранятся в очень быстром хранилище данных, таком как Memcached или Redis.
К счастью, Laravel предоставляет выразительный унифицированный API для различных механизмов кеширования, что позволяет вам воспользоваться их невероятно быстрым извлечением данных и ускорить работу вашего веб-приложения.
Конфигурация
Файл конфигурации кеша вашего приложения находится в config/cache.php
. В этом файле вы можете указать, какой драйвер кеша вы хотите использовать по умолчанию во всем приложении. Laravel поддерживает популярные серверы кеширования, такие как Memcached, Redis, DynamoDB и реляционные базы данных из коробки. Кроме того, доступен драйвер кеширования на основе файлов, а драйверы кеш-памяти array
и null
предоставляют удобные механизмы кэширования для ваших автоматических тестов.
Файл конфигурации кеша также содержит различные другие параметры, которые задокументированы в файле, поэтому обязательно прочтите эти параметры. По умолчанию Laravel настроен на использование драйвера кеширования файлов file
, который хранит сериализованные кэшированные объекты в файловой системе сервера. Для более крупных приложений рекомендуется использовать более надежный драйвер, например Memcached или Redis. Вы даже можете настроить несколько конфигураций кеша для одного и того же драйвера.
Требования к драйверам
База данных
При использовании драйвера кэша database
вам нужно будет настроить таблицу для хранения элементов кеша. Вы найдете пример объявления Schema
в таблице ниже:
Schema::create('cache', function ($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 вам нужно будет либо установить расширение PHP PhpRedis через PECL, либо установить пакет predis/predis
(~1.0) через Composer. Laravel Sail уже включает это расширение. Кроме того, на официальных платформах развертывания Laravel, таких как Laravel Forge и Laravel Vapor, по умолчанию установлено расширение PhpRedis.
Для получения дополнительной информации о настройке Redis обратитесь к его странице документации.
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{ /** * Show a list of all users of the application. * * @return Response */ public function index() { $value = Cache::get('key'); // }}
Доступ к нескольким кеш-хранилищам
Используя фасад Cache
, вы можете получить доступ к различным хранилищам кеша с помощью метода store
. Ключ, переданный методу store
, должен соответствовать одному из хранилищ, перечисленных в массиве конфигурации stores
в вашем конфигурационном файле cache
:
$value = Cache::store('file')->get('foo'); Cache::store('redis')->put('bar', 'baz', 600); // 10 Minutes
Получение элементов из кеша
Метод 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::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();
Очистка кеша не учитывает ваш настроенный «префикс» кеша и удаляет все записи из кеша. Внимательно учтите это при очистке кеша, который используется другими приложениями.
Помощник кеша
Помимо использования фасада 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
так же, как если бы вы тестировали фасад.
Теги кеша
Теги кеширования не поддерживаются при использовании драйверов кеширования
file
,dynamodb
илиdatabase
. Более того, при использовании нескольких тегов с кешами, которые хранятся «навсегда», производительность будет лучше с таким драйвером, какmemcached
, , который автоматически очищает устаревшие записи.
Хранение элементов кэша с тегами
Теги кэша позволяют помечать связанные элементы в кеше, а затем сбрасывать все кэшированные значения, которым был назначен данный тег. Вы можете получить доступ к тегированному кешу, передав упорядоченный массив имен тегов. Например, давайте обратимся к тегированному кешу и put
значение в кеш:
Cache::tags(['people', 'artists'])->put('John', $john, $seconds); Cache::tags(['people', 'authors'])->put('Anne', $anne, $seconds);
Доступ к элементам кэша с тегами
Чтобы получить помеченный элемент кеша, передайте тот же упорядоченный список тегов методу tags
, а затем вызовите метод get
с ключом, который вы хотите получить:
$john = Cache::tags(['people', 'artists'])->get('John'); $anne = Cache::tags(['people', 'authors'])->get('Anne');
Удаление элементов кеша с тегами
Вы можете удалить все элементы, которым назначен тег или список тегов. Например, этот оператор удалит все кеши, помеченные как people
, authors
или и то, и другое. Таким образом, и Anne
и John
будут удалены из кеша:
Cache::tags(['people', 'authors'])->flush();
Напротив, этот оператор удалит только кешированные значения, помеченные как authors
, поэтому будет удалена Anne
, но не John
:
Cache::tags('authors')->flush();
Атомарные блокировки
Чтобы использовать эту функцию, ваше приложение должно использовать драйвер кэша
memcached
,redis
,dynamodb
,database
,file
илиarray
в качестве драйвера кэша по умолчанию для вашего приложения. Кроме того, все серверы должны взаимодействовать с одним и тем же сервером центрального кеша.
Требования к драйверам
База данных
При использовании драйвера кеша database
вам нужно будет настроить таблицу, в которой будут храниться блокировки кеша вашего приложения. Вы найдете пример объявления Schema
в таблице ниже:
Schema::create('cache_locks', function ($table) { $table->string('key')->primary(); $table->string('owner'); $table->integer('expiration');});
Управление блокировками
Атомарные блокировки позволяют манипулировать распределенными блокировками, не беспокоясь об условиях гонки. Например, Laravel Forge использует атомарные блокировки, чтобы гарантировать, что на сервере одновременно выполняется только одна удаленная задача. Вы можете создавать и управлять блокировками, используя метод Cache::lock
:
use Illuminate\Support\Facades\Cache; $lock = Cache::lock('foo', 10); if ($lock->get()) { // Lock acquired for 10 seconds... $lock->release();}
Метод get
также принимает замыкание. После выполнения замыкания Laravel автоматически снимает блокировку:
Cache::lock('foo')->get(function () { // Lock acquired indefinitely and automatically released...});
Если блокировка недоступна в тот момент, когда вы ее запрашиваете, вы можете указать Laravel подождать определенное количество секунд. Если блокировка не может быть получена в течение указанного срока, будет сгенерировано исключение Illuminate\Contracts\Cache\LockTimeoutException
:
use Illuminate\Contracts\Cache\LockTimeoutException; $lock = Cache::lock('foo', 10); try { $lock->block(5); // Lock acquired after waiting a maximum of 5 seconds...} catch (LockTimeoutException $e) { // Unable to acquire lock...} finally { optional($lock)->release();}
Приведенный выше пример можно упростить, передав замыкание методу block
. Когда замыкание передается этому методу, Laravel будет пытаться получить блокировку на указанное количество секунд и автоматически снимет блокировку после того, как замыкание будет выполнено:
Cache::lock('foo', 10)->block(5, function () { // Lock acquired after waiting a maximum of 5 seconds...});
Управление блокировками между процессами
Иногда вам может потребоваться установить блокировку в одном процессе и снять ее в другом процессе. Например, вы можете получить блокировку во время веб-запроса и захотите снять блокировку в конце задания в очереди, которое запускается этим запросом. В этом сценарии вы должны передать «токен владельца» с областью действия блокировки в задание в очереди, чтобы задание могло повторно создать экземпляр блокировки с использованием данного токена.
В приведенном ниже примере мы отправим задание в очередь, если блокировка будет успешно получена. Кроме того, мы передадим токен владельца блокировки заданию в очереди с помощью метода блокировки 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 ($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\Support\Facades\Cache;use Illuminate\Support\ServiceProvider; class CacheServiceProvider extends ServiceProvider{ /** * Register any application services. * * @return void */ public function register() { $this->app->booting(function () { Cache::extend('mongo', function ($app) { return Cache::repository(new MongoStore); }); }); } /** * Bootstrap any application services. * * @return void */ public function boot() { // }}
Первым аргументом, передаваемым методу extend
, является имя драйвера. Это будет соответствовать вашей опции driver
в файле конфигурации config/cache.php
. Второй аргумент - это замыкание, которое должно возвращать экземпляр Illuminate\Cache\Repository
. Замыкание будет передано экземпляру $app
, который является экземпляром сервис контейнера.
После регистрации расширения обновите параметр driver
в файле конфигурации config/cache.php
, указав имя вашего расширения.
События
Чтобы выполнить код для каждой операции с кешем, вы можете прослушивать события, запускаемые кешем. Как правило, вы должны поместить эти прослушиватели событий в класс приложения App\Providers\EventServiceProvider
:
/** * The event listener mappings for the application. * * @var array */protected $listen = [ 'Illuminate\Cache\Events\CacheHit' => [ 'App\Listeners\LogCacheHit', ], 'Illuminate\Cache\Events\CacheMissed' => [ 'App\Listeners\LogCacheMissed', ], 'Illuminate\Cache\Events\KeyForgotten' => [ 'App\Listeners\LogKeyForgotten', ], 'Illuminate\Cache\Events\KeyWritten' => [ 'App\Listeners\LogKeyWritten', ],];