Cache
- Introducción
- Configuración
- Uso de Caché
- Bloqueos Atómicos
- Agregar Controladores de Caché Personalizados
- Eventos
Introducción
Algunas de las tareas de recuperación o procesamiento de datos realizadas por su aplicación pueden ser intensivas en CPU o tardar varios segundos en completarse. Cuando este es el caso, es común almacenar en caché los datos recuperados durante un tiempo para que se puedan recuperar rápidamente en solicitudes posteriores para los mismos datos. Los datos en caché suelen almacenarse en un almacén de datos muy rápido como Memcached o Redis. Afortunadamente, Laravel ofrece una API unificada y expresiva para varios backends de caché, lo que te permite aprovechar su rápida recuperación de datos y acelerar tu aplicación web.
Configuración
El archivo de configuración de caché de tu aplicación se encuentra en config/cache.php
. En este archivo, puedes especificar qué almacén de caché te gustaría usar de manera predeterminada en toda tu aplicación. Laravel admite backend de caché populares como Memcached, Redis, DynamoDB y bases de datos relacionales de manera predeterminada. Además, hay disponible un driver de caché basado en archivos, mientras que los controladores de caché array
y "null" ofrecen backend de caché convenientes para tus pruebas automatizadas.
El archivo de configuración de caché también contiene una variedad de otras opciones que puedes revisar. Por defecto, Laravel está configurado para usar el driver de caché database
, que almacena los objetos en caché serializados en la base de datos de tu aplicación.
Requisitos Previos del Driver
Base de datos
Al utilizar el driver de caché database
, necesitarás una tabla de base de datos para contener los datos de caché. Típicamente, esto se incluye en la migración de base de datos predeterminada de Laravel 0001_01_01_000001_create_cache_table.php
migración de base de datos; sin embargo, si tu aplicación no contiene esta migración, puedes usar el comando Artisan make:cache-table
para crearla:
php artisan make:cache-table php artisan migrate
Memcached
Usar el driver de Memcached requiere que el paquete PECL de Memcached esté instalado. Puedes listar todos tus servidores de Memcached en el archivo de configuración config/cache.php
. Este archivo ya contiene una entrada memcached.servers
para que comiences:
'memcached' => [ // ... 'servers' => [ [ 'host' => env('MEMCACHED_HOST', '127.0.0.1'), 'port' => env('MEMCACHED_PORT', 11211), 'weight' => 100, ], ], ],
Si es necesario, puedes establecer la opción host
en una ruta de socket UNIX. Si haces esto, la opción port
debe configurarse en 0
:
'memcached' => [ // ... 'servers' => [ [ 'host' => '/var/run/memcached/memcached.sock', 'port' => 0, 'weight' => 100 ], ], ],
Redis
Antes de usar un caché Redis con Laravel, necesitarás instalar la extensión PHP PhpRedis a través de PECL o instalar el paquete predis/predis
(~2.0) a través de Composer. Laravel Sail ya incluye esta extensión. Además, plataformas de despliegue oficiales de Laravel como Laravel Forge y Laravel Vapor tienen la extensión PhpRedis instalada por defecto.
Para obtener más información sobre la configuración de Redis, consulta su página de documentación de Laravel.
DynamoDB
Antes de utilizar el driver de caché DynamoDB, debes crear una tabla DynamoDB para almacenar todos los datos en caché. Típicamente, esta tabla debería llamarse cache
. Sin embargo, debes nombrar la tabla en función del valor de la configuración stores.dynamodb.table
dentro del archivo de configuración cache
. El nombre de la tabla también puede configurarse a través de la variable de entorno DYNAMODB_CACHE_TABLE
.
Esta tabla también debe tener una clave de partición de cadena con un nombre que corresponda al valor del ítem de configuración stores.dynamodb.attributes.key
dentro del archivo de configuración cache
de tu aplicación. Por defecto, la clave de partición debe llamarse key
.
Típicamente, DynamoDB no eliminará proactivamente los elementos expirados de una tabla. Por lo tanto, deberías activar Time to Live (TTL) en la tabla. Al configurar la configuración TTL de la tabla, debes establecer el nombre del atributo TTL en expires_at
.
A continuación, instala el SDK de AWS para que tu aplicación Laravel pueda comunicarse con DynamoDB:
composer require aws/aws-sdk-php
Además, debes asegurarte de que se proporcionen valores para las opciones de configuración del almacén de caché de DynamoDB. Típicamente, estas opciones, como AWS_ACCESS_KEY_ID
y AWS_SECRET_ACCESS_KEY
, deben definirse en el archivo de configuración .env
de tu aplicación:
'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
Si estás utilizando MongoDB, se proporciona un driver de caché mongodb
a través del paquete oficial mongodb/laravel-mongodb
, el cual se puede configurar utilizando una conexión a una base de datos mongodb
. MongoDB admite índices TTL, que se pueden usar para eliminar automáticamente los elementos de caché expirados.
Para obtener más información sobre la configuración de MongoDB, consulta la documentación de Caché y Bloqueos de MongoDB.
Uso de Caché
Obtener una Instancia de Caché
Para obtener una instancia de almacenamiento en caché, puedes usar la facade Cache
, que es lo que utilizaremos a lo largo de esta documentación. La facade Cache
proporciona un acceso conveniente y conciso a las implementaciones subyacentes de los contratos de caché de Laravel:
<?php namespace App\Http\Controllers; use Illuminate\Support\Facades\Cache; class UserController extends Controller { /** * Show a list of all users of the application. */ public function index(): array { $value = Cache::get('key'); return [ // ... ]; } }
Accediendo a Múltiples Almacenes de Caché
Usando la facade Cache
, puedes acceder a varias tiendas de caché a través del método store
. La clave pasada al método store
debe corresponder a una de las tiendas listadas en el array de configuración stores
en tu archivo de configuración cache
:
$value = Cache::store('file')->get('foo'); Cache::store('redis')->put('bar', 'baz', 600); // 10 Minutes
Recuperar Elementos de la Caché
El método get
de la fachada Cache
se utiliza para recuperar elementos de la caché. Si el elemento no existe en la caché, se devolverá null
. Si lo desea, puede pasar un segundo argumento al método get
especificando el valor predeterminado que desea que se devuelva si el elemento no existe:
$value = Cache::get('key'); $value = Cache::get('key', 'default');
Puedes incluso pasar una función anónima
como el valor predeterminado. El resultado de la función anónima
se devolverá si el ítem especificado no existe en la caché. Pasar una función anónima
te permite aplazar la recuperación de valores predeterminados de una base de datos u otro servicio externo:
$value = Cache::get('key', function () { return DB::table(/* ... */)->get(); });
Determinando la Existencia de un Elemento
El método has
puede utilizarse para determinar si un elemento existe en la caché. Este método también devolverá false
si el elemento existe pero su valor es null
:
if (Cache::has('key')) { // ... }
Incrementando / Decrementando Valores
Los métodos increment
y decrement
se pueden utilizar para ajustar el valor de elementos enteros en la caché. Ambos métodos aceptan un segundo argumento opcional que indica la cantidad por la cual aumentar o disminuir el valor del elemento:
// Initialize the value if it does not exist... Cache::add('key', 0, now()->addHours(4)); // Increment or decrement the value... Cache::increment('key'); Cache::increment('key', $amount); Cache::decrement('key'); Cache::decrement('key', $amount);
Recuperar y Almacenar
A veces es posible que desees recuperar un elemento de la caché, pero también almacenar un valor predeterminado si el elemento solicitado no existe. Por ejemplo, es posible que desees recuperar todos los usuarios de la caché o, si no existen, recuperarlos de la base de datos y agregarlos a la caché. Puedes hacer esto utilizando el método Cache::remember
:
$value = Cache::remember('users', $seconds, function () { return DB::table('users')->get(); });
Si el elemento no existe en la caché, la función anónima pasada al método remember
se ejecutará y su resultado se colocará en la caché.
Puedes usar el método rememberForever
para recuperar un elemento de la caché o almacenarlo de forma indefinida si no existe:
$value = Cache::rememberForever('users', function () { return DB::table('users')->get(); });
Stale While Revalidate
Al utilizar el método Cache::remember
, algunos usuarios pueden experimentar tiempos de respuesta lentos si el valor en caché ha expirado. Para ciertos tipos de datos, puede ser útil permitir que se sirvan datos parcialmente obsoletos mientras se recalcula el valor en caché en segundo plano, evitando que algunos usuarios experimenten tiempos de respuesta lentos mientras se calculan los valores en caché. Esto se conoce a menudo como el patrón "stale-while-revalidate", y el método Cache::flexible
proporciona una implementación de este patrón.
El método flexible acepta un array que especifica cuánto tiempo se considera "fresco" el valor en caché y cuándo se vuelve "obsoleto". El primer valor del array representa el número de segundos que se considera fresca la caché, mientras que el segundo valor define cuánto tiempo se puede servir como datos obsoletos antes de que sea necesaria la recalculación.
Si se realiza una solicitud dentro del período fresco (antes del primer valor), la caché se devuelve inmediatamente sin recálculo. Si se realiza una solicitud durante el período obsoleto (entre los dos valores), se sirve el valor obsoleto al usuario, y se registra una función anónima para actualizar el valor en caché después de que se envíe la respuesta al usuario. Si se realiza una solicitud después del segundo valor, la caché se considera expirada, y el valor se recalcula inmediatamente, lo que puede resultar en una respuesta más lenta para el usuario:
$value = Cache::flexible('users', [5, 10], function () { return DB::table('users')->get(); });
Recuperar y Eliminar
Si necesitas recuperar un elemento de la caché y luego eliminar el elemento, puedes usar el método pull
. Al igual que el método get
, se devolverá null
si el elemento no existe en la caché:
$value = Cache::pull('key'); $value = Cache::pull('key', 'default');
Almacenar Elementos en la Caché
Puedes usar el método put
en la fachada Cache
para almacenar elementos en la caché:
Cache::put('key', 'value', $seconds = 10);
Si el tiempo de almacenamiento no se pasa al método put
, el elemento se almacenará indefinidamente:
Cache::put('key', 'value');
En lugar de pasar el número de segundos como un entero, también puedes pasar una instancia de DateTime
que represente el tiempo de expiración deseado del elemento en caché:
Cache::put('key', 'value', now()->addMinutes(10));
Almacenar si no está presente
El método add
solo añadirá el elemento a la caché si no existe ya en el almacén de caché. El método devolverá true
si el elemento es realmente añadido a la caché. De lo contrario, el método devolverá false
. El método add
es una operación atómica:
Cache::add('key', 'value', $seconds);
Almacenando Elementos para Siempre
El método forever
se puede utilizar para almacenar un elemento en la caché de manera permanente. Dado que estos elementos no expirarán, deben ser eliminados manualmente de la caché utilizando el método forget
:
Cache::forever('key', 'value');
Si estás utilizando el driver de Memcached, los elementos que se almacenan "para siempre" pueden ser eliminados cuando el caché alcanza su límite de tamaño.
Eliminar Elementos de la Caché
Puedes eliminar elementos de la caché utilizando el método forget
:
Cache::forget('key');
También puedes eliminar elementos proporcionando un número de segundos de expiración cero o negativo:
Cache::put('key', 'value', 0); Cache::put('key', 'value', -5);
Puedes limpiar toda la caché usando el método flush
:
Cache::flush();
Flushing the cache no respeta tu "prefijo" de caché configurado y eliminará todas las entradas de la caché. Considera esto cuidadosamente al borrar una caché que es compartida por otras aplicaciones.
El Helper de Caché
Además de utilizar la facade Cache
, también puedes usar la función global cache
para recuperar y almacenar datos a través de la caché. Cuando se llama a la función cache
con un solo argumento de cadena, devolverá el valor de la clave dada:
$value = cache('key');
Si proporcionas un array de pares clave / valor y un tiempo de expiración a la función, almacenará los valores en la caché por la duración especificada:
cache(['key' => 'value'], $seconds); cache(['key' => 'value'], now()->addMinutes(10));
Cuando se llama a la función cache
sin argumentos, devuelve una instancia de la implementación Illuminate\Contracts\Cache\Factory
, lo que te permite llamar a otros métodos de almacenamiento en caché:
cache()->remember('users', $seconds, function () { return DB::table('users')->get(); });
Al probar la llamada a la función global cache
, puedes usar el método Cache::shouldReceive
como si estuvieras probando la facade.
Bloqueos Atómicos
Para utilizar esta función, tu aplicación debe estar utilizando el driver de caché memcached
, redis
, dynamodb
, database
, file
o array
como el driver de caché predeterminado de tu aplicación. Además, todos los servidores deben estar comunicándose con el mismo servidor de caché central.
Gestionar Bloqueos
Los bloqueos atómicos permiten la manipulación de bloqueos distribuidos sin preocuparse por condiciones de carrera. Por ejemplo, Laravel Forge utiliza bloqueos atómicos para asegurar que solo una tarea remota se esté ejecutando en un servidor a la vez. Puedes crear y gestionar bloqueos utilizando el método Cache::lock
:
use Illuminate\Support\Facades\Cache; $lock = Cache::lock('foo', 10); if ($lock->get()) { // Lock acquired for 10 seconds... $lock->release(); }
El método get
también acepta una función anónima. Después de que se ejecute la función anónima, Laravel liberará automáticamente el bloqueo:
Cache::lock('foo', 10)->get(function () { // Lock acquired for 10 seconds and automatically released... });
Si el bloqueo no está disponible en el momento en que lo solicitas, puedes instructar a Laravel para que espere un número específico de segundos. Si el bloqueo no puede ser adquirido dentro del límite de tiempo especificado, se lanzará una 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 { $lock->release(); }
El ejemplo anterior puede simplificarse pasando una función anónima al método block
. Cuando se pasa una función anónima a este método, Laravel intentará adquirir el bloqueo durante el número especificado de segundos y liberará automáticamente el bloqueo una vez que se haya ejecutado la función anónima:
Cache::lock('foo', 10)->block(5, function () { // Lock acquired after waiting a maximum of 5 seconds... });
Gestionar Bloqueos Entre Procesos
A veces, es posible que desees adquirir un bloqueo en un proceso y liberarlo en otro proceso. Por ejemplo, puedes adquirir un bloqueo durante una solicitud web y desear liberar el bloqueo al final de un trabajo en cola que es activado por esa solicitud. En este escenario, debes pasar el "token de propietario" con alcance del bloqueo al trabajo en cola para que el trabajo pueda reinstanciar el bloqueo utilizando el token dado.
En el ejemplo a continuación, despacharemos un trabajo en cola si se adquiere un bloqueo con éxito. Además, pasaremos el token del propietario del bloqueo al trabajo en cola a través del método owner
del bloqueo:
$podcast = Podcast::find($id); $lock = Cache::lock('processing', 120); if ($lock->get()) { ProcessPodcast::dispatch($podcast, $lock->owner()); }
Dentro del trabajo ProcessPodcast
de nuestra aplicación, podemos restaurar y liberar el bloqueo utilizando el token del propietario:
Cache::restoreLock('processing', $this->owner)->release();
Si deseas liberar un bloqueo sin respetar su propietario actual, puedes usar el método forceRelease
:
Cache::lock('processing')->forceRelease();
Agregar Controladores de Caché Personalizados
Escribir el Driver
Para crear nuestro driver de caché personalizado, primero necesitamos implementar el contrato Illuminate\Contracts\Cache\Store
contract. Así que, una implementación de caché de MongoDB podría verse algo así:
<?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() {} }
Solo necesitamos implementar cada uno de estos métodos utilizando una conexión a MongoDB. Para un ejemplo de cómo implementar cada uno de estos métodos, echa un vistazo a la Illuminate\Cache\MemcachedStore
en el código fuente del framework Laravel. Una vez que nuestra implementación esté completa, podemos finalizar nuestro registro de driver personalizado llamando al método extend
de la fachada Cache
:
Cache::extend('mongo', function (Application $app) { return Cache::repository(new MongoStore); });
Si te preguntas dónde colocar tu código del driver de caché personalizado, podrías crear un espacio de nombres Extensions
dentro de tu directorio app
. Sin embargo, ten en cuenta que Laravel no tiene una estructura de aplicación rígida y eres libre de organizar tu aplicación según tus preferencias.
Registrar el Driver
Para registrar el driver de caché personalizado con Laravel, utilizaremos el método extend
en la fachada Cache
. Dado que otros proveedores de servicios pueden intentar leer los valores en caché dentro de su método boot
, registraremos nuestro driver personalizado en un callback booting
. Al usar el callback booting
, podemos asegurarnos de que el driver personalizado se registre justo antes de que se llame al método boot
en los proveedores de servicios de nuestra aplicación, pero después de que se llame al método register
en todos los proveedores de servicios. Registraremos nuestro callback booting
dentro del método register
de la clase App\Providers\AppServiceProvider
de nuestra aplicación:
<?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 { /** * Register any application services. */ public function register(): void { $this->app->booting(function () { Cache::extend('mongo', function (Application $app) { return Cache::repository(new MongoStore); }); }); } /** * Bootstrap any application services. */ public function boot(): void { // ... } }
El primer argumento pasado al método extend
es el nombre del driver. Esto corresponderá a tu opción driver
en el archivo de configuración config/cache.php
. El segundo argumento es una función anónima que debe devolver una instancia de Illuminate\Cache\Repository
. La función anónima recibirá una instancia de $app
, que es una instancia del contenedor de servicios.
Una vez que tu extensión esté registrada, actualiza la variable de entorno CACHE_STORE
o la opción default
dentro del archivo de configuración config/cache.php
de tu aplicación al nombre de tu extensión.
Eventos
Para ejecutar código en cada operación de caché, puedes escuchar varios eventos despachados por la caché:
Nombre del Evento |
---|
Illuminate\Cache\Events\CacheHit |
Illuminate\Cache\Events\CacheMissed |
Illuminate\Cache\Events\KeyForgotten |
Illuminate\Cache\Events\KeyWritten |
'database' => [ 'driver' => 'database', // ... 'events' => false, ],