Cache
Cache
Introduction
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.
Configuration
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.
Driver Prerequisites
Database
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. 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 estas usando MongoDb, el cache driver mongodb es proporcionado por el paquete oficial mongodb/laravel-mongodb y puede ser configurado usando una conexion a base de datos de mongodb . MongoDB soporta indexado TTL, el cual puede ser usado para automaticamente limpiar los items expirados de la cache
Para mas información: Cache and Locks documentation.
Cache Usage
Obtaining a Cache Instance
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 [
// ...
];
}
}
Accessing Multiple Cache Stores
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
Retrieving Items From the Cache
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();
});
Determining Item Existence
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')) {
// ...
}
Incrementing / Decrementing Values
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);
Retrieve and Store
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
Cuando se usa 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 el valor en caché se recalcula en segundo plano, evitando que algunos usuarios experimenten tiempos de respuesta lentos mientras se calculan los valores en caché. Esto se conoce comúnmente 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 en el array representa el número de segundos que la caché se considera fresca, mientras que el segundo valor define cuánto tiempo puede servirse como datos obsoletos antes de que sea necesaria una recalculación.
Si se realiza una solicitud dentro del período fresco (antes del primer valor), la caché se devuelve inmediatamente sin recalculación. Si se realiza una solicitud durante el período de obsolescencia (entre los dos valores), se sirve el valor obsoleto al usuario y se registra una función diferida 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::rememberForever('users', function () {
return DB::table('users')->get();
});
Retrieve and Delete
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');
Storing Items in the Cache
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));
Store if Not Present
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);
Storing Items Forever
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.
Removing Items From the Cache
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.
The Cache Helper
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.
Atomic Locks
❗ 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.
Managing Locks
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 instruir 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...
});
Managing Locks Across Processes
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();
Adding Custom Cache Drivers
Writing the 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.
Registering the 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.
Events
Para ejecutar código en cada operación de caché, puedes escuchar varios eventos despachados por la caché:
| Event Name |
|---|
| Illuminate\Cache\Events\CacheHit |
| Illuminate\Cache\Events\CacheMissed |
| Illuminate\Cache\Events\KeyForgotten |
| Illuminate\Cache\Events\KeyWritten |
Para aumentar el rendimiento, puedes deshabilitar los eventos de caché configurando la opción de configuración events a false para un almacén de caché dado en el archivo de configuración config/cache.php de tu aplicación:
'database' => [
'driver' => 'database',
// ...
'events' => false,
],