Cache
Introducción
Algunas de las tareas de recuperación o procesamiento de datos realizadas por tu aplicación podrían 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 puedan ser recuperados 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 proporciona 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 por defecto en toda tu aplicación. Laravel admite backends de caché populares como Memcached, Redis, DynamoDB y bases de datos relacionales de forma predeterminada. Además, hay un controlador de caché basado en archivos disponible, mientras que los controladores de caché de array y «null» proporcionan backends 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 controlador de caché de base de datos, que almacena los objetos en caché serializados en la base de datos de tu aplicación.
Requisitos Previos del Controlador
Base de Datos
Cuando uses el controlador de caché de base de datos, 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 por defecto 0001_01_01_000001_create_cache_table.php
de Laravel; 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 controlador 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 establecerse en 0:
'memcached' => [
// ...
'servers' => [
[
'host' => '/var/run/memcached/memcached.sock',
'port' => 0,
'weight' => 100,
],
],
],
Redis
Antes de usar una caché de 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 más información sobre cómo configurar Redis, consulta su página de documentación de Laravel.
DynamoDB
Antes de usar el controlador de caché de DynamoDB, debes crear una tabla de DynamoDB para almacenar todos los datos en caché. Típicamente, esta tabla debería llamarse cache
. Sin embargo, deberías nombrar la tabla según el valor de la opción de configuración stores.dynamodb.table
dentro de tu archivo de configuración de caché. El nombre de la tabla también puede establecerse a través de la variable de entorno DYNAMODB_CACHE_TABLE
.
Esta tabla también debe tener una clave de partición de tipo cadena con un nombre que corresponda al valor del elemento de configuración stores.dynamodb.attributes.key
dentro de tu archivo de configuración de caché. 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, debes habilitar el Tiempo de Vida (TTL) en la tabla. Al configurar los ajustes de 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 usando MongoDB, se proporciona un controlador de caché de mongodb por el paquete oficial mongodb/laravel-mongodb
y puede configurarse utilizando una conexión de base de datos de mongodb. MongoDB admite índices TTL, que pueden usarse para eliminar automáticamente los elementos de caché expirados.
Para más información sobre cómo configurar MongoDB, consulta la documentación de Caché y Bloqueos de MongoDB.
Uso de Caché
Obteniendo una Instancia de Caché
Para obtener una instancia de almacén de caché, puedes usar la fachada Cache
, que es lo que usaremos a lo largo de esta documentación. La fachada Cache
proporciona 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
{
/**
* Mostrar una lista de todos los usuarios de la aplicación.
*/
public function index(): array
{
$value = Cache::get('key');
return [
// ...
];
}
}
Accediendo a Múltiples Almacenes de Caché
Usando la fachada Cache
, puedes acceder a varios almacenes de caché a través del método store
. La clave pasada al método store
debe corresponder a uno de los almacenes listados en el array de configuración de almacenes en tu archivo de configuración de caché:
$value = Cache::store('file')->get('foo');
Cache::store('redis')->put('bar', 'baz', 600); // 10 Minutos
Recuperando 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 deseas, puedes pasar un segundo argumento al método get
especificando el valor predeterminado que deseas que se devuelva si el elemento no existe:
$value = Cache::get('key');
$value = Cache::get('key', 'default');
Incluso puedes pasar una closure como el valor predeterminado. El resultado de la closure se devolverá si el elemento especificado no existe en la caché. Pasar una closure te permite diferir 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 usarse 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
pueden usarse para ajustar el valor de elementos enteros en la caché. Ambos métodos aceptan un segundo argumento opcional que indica la cantidad por la que se debe incrementar o decrementar el valor del elemento:
// Inicializa el valor si no existe...
Cache::add('key', 0, now()->addHours(4));
// Incrementar o decrementar el valor...
Cache::increment('key');
Cache::increment('key', $amount);
Cache::decrement('key');
Cache::decrement('key', $amount);
Recuperar y Almacenar
A veces, puedes desear recuperar un elemento de la caché, pero también almacenar un valor predeterminado si el elemento solicitado no existe. Por ejemplo, puedes desear 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 usando 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 closure 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 para siempre si no existe:
$value = Cache::rememberForever('users', function () {
return DB::table('users')->get();
});
Stale While Revalidate
Al usar 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 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 la recalibración.
$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');
Almacenando 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 no se pasa el tiempo de almacenamiento 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 agregará el elemento a la caché si no existe ya en el almacén de caché. El método devolverá true
si el elemento se agrega realmente 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
puede usarse para almacenar un elemento en la caché de forma permanente. Dado que estos elementos no expirarán, deben eliminarse manualmente de la caché usando el método forget
:
Cache::forever('key', 'value');
Si estás usando el controlador de Memcached, los elementos que se almacenan «para siempre» pueden eliminarse cuando la caché alcanza su límite de tamaño.
Eliminando Elementos de la Caché
Puedes eliminar elementos de la caché usando el método forget
:
Cache::forget('key');
También puedes eliminar elementos proporcionando un número cero o negativo de segundos de expiración:
Cache::put('key', 'value', 0);
Cache::put('key', 'value', -5);
Puedes limpiar toda la caché usando el método flush
:
Cache::flush();
Limpiar la caché no respeta tu «prefijo» de caché configurado y eliminará todas las entradas de la caché. Considera esto cuidadosamente al limpiar una caché que se comparte con otras aplicaciones.
El Helper de Caché
Además de usar la fachada 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á valores en la caché durante 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 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 fachada.
Bloqueos Atómicos
Para utilizar esta función, tu aplicación debe estar usando el controlador de caché memcached
, redis
, dynamodb
, database
, file
o array
como el controlador de caché predeterminado de tu aplicación. Además, todos los servidores deben comunicarse con el mismo servidor de caché central.
Gestionando 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 garantizar que solo se ejecute una tarea remota en un servidor a la vez. Puedes crear y gestionar bloqueos usando el método Cache::lock
:
use Illuminate\Support\Facades\Cache;
$lock = Cache::lock('foo', 10);
if ($lock->get()) {
// Bloqueo adquirido durante 10 segundos...
$lock->release();
}
El método get
también acepta una closure. Después de que se ejecute la closure, Laravel liberará automáticamente el bloqueo:
Cache::lock('foo', 10)->get(function () {
// Bloqueo adquirido durante 10 segundos y liberado automáticamente...
});
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 se puede adquirir 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);
// Bloqueo adquirido después de esperar un máximo de 5 segundos...
} catch (LockTimeoutException $e) {
// No se pudo adquirir el bloqueo...
} finally {
$lock->release();
}
El ejemplo anterior puede simplificarse pasando una closure al método block
. Cuando se pasa una closure 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 closure:
Cache::lock('foo', 10)->block(5, function () {
// Bloqueo adquirido después de esperar un máximo de 5 segundos...
});
Gestionando Bloqueos a Través de Procesos
A veces, puedes desear 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 se activa por esa solicitud. En este escenario, debes pasar el «token de propietario» del bloqueo al trabajo en cola para que el trabajo pueda reinstanciar el bloqueo utilizando el token dado.
$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 usando el token de 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();
Agregando Controladores de Caché Personalizados
Escribiendo el Controlador
Para crear nuestro controlador de caché personalizado, primero necesitamos implementar el contrato Illuminate\Contracts\Cache\Store
. Así, 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 de MongoDB. Para un ejemplo de cómo implementar cada uno de estos métodos, consulta Illuminate\Cache\MemcachedStore
en el código fuente del framework Laravel. Una vez que nuestra implementación esté completa, podemos finalizar el registro de nuestro controlador 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 el código de tu controlador de caché personalizado, podrías crear un espacio de nombres Extensions
dentro de tu directorio de aplicación. 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.
Registrando el Controlador
Para registrar el controlador de caché personalizado con Laravel, utilizaremos el método extend
en la fachada Cache
. Dado que otros proveedores de servicios pueden intentar leer valores en caché dentro de su método boot
, registraremos nuestro controlador personalizado dentro de un callback de arranque. Al usar el callback de arranque, podemos asegurarnos de que el controlador 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 de arranque 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
{
/**
* Registrar cualquier servicio de la aplicación.
*/
public function register(): void
{
$this->app->booting(function () {
Cache::extend('mongo', function (Application $app) {
return Cache::repository(new MongoStore);
});
});
}
/**
* Inicializar cualquier servicio de la aplicación.
*/
public function boot(): void
{
// ...
}
}
El primer argumento pasado al método extend
es el nombre del controlador. Esto corresponderá a tu opción de controlador en el archivo de configuración config/cache.php
. El segundo argumento es una closure que debe devolver una instancia de Illuminate\Cache\Repository
. La closure se pasará 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 predeterminada 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 |
Para aumentar el rendimiento, puedes desactivar los eventos de caché estableciendo la opción de configuración events
en 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,
],