Cache

Introduzione

Alcune operazioni di recupero o elaborazione dei dati eseguite dalla tua applicazione potrebbero richiedere molte risorse CPU o impiegare diversi secondi per essere completate. In questi casi, è comune memorizzare nella cache i dati recuperati per un certo periodo in modo che possano essere ottenuti rapidamente nelle richieste successive per gli stessi dati. I dati memorizzati nella cache sono solitamente conservati in un data store molto veloce come Memcached o Redis.

Fortunatamente, Laravel offre un’API espressiva e unificata per diversi sistemi di cache, permettendoti di sfruttare il loro recupero dati super veloce e velocizzare la tua applicazione web.

Configurazione

Il file di configurazione della cache della tua applicazione si trova in config/cache.php. In questo file, puoi specificare quale store di cache vuoi usare di default in tutta l’applicazione. Laravel supporta backend di cache popolari come Memcached, Redis, DynamoDB e database relazionali. Inoltre, è disponibile un driver per la cache basata su file, mentre i driver array e "null" offrono soluzioni comode per i tuoi test automatici.

Il file di configurazione della cache contiene anche diverse altre opzioni che puoi esaminare. Per impostazione predefinita, Laravel è configurato per usare il driver di cache database, che memorizza gli oggetti serializzati nella database della tua applicazione.

Prerequisiti del Driver

Database

Quando utilizzi il driver di cache database, hai bisogno di una tabella nel database per memorizzare i dati della cache. Di solito, questa tabella è inclusa nella migrazione predefinita di Laravel 0001_01_01_000001_create_cache_table.php migrazione del database. Tuttavia, se la tua applicazione non include questa migrazione, puoi usare il comando Artisan make:cache-table per crearla:

php artisan make:cache-table

php artisan migrate

Memcached

Usare il driver Memcached richiede l’installazione del pacchetto Memcached PECL. Puoi elencare tutti i tuoi server Memcached nel file di configurazione config/cache.php. Questo file contiene già una voce memcached.servers per iniziare:

'memcached' => [
    // ...

    'servers' => [
        [
            'host' => env('MEMCACHED_HOST', '127.0.0.1'),
            'port' => env('MEMCACHED_PORT', 11211),
            'weight' => 100,
        ],
    ],
],

Se necessario, puoi impostare l’opzione host su un percorso di socket UNIX. In questo caso, l’opzione port deve essere impostata su 0:

'memcached' => [
    // ...

    'servers' => [
        [
            'host' => '/var/run/memcached/memcached.sock',
            'port' => 0,
            'weight' => 100
        ],
    ],
],

Redis

Prima di usare una cache Redis con Laravel, devi installare l’estensione PHP PhpRedis tramite PECL oppure installare il pacchetto predis/predis (~2.0) tramite Composer. Laravel Sail include già questa estensione. Inoltre, le piattaforme di deployment ufficiali di Laravel come Laravel Forge e Laravel Vapor hanno l’estensione PhpRedis installata di default.

Per maggiori informazioni sulla configurazione di Redis, consulta la sua pagina della documentazione Laravel.

DynamoDB

Prima di usare il driver cache DynamoDB, devi creare una tabella DynamoDB per memorizzare tutti i dati in cache. Di solito, questa tabella dovrebbe chiamarsi cache. Tuttavia, dovresti nominare la tabella in base al valore della configurazione stores.dynamodb.table nel file di configurazione cache. Il nome della tabella può essere impostato anche tramite la variabile di ambiente DYNAMODB_CACHE_TABLE.

Questa tabella dovrebbe avere anche una chiave di partizione di tipo stringa con un nome che corrisponde al valore dell’elemento di configurazione stores.dynamodb.attributes.key nel file di configurazione cache della tua applicazione. Di default, la chiave di partizione dovrebbe chiamarsi key.

In genere, DynamoDB non rimuove proattivamente gli elementi scaduti da una tabella. Pertanto, dovresti abilitare Time to Live (TTL) sulla tabella. Quando configuri le impostazioni TTL della tabella, dovresti impostare il nome dell’attributo TTL su expires_at.

Successivamente, installa l’SDK AWS in modo che la tua applicazione Laravel possa comunicare con DynamoDB:

composer require aws/aws-sdk-php

Inoltre, dovresti assicurarti che vengano forniti valori per le opzioni di configurazione del negozio cache DynamoDB. Tipicamente, queste opzioni, come AWS_ACCESS_KEY_ID e AWS_SECRET_ACCESS_KEY, dovrebbero essere definite nel file di configurazione .env della tua applicazione:

'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

Se stai usando MongoDB, un driver di cache mongodb è fornito dal pacchetto ufficiale mongodb/laravel-mongodb e può essere configurato usando una connessione al database mongodb. MongoDB supporta indici TTL, che possono essere usati per cancellare automaticamente gli elementi di cache scaduti.

Per maggiori informazioni sulla configurazione di MongoDB, consulta la documentazione MongoDB Cache and Locks.

Utilizzo della Cache

Ottenere un’istanza di Cache

Per ottenere un’istanza di uno store di cache, puoi utilizzare la facade Cache, che useremo in tutta questa documentazione. La facade Cache offre un accesso comodo e conciso alle implementazioni sottostanti dei contratti di cache di Laravel:

    <?php

    namespace App\Http\Controllers;

    use Illuminate\Support\Facades\Cache;

    class UserController extends Controller
    {
        /**
         * Mostra una lista di tutti gli utenti dell'applicazione.
         */
        public function index(): array
        {
            $value = Cache::get('key');

            return [
                // ...
            ];
        }
    }

Accesso a più cache store

Usando la facciata Cache, puoi accedere a diversi cache store tramite il metodo store. La chiave passata al metodo store deve corrispondere a uno degli store elencati nell’array di configurazione stores nel tuo file di configurazione cache:

    $value = Cache::store('file')->get('foo');

    Cache::store('redis')->put('bar', 'baz', 600); // 10 minuti

Recuperare Elementi dalla Cache

Il metodo get del facade Cache viene utilizzato per recuperare elementi dalla cache. Se l’elemento non esiste nella cache, verrà restituito null. Se vuoi, puoi passare un secondo argomento al metodo get specificando il valore di default che desideri venga restituito se l’elemento non esiste:

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

$value = Cache::get('key', 'default');

Puoi anche passare una closure come valore di default. Il risultato della closure verrà restituito se l’elemento specificato non esiste nella cache. Passare una closure ti permette di ritardare il recupero dei valori di default da un database o da un altro servizio esterno:

$value = Cache::get('key', function () {
    return DB::table(/* ... */)->get();
});

Verificare se un Elemento Esiste

Il metodo has può essere usato per verificare se un elemento esiste nella cache. Questo metodo restituirà anche false se l’elemento esiste ma il suo valore è null:

if (Cache::has('key')) {
    // ...
}

Incremento / Decremento dei Valori

I metodi increment e decrement possono essere usati per modificare il valore degli elementi interi nella cache. Entrambi questi metodi accettano un secondo argomento opzionale che indica l’ammontare di incremento o decremento del valore dell’elemento:

// Inizializza il valore se non esiste...
Cache::add('key', 0, now()->addHours(4));

// Incrementa o decrementa il valore...
Cache::increment('key');
Cache::increment('key', $amount);
Cache::decrement('key');
Cache::decrement('key', $amount);

Recupera e Memorizza

A volte potresti voler recuperare un elemento dalla cache, ma anche memorizzare un valore predefinito se l’elemento richiesto non esiste. Ad esempio, potresti voler recuperare tutti gli utenti dalla cache oppure, se non esistono, recuperarli dal database e aggiungerli alla cache. Puoi farlo usando il metodo Cache::remember:

    $value = Cache::remember('users', $seconds, function () {
        return DB::table('users')->get();
    });

Se l’elemento non esiste nella cache, la closure passata al metodo remember verrà eseguita e il suo risultato sarà inserito nella cache.

Puoi usare il metodo rememberForever per recuperare un elemento dalla cache o memorizzarlo indefinitamente se non esiste:

    $value = Cache::rememberForever('users', function () {
        return DB::table('users')->get();
    });

Stale While Revalidate

Quando si utilizza il metodo Cache::remember, alcuni utenti potrebbero riscontrare tempi di risposta lenti se il valore memorizzato nella cache è scaduto. Per certi tipi di dati, può essere utile permettere di servire dati parzialmente obsoleti mentre il valore della cache viene ricalcolato in background, evitando che alcuni utenti sperimentino tempi di risposta lenti durante il ricalcolo dei valori memorizzati. Questo è spesso chiamato pattern "stale-while-revalidate" e il metodo Cache::flexible fornisce un’implementazione di questo pattern.

Il metodo flexible accetta un array che specifica per quanto tempo il valore della cache è considerato “fresco” e quando diventa “stale”. Il primo valore nell’array rappresenta il numero di secondi in cui la cache è considerata fresca, mentre il secondo valore definisce per quanto tempo può essere servita come dato obsoleto prima che sia necessario un ricalcolo.

Se viene fatta una richiesta durante il periodo fresco (prima del primo valore), la cache viene restituita immediatamente senza ricalcolo. Se viene fatta una richiesta durante il periodo stale (tra i due valori), viene servito all’utente il valore obsoleto e viene registrata una deferred function per aggiornare il valore della cache dopo che la risposta è stata inviata all’utente. Se viene fatta una richiesta dopo il secondo valore, la cache è considerata scaduta e il valore viene ricalcolato immediatamente, il che può comportare una risposta più lenta per l’utente:

$value = Cache::flexible('users', [5, 10], function () {
    return DB::table('users')->get();
});

Recupera ed Elimina

Se hai bisogno di recuperare un elemento dalla cache e poi eliminarlo, puoi usare il metodo pull. Come il metodo get, verrà restituito null se l’elemento non esiste nella cache:

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

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

Memorizzare Elementi nella Cache

Puoi usare il metodo put sulla facade Cache per memorizzare elementi nella cache:

Cache::put('key', 'value', $seconds = 10);

Se il tempo di memorizzazione non viene passato al metodo put, l’elemento sarà memorizzato indefinitamente:

Cache::put('key', 'value');

Invece di passare il numero di secondi come intero, puoi anche passare un’istanza di DateTime che rappresenta il tempo di scadenza desiderato per l’elemento memorizzato:

Cache::put('key', 'value', now()->addMinutes(10));

Memorizza solo se assente

Il metodo add aggiunge l’elemento alla cache solo se non esiste già nel cache store. Il metodo restituirà true se l’elemento viene effettivamente aggiunto alla cache. Altrimenti, restituirà false. Il metodo add è un’operazione atomica:

Cache::add('key', 'value', $seconds);

Memorizzare gli Elementi per Sempre

Il metodo forever può essere usato per memorizzare un elemento nella cache in modo permanente. Poiché questi elementi non scadono, devono essere rimossi manualmente dalla cache utilizzando il metodo forget:

Cache::forever('key', 'value');

Se stai usando il driver Memcached, gli elementi memorizzati "per sempre" potrebbero essere rimossi quando la cache raggiunge il suo limite di dimensione.

Rimuovere Elementi dalla Cache

Puoi rimuovere elementi dalla cache usando il metodo forget:

    Cache::forget('key');

Puoi anche rimuovere elementi fornendo un numero di secondi di scadenza pari a zero o negativo:

    Cache::put('key', 'value', 0);

    Cache::put('key', 'value', -5);

Puoi svuotare l’intera cache usando il metodo flush:

    Cache::flush();

Svuotare la cache non rispetta il "prefisso" configurato e rimuoverà tutte le voci dalla cache. Considera attentamente questa operazione quando svuoti una cache condivisa da altre applicazioni.

L’Helper Cache

Oltre a usare il facade Cache, puoi anche utilizzare la funzione globale cache per recuperare e memorizzare dati nella cache. Quando la funzione cache viene chiamata con un solo argomento di tipo stringa, restituirà il valore della chiave specificata:

$value = cache('key');

Se fornisci un array di coppie chiave / valore e un tempo di scadenza alla funzione, essa memorizzerà i valori nella cache per la durata specificata:

cache(['key' => 'value'], $seconds);

cache(['key' => 'value'], now()->addMinutes(10));

Quando la funzione cache viene chiamata senza argomenti, restituisce un’istanza dell’implementazione di Illuminate\Contracts\Cache\Factory, permettendoti di chiamare altri metodi di caching:

cache()->remember('users', $seconds, function () {
    return DB::table('users')->get();
});

Durante il test delle chiamate alla funzione globale cache, puoi usare il metodo Cache::shouldReceive proprio come se stessi testando il facade.

Blocchi Atomici

Per utilizzare questa funzionalità, la tua applicazione deve usare il driver di cache memcached, redis, dynamodb, database, file o array come driver di cache predefinito. Inoltre, tutti i server devono comunicare con lo stesso server di cache centrale.

Gestire i Lock

I lock atomici permettono di gestire lock distribuiti senza preoccuparsi delle condizioni di race. Ad esempio, Laravel Forge utilizza lock atomici per garantire che solo un’attività remota venga eseguita su un server alla volta. Puoi creare e gestire i lock usando il metodo Cache::lock:

use Illuminate\Support\Facades\Cache;

$lock = Cache::lock('foo', 10);

if ($lock->get()) {
    // Lock acquisito per 10 secondi...

    $lock->release();
}

Il metodo get accetta anche una closure. Dopo che la closure viene eseguita, Laravel rilascerà automaticamente il lock:

Cache::lock('foo', 10)->get(function () {
    // Lock acquisito per 10 secondi e rilasciato automaticamente...
});

Se il lock non è disponibile al momento della richiesta, puoi istruire Laravel ad aspettare per un numero specificato di secondi. Se il lock non può essere acquisito entro il limite di tempo specificato, verrà lanciata un’Illuminate\Contracts\Cache\LockTimeoutException:

use Illuminate\Contracts\Cache\LockTimeoutException;

$lock = Cache::lock('foo', 10);

try {
    $lock->block(5);

    // Lock acquisito dopo aver atteso al massimo 5 secondi...
} catch (LockTimeoutException $e) {
    // Impossibile acquisire il lock...
} finally {
    $lock->release();
}

L’esempio sopra può essere semplificato passando una closure al metodo block. Quando una closure viene passata a questo metodo, Laravel tenterà di acquisire il lock per il numero di secondi specificato e rilascerà automaticamente il lock una volta eseguita la closure:

Cache::lock('foo', 10)->block(5, function () {
    // Lock acquisito dopo aver atteso al massimo 5 secondi...
});

Gestione dei Lock tra Processi

A volte, potresti voler acquisire un lock in un processo e rilasciarlo in un altro processo. Ad esempio, potresti acquisire un lock durante una richiesta web e desiderare rilasciare il lock alla fine di un job in coda attivato da quella richiesta. In questo scenario, dovresti passare il "owner token" del lock al job in coda affinché il job possa re-instanziare il lock usando il token fornito.

Nell’esempio seguente, dispatcheremo un job in coda se un lock viene acquisito con successo. Inoltre, passeremo il "owner token" del lock al job in coda tramite il metodo owner del lock:

    $podcast = Podcast::find($id);

    $lock = Cache::lock('processing', 120);

    if ($lock->get()) {
        ProcessPodcast::dispatch($podcast, $lock->owner());
    }

All’interno del job ProcessPodcast della nostra applicazione, possiamo ripristinare e rilasciare il lock usando il "owner token":

    Cache::restoreLock('processing', $this->owner)->release();

Se desideri rilasciare un lock senza rispettare il suo owner attuale, puoi usare il metodo forceRelease:

    Cache::lock('processing')->forceRelease();

Aggiungere driver di cache personalizzati

Scrivere il Driver

Per creare il nostro driver di cache personalizzato, dobbiamo prima implementare il Illuminate\Contracts\Cache\Store contratto. Quindi, un’implementazione della cache con MongoDB potrebbe essere simile a questa:

<?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() {}
}

Dobbiamo solo implementare ciascuno di questi metodi utilizzando una connessione MongoDB. Per un esempio di come implementare ciascuno di questi metodi, dai un’occhiata a Illuminate\Cache\MemcachedStore nel codice sorgente del framework Laravel. Una volta completata la nostra implementazione, possiamo finire la registrazione del driver personalizzato chiamando il metodo extend della facade Cache:

Cache::extend('mongo', function (Application $app) {
    return Cache::repository(new MongoStore);
});

Se ti stai chiedendo dove mettere il codice del tuo driver di cache personalizzato, puoi creare uno spazio dei nomi Extensions nella directory app. Tuttavia, ricorda che Laravel non ha una struttura applicativa rigida e puoi organizzare la tua applicazione come preferisci.

Registrare il Driver

Per registrare il driver cache personalizzato con Laravel, utilizzeremo il metodo extend sulla facade Cache. Poiché altri service provider potrebbero tentare di leggere valori cache all’interno del loro metodo boot, registreremo il nostro driver personalizzato all’interno di un callback booting. Usando il callback booting, possiamo assicurarci che il driver personalizzato sia registrato poco prima che il metodo boot venga chiamato sui service provider della nostra applicazione, ma dopo che il metodo register sia stato chiamato su tutti i service provider. Registreremo il nostro callback booting all’interno del metodo register della classe App\Providers\AppServiceProvider della nostra applicazione:

    <?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
        {
            // ...
        }
    }

Il primo argomento passato al metodo extend è il nome del driver. Questo corrisponderà alla tua opzione driver nel file di configurazione config/cache.php. Il secondo argomento è una closure che dovrebbe restituire un’istanza di Illuminate\Cache\Repository. La closure riceverà un’istanza $app, che è un’istanza del service container.

Una volta che la tua estensione è registrata, aggiorna la variabile d’ambiente CACHE_STORE o l’opzione default nel file di configurazione config/cache.php della tua applicazione con il nome della tua estensione.

Events

Per eseguire del codice ad ogni operazione di cache, puoi ascoltare vari eventi emessi dalla cache:

Nome Evento
Illuminate\Cache\Events\CacheHit
Illuminate\Cache\Events\CacheMissed
Illuminate\Cache\Events\KeyForgotten
Illuminate\Cache\Events\KeyWritten

Per migliorare le prestazioni, puoi disabilitare gli eventi della cache impostando l’opzione di configurazione events su false per uno specifico store della cache nel file di configurazione config/cache.php della tua applicazione:

'database' => [
    'driver' => 'database',
    // ...
    'events' => false,
],
Lascia un commento

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *