Octane

Introduzione

Laravel Octane migliora le prestazioni della tua applicazione servendola tramite server applicativi potenti, tra cui FrankenPHP, Open Swoole, Swoole e RoadRunner. Octane avvia la tua applicazione una volta sola, la mantiene in memoria e poi gestisce le richieste a velocità supersonica.

Installazione

Octane può essere installato tramite il gestore di pacchetti Composer:

composer require laravel/octane

Dopo aver installato Octane, puoi eseguire il comando Artisan octane:install, che installerà il file di configurazione di Octane nella tua applicazione:

php artisan octane:install

Prerequisiti del Server

Laravel Octane richiede PHP 8.1+.

FrankenPHP

FrankenPHP è un server per applicazioni PHP, scritto in Go, che supporta funzionalità web moderne come early hints, compressione Brotli e Zstandard. Quando installi Octane e scegli FrankenPHP come server, Octane scaricherà e installerà automaticamente il binario di FrankenPHP per te.

FrankenPHP tramite Laravel Sail

Se intendi sviluppare la tua applicazione utilizzando Laravel Sail, dovresti eseguire i seguenti comandi per installare Octane e FrankenPHP:

./vendor/bin/sail up

./vendor/bin/sail composer require laravel/octane

Successivamente, dovresti usare il comando Artisan octane:install per installare il binario di FrankenPHP:

./vendor/bin/sail artisan octane:install --server=frankenphp

Infine, aggiungi una variabile d’ambiente SUPERVISOR_PHP_COMMAND alla definizione del servizio laravel.test nel file docker-compose.yml della tua applicazione. Questa variabile d’ambiente conterrà il comando che Sail utilizzerà per servire la tua applicazione usando Octane invece del server di sviluppo PHP:

services:
  laravel.test:
    environment:
      SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --server=frankenphp --host=0.0.0.0 --admin-port=2019 --port='${APP_PORT:-80}'" # [tl! add]
      XDG_CONFIG_HOME:  /var/www/html/config # [tl! add]
      XDG_DATA_HOME:  /var/www/html/data # [tl! add]

Per abilitare HTTPS, HTTP/2 e HTTP/3, applica invece queste modifiche:

services:
  laravel.test:
    ports:
        - '${APP_PORT:-80}:80'
        - '${VITE_PORT:-5173}:${VITE_PORT:-5173}'
        - '443:443' # [tl! add]
        - '443:443/udp' # [tl! add]
    environment:
      SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --host=localhost --port=443 --admin-port=2019 --https" # [tl! add]
      XDG_CONFIG_HOME:  /var/www/html/config # [tl! add]
      XDG_DATA_HOME:  /var/www/html/data # [tl! add]

Di solito, dovresti accedere alla tua applicazione FrankenPHP Sail tramite https://localhost, poiché utilizzare https://127.0.0.1 richiede una configurazione aggiuntiva ed è sconsigliato.

FrankenPHP tramite Docker

Utilizzare le immagini Docker ufficiali di FrankenPHP può offrire prestazioni migliorate e l’uso di estensioni aggiuntive non incluse nelle installazioni statiche di FrankenPHP. Inoltre, le immagini Docker ufficiali supportano l’esecuzione di FrankenPHP su piattaforme che non lo supporta nativamente, come Windows. Le immagini Docker ufficiali di FrankenPHP sono adatte sia per lo sviluppo locale che per l’utilizzo in produzione.

Puoi usare il seguente Dockerfile come punto di partenza per containerizzare la tua applicazione Laravel alimentata da FrankenPHP:

FROM dunglas/frankenphp

RUN install-php-extensions \
    pcntl
		
# Aggiungi altre estensioni PHP qui...

COPY . /app

ENTRYPOINT ["php", "artisan", "octane:frankenphp"]

Quindi, durante lo sviluppo, puoi utilizzare il seguente file Docker Compose per eseguire la tua applicazione:

# compose.yaml
services:
  frankenphp:
    build:
      context: .
    entrypoint: php artisan octane:frankenphp --workers=1 --max-requests=1
    ports:
      - "8000:8000"
    volumes:
      - .:/app

Se l’opzione --log-level viene esplicitamente passata al comando php artisan octane:start, Octane utilizzerà il logger nativo di FrankenPHP e, a meno che non sia configurato diversamente, produrrà log JSON strutturati.

Puoi consultare la documentazione ufficiale di FrankenPHP per ulteriori informazioni su come eseguire FrankenPHP con Docker.

RoadRunner

RoadRunner funziona grazie al binario RoadRunner, creato con Go. La prima volta che avvii un server Octane basato su RoadRunner, Octane ti offrirà di scaricare e installare automaticamente il binario RoadRunner.

RoadRunner tramite Laravel Sail

Se hai intenzione di sviluppare la tua applicazione utilizzando Laravel Sail, dovresti eseguire i seguenti comandi per installare Octane e RoadRunner:

./vendor/bin/sail up

./vendor/bin/sail composer require laravel/octane spiral/roadrunner-cli spiral/roadrunner-http

Successivamente, dovresti avviare una shell di Sail e utilizzare l’eseguibile rr per ottenere l’ultima build basata su Linux del binario di RoadRunner:

./vendor/bin/sail shell

# Dentro la shell di Sail...
./vendor/bin/rr get-binary

Poi, aggiungi una variabile d’ambiente SUPERVISOR_PHP_COMMAND alla definizione del servizio laravel.test nel file docker-compose.yml della tua applicazione. Questa variabile conterrà il comando che Sail utilizzerà per servire la tua applicazione usando Octane invece del server di sviluppo PHP:

services:
  laravel.test:
    environment:
      SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --server=roadrunner --host=0.0.0.0 --rpc-port=6001 --port='${APP_PORT:-80}'" # [tl! add]

Infine, assicurati che il binario rr sia eseguibile e costruisci le immagini di Sail:

chmod +x ./rr

./vendor/bin/sail build --no-cache

Swoole

Se intendi utilizzare il server applicativo Swoole per gestire la tua applicazione Laravel Octane, devi installare l’estensione PHP Swoole. Di solito, puoi farlo tramite PECL:

pecl install swoole

Open Swoole

Se desideri utilizzare il server applicativo Open Swoole per eseguire la tua applicazione Laravel Octane, devi installare l’estensione PHP Open Swoole. Tipicamente, questo può essere fatto tramite PECL:

pecl install openswoole

Utilizzare Laravel Octane con Open Swoole offre le stesse funzionalità fornite da Swoole, come attività concorrenti, ticks e intervalli.

Swoole tramite Laravel Sail

Prima di servire un’applicazione Octane tramite Sail, assicurati di avere l’ultima versione di Laravel Sail ed esegui ./vendor/bin/sail build --no-cache nella directory principale della tua applicazione.

In alternativa, puoi sviluppare la tua applicazione Octane basata su Swoole usando Laravel Sail, l’ambiente di sviluppo ufficiale basato su Docker per Laravel. Laravel Sail include l’estensione Swoole di default. Tuttavia, sarà comunque necessario modificare il file docker-compose.yml utilizzato da Sail.

Per iniziare, aggiungi una variabile d’ambiente SUPERVISOR_PHP_COMMAND alla definizione del servizio laravel.test nel file docker-compose.yml della tua applicazione. Questa variabile d’ambiente conterrà il comando che Sail utilizzerà per servire la tua applicazione usando Octane invece del server di sviluppo PHP:

services:
  laravel.test:
    environment:
      SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --server=swoole --host=0.0.0.0 --port='${APP_PORT:-80}'" # [tl! add]

Infine, costruisci le immagini di Sail:

./vendor/bin/sail build --no-cache

Configurazione Swoole

Swoole supporta alcune opzioni di configurazione aggiuntive che puoi aggiungere al tuo file di configurazione octane se necessario. Poiché raramente devono essere modificate, queste opzioni non sono incluse nel file di configurazione predefinito:

'swoole' => [
    'options' => [
        'log_file' => storage_path('logs/swoole_http.log'),
        'package_max_length' => 10 * 1024 * 1024,
    ],
],

Servire la tua applicazione

Il server Octane può essere avviato tramite il comando Artisan octane:start. Di default, questo comando utilizzerà il server specificato dall’opzione di configurazione server nel file di configurazione octane della tua applicazione:

php artisan octane:start

Di default, Octane avvierà il server sulla porta 8000, quindi puoi accedere alla tua applicazione tramite un browser web all’indirizzo http://localhost:8000.

Servire la tua applicazione tramite HTTPS

Di default, le applicazioni eseguite con Octane generano link che iniziano con http://. La variabile d’ambiente OCTANE_HTTPS, presente nel file di configurazione config/octane.php della tua applicazione, può essere impostata su true quando servi la tua applicazione tramite HTTPS. Quando questo valore è impostato su true, Octane farà in modo che Laravel aggiunga https:// all’inizio di tutti i link generati:

'https' => env('OCTANE_HTTPS', false),

Servire la tua applicazione tramite Nginx

Se non sei ancora pronto a gestire la configurazione del tuo server o non ti senti a tuo agio nel configurare tutti i vari servizi necessari per eseguire un’applicazione Laravel Octane robusta, dai un’occhiata a Laravel Forge.

In ambienti di produzione, dovresti servire la tua applicazione Octane dietro a un server web tradizionale come Nginx o Apache. Questo permetterà al server web di gestire gli asset statici come immagini e fogli di stile, oltre a gestire la terminazione del certificato SSL.

Nell’esempio di configurazione di Nginx riportato di seguito, Nginx servirà gli asset statici del sito e reindirizzerà le richieste al server Octane che è in esecuzione sulla porta 8000:

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 80;
    listen [::]:80;
    server_name domain.com;
    server_tokens off;
    root /home/forge/domain.com/public;

    index index.php;

    charset utf-8;

    location /index.php {
        try_files /not_exists @octane;
    }

    location / {
        try_files $uri $uri/ @octane;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    access_log off;
    error_log  /var/log/nginx/domain.com-error.log error;

    error_page 404 /index.php;

    location @octane {
        set $suffix "";

        if ($uri = /index.php) {
            set $suffix ?$query_string;
        }

        proxy_http_version 1.1;
        proxy_set_header Host $http_host;
        proxy_set_header Scheme $scheme;
        proxy_set_header SERVER_PORT $server_port;
        proxy_set_header REMOTE_ADDR $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

        proxy_pass http://127.0.0.1:8000$suffix;
    }
}

Monitoraggio delle Modifiche ai File

Poiché la tua applicazione viene caricata in memoria una volta all’avvio del server Octane, eventuali modifiche ai file della tua applicazione non saranno visibili quando aggiorni il browser. Ad esempio, le definizioni delle route aggiunte al file routes/web.php non saranno riflesse fino al riavvio del server. Per comodità, puoi usare l’opzione --watch per istruire Octane a riavviare automaticamente il server ogni volta che un file della tua applicazione viene modificato:

php artisan octane:start --watch

Prima di usare questa funzionalità, assicurati che Node sia installato nel tuo ambiente di sviluppo locale. Inoltre, dovresti installare la libreria di monitoraggio dei file Chokidar nel tuo progetto:

npm install --save-dev chokidar

Puoi configurare le directory e i file che devono essere monitorati utilizzando l’opzione di configurazione watch nel file di configurazione config/octane.php della tua applicazione.

Specificare il Numero di Worker

Di default, Octane avvierà un worker per ogni core CPU disponibile sulla tua macchina. Questi worker verranno utilizzati per gestire le richieste HTTP in arrivo mentre entrano nella tua applicazione. Puoi specificare manualmente quanti worker desideri avviare usando l’opzione --workers quando esegui il comando octane:start:

php artisan octane:start --workers=4

Se stai utilizzando il server applicativo Swoole, puoi anche specificare quanti "task workers" desideri avviare:

php artisan octane:start --workers=4 --task-workers=6

Specificare il Numero Massimo di Richieste

Per evitare perdite di memoria, Octane riavvia automaticamente ogni worker dopo aver gestito 500 richieste. Per modificare questo valore, puoi usare l’opzione --max-requests:

php artisan octane:start --max-requests=250

Ricaricare i Workers

Puoi riavviare in modo sicuro i workers dell’applicazione del server Octane usando il comando octane:reload. Tipicamente, questo dovrebbe essere fatto dopo il deployment in modo che il tuo codice appena distribuito sia caricato in memoria e venga usato per servire le richieste successive:

php artisan octane:reload

Fermare il Server

Puoi fermare il server Octane usando il comando Artisan octane:stop:

php artisan octane:stop

Controllare lo Stato del Server

Puoi verificare lo stato attuale del server Octane usando il comando Artisan octane:status:

php artisan octane:status

Iniezione delle Dipendenze e Octane

Poiché Octane avvia la tua applicazione una sola volta e la mantiene in memoria durante l’elaborazione delle richieste, ci sono alcuni aspetti da considerare durante la costruzione della tua applicazione. Ad esempio, i metodi register e boot dei service providers della tua applicazione verranno eseguiti solo una volta quando il worker gestisce la richiesta per la prima volta. Nelle richieste successive, verrà riutilizzata la stessa istanza dell’applicazione.

In questo contesto, dovresti prestare particolare attenzione quando inietti il service container dell’applicazione o la richiesta nel costruttore di un oggetto. In questo modo, quell’oggetto potrebbe avere una versione obsoleta del container o della richiesta nelle richieste successive.

Octane gestirà automaticamente il reset di qualsiasi stato del framework di prima parte tra le richieste. Tuttavia, Octane non sempre sa come resettare lo stato globale creato dalla tua applicazione. Perciò, dovresti sapere come costruire la tua applicazione in modo compatibile con Octane. Di seguito, discuteremo le situazioni più comuni che potrebbero causare problemi durante l’uso di Octane.

Container Injection

In generale, dovresti evitare di iniettare il service container dell’applicazione o l’istanza HTTP request nei costruttori di altri oggetti. Per esempio, il binding seguente inietta l’intero service container dell’applicazione in un oggetto registrato come singleton:

use App\Service;
use Illuminate\Contracts\Foundation\Application;

/**
 * Registra i servizi dell'applicazione.
 */
public function register(): void
{
    $this->app->singleton(Service::class, function (Application $app) {
        return new Service($app);
    });
}

In questo esempio, se l’istanza di Service viene risolta durante il processo di bootstrap dell’applicazione, il container verrà iniettato nel servizio e lo stesso container sarà mantenuto dall’istanza di Service nelle richieste successive. Questo potrebbe non essere un problema per la tua applicazione specifica; tuttavia, può portare a situazioni in cui il container manca inaspettatamente di binding aggiunti successivamente nel ciclo di bootstrap o da una richiesta successiva.

Come soluzione alternativa, potresti smettere di registrare il binding come singleton oppure iniettare una closure risolutiva del container nel servizio che risolve sempre l’istanza corrente del container:

use App\Service;
use Illuminate\Container\Container;
use Illuminate\Contracts\Foundation\Application;

$this->app->bind(Service::class, function (Application $app) {
    return new Service($app);
});

$this->app->singleton(Service::class, function () {
    return new Service(fn () => Container::getInstance());
});

Il helper globale app e il metodo Container::getInstance() restituiranno sempre l’ultima versione del container dell’applicazione.

Iniezione della Richiesta

In generale, dovresti evitare di iniettare il service container dell’applicazione o l’istanza della richiesta HTTP nei costruttori di altri oggetti. Ad esempio, il binding seguente inietta l’intera istanza della richiesta in un oggetto registrato come singleton:

use App\Service;
use Illuminate\Contracts\Foundation\Application;

/**
 * Register any application services.
 */
public function register(): void
{
    $this->app->singleton(Service::class, function (Application $app) {
        return new Service($app['request']);
    });
}

In questo esempio, se l’istanza di Service viene risolta durante il processo di avvio dell’applicazione, la richiesta HTTP verrà iniettata nel servizio e la stessa richiesta sarà mantenuta dall’istanza di Service nelle richieste successive. Di conseguenza, tutti gli header, gli input e i dati della stringa di query saranno errati, così come tutti gli altri dati della richiesta.

Come soluzione alternativa, potresti o smettere di registrare il binding come singleton, oppure potresti iniettare una closure risolutrice della richiesta nel servizio che risolve sempre l’istanza della richiesta corrente. Oppure, l’approccio più consigliato è semplicemente passare le informazioni specifiche della richiesta di cui il tuo oggetto ha bisogno a uno dei metodi dell’oggetto durante l’esecuzione:

use App\Service;
use Illuminate\Contracts\Foundation\Application;

$this->app->bind(Service::class, function (Application $app) {
    return new Service($app['request']);
});

$this->app->singleton(Service::class, function (Application $app) {
    return new Service(fn () => $app['request']);
});

// Or...

$service->method($request->input('name'));

Il helper globale request restituirà sempre la richiesta che l’applicazione sta gestendo attualmente ed è quindi sicuro da usare all’interno della tua applicazione.

È accettabile specificare il tipo dell’istanza Illuminate\Http\Request nei metodi del tuo controller e nelle chiusure delle route.

Iniezione del Repository di Configurazione

In generale, dovresti evitare di iniettare l’istanza del repository di configurazione nei costruttori di altri oggetti. Ad esempio, il seguente binding inietta il repository di configurazione in un oggetto registrato come singleton:

use App\Service;
use Illuminate\Contracts\Foundation\Application;

/**
 * Registra i servizi dell'applicazione.
 */
public function register(): void
{
    $this->app->singleton(Service::class, function (Application $app) {
        return new Service($app->make('config'));
    });
}

In questo esempio, se i valori di configurazione cambiano tra le richieste, quel servizio non avrà accesso ai nuovi valori perché dipende dall’istanza originale del repository.

Come soluzione alternativa, potresti o smettere di registrare il binding come singleton, oppure potresti iniettare una closure risolutrice del repository di configurazione nella classe:

use App\Service;
use Illuminate\Container\Container;
use Illuminate\Contracts\Foundation\Application;

$this->app->bind(Service::class, function (Application $app) {
    return new Service($app->make('config'));
});

$this->app->singleton(Service::class, function () {
    return new Service(fn () => Container::getInstance()->make('config'));
});

Il config globale restituirà sempre l’ultima versione del repository di configurazione ed è quindi sicuro da usare all’interno della tua applicazione.

Gestione delle Perdite di Memoria

Ricorda, Octane mantiene la tua applicazione in memoria tra le richieste; quindi, aggiungere dati a un array mantenuto staticamente causerà una perdita di memoria. Ad esempio, il seguente controller presenta una perdita di memoria poiché ogni richiesta all’applicazione continuerà ad aggiungere dati all’array statico $data:

use App\Service;
use Illuminate\Http\Request;
use Illuminate\Support\Str;

/**
 * Gestisci una richiesta in arrivo.
 */
public function index(Request $request): array
{
    Service::$data[] = Str::random(10);

    return [
        // ...
    ];
}

Durante lo sviluppo della tua applicazione, devi prestare particolare attenzione per evitare di creare questo tipo di perdite di memoria. È consigliabile monitorare l’utilizzo della memoria della tua applicazione durante lo sviluppo locale per assicurarti di non introdurre nuove perdite di memoria.

Task Concurrenti

Questa funzionalità richiede Swoole.

Quando usi Swoole, puoi eseguire operazioni contemporaneamente tramite task di background leggeri. Puoi farlo usando il metodo concurrently di Octane. Puoi combinare questo metodo con la destrutturazione degli array in PHP per ottenere i risultati di ogni operazione:

use App\Models\User;
use App\Models\Server;
use Laravel\Octane\Facades\Octane;

[$users, $servers] = Octane::concurrently([
    fn () => User::all(),
    fn () => Server::all(),
]);

I task concurrenti elaborati da Octane utilizzano i "task workers" di Swoole e vengono eseguiti in un processo completamente diverso rispetto alla richiesta in arrivo. Il numero di worker disponibili per elaborare task concurrenti è determinato dalla direttiva --task-workers nel comando octane:start:

php artisan octane:start --workers=4 --task-workers=6

Quando invoci il metodo concurrently, non dovresti fornire più di 1024 task a causa delle limitazioni imposte dal sistema di task di Swoole.

Ticks e Intervalli

Questa funzionalità richiede Swoole.

Quando usi Swoole, puoi registrare operazioni "tick" che verranno eseguite ogni numero di secondi specificato. Puoi registrare i callback "tick" tramite il metodo tick. Il primo argomento del metodo tick deve essere una stringa che rappresenta il nome del ticker. Il secondo argomento deve essere una callable che verrà invocata all’intervallo specificato.

In questo esempio, registriamo una closure che verrà invocata ogni 10 secondi. Di solito, il metodo tick dovrebbe essere chiamato all’interno del metodo boot di uno dei service provider della tua applicazione:

Octane::tick('simple-ticker', fn () => ray('Ticking...'))
        ->seconds(10);

Usando il metodo immediate, puoi indicare a Octane di invocare immediatamente il callback tick quando il server Octane si avvia e ogni N secondi successivamente:

Octane::tick('simple-ticker', fn () => ray('Ticking...'))
        ->seconds(10)
        ->immediate();

La Cache di Octane

Questa funzionalità richiede Swoole.

Quando usi Swoole, puoi sfruttare il driver cache di Octane, che offre velocità di lettura e scrittura fino a 2 milioni di operazioni al secondo. Perciò, questo driver cache è un’ottima scelta per applicazioni che necessitano di velocità estreme di lettura/scrittura dalla loro cache.

Questo driver cache utilizza le tabelle Swoole. Tutti i dati memorizzati nella cache sono disponibili per tutti i worker sul server. Tuttavia, i dati nella cache verranno cancellati quando il server viene riavviato:

Cache::store('octane')->put('framework', 'Laravel', 30);

Il numero massimo di voci consentite nella cache di Octane può essere definito nel file di configurazione octane della tua applicazione.

Intervalli di Cache

Oltre ai metodi tipici forniti dal sistema di cache di Laravel, il driver di cache Octane include cache basate su intervalli. Queste cache vengono automaticamente aggiornate all’intervallo specificato e dovrebbero essere registrate nel metodo boot di uno dei provider di servizio della tua applicazione. Ad esempio, la seguente cache sarà aggiornata ogni cinque secondi:

use Illuminate\Support\Str;

Cache::store('octane')->interval('random', function () {
    return Str::random(10);
}, seconds: 5);

Tabelle

Questa funzionalità richiede Swoole.

Quando usi Swoole, puoi definire e utilizzare le tue tabelle Swoole. Le tabelle Swoole offrono prestazioni elevate e i dati in queste tabelle possono essere accessibili da tutti i worker sul server. Tuttavia, i dati verranno persi quando il server viene riavviato.

Le tabelle devono essere definite nell’array di configurazione tables del file di configurazione octane della tua applicazione. È già configurata una tabella di esempio che permette un massimo di 1000 righe. La dimensione massima delle colonne di tipo string può essere impostata specificando la dimensione della colonna dopo il tipo, come mostrato di seguito:

'tables' => [
    'example:1000' => [
        'name' => 'string:1000',
        'votes' => 'int',
    ],
],

Per accedere a una tabella, puoi usare il metodo Octane::table:

use Laravel\Octane\Facades\Octane;

Octane::table('example')->set('uuid', [
    'name' => 'Nuno Maduro',
    'votes' => 1000,
]);

return Octane::table('example')->get('uuid');

I tipi di colonne supportati dalle tabelle Swoole sono: string, int e float.

Lascia un commento

Lascia un commento

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