Vai al contenuto

Job

I job sono l'unità di lavoro fondamentale in Queuety. Inviate un job con il nome di un handler e un payload; un worker lo preleva ed esegue in background.

Invio dei job

PHP
use Queuety\Queuety;

Queuety::dispatch( 'send_email', [ 'to' => 'user@example.com' ] );

dispatch() restituisce un builder PendingJob. Potete concatenare le opzioni prima che venga inviato automaticamente:

PHP
use Queuety\Enums\Priority;

Queuety::dispatch( 'send_email', [ 'to' => 'user@example.com' ] )
    ->on_queue( 'emails' )
    ->with_priority( Priority::High )
    ->delay( 300 )
    ->max_attempts( 5 );

Per ottenere subito l'ID del job, chiamate ->id():

PHP
$job_id = Queuety::dispatch( 'send_email', $payload )->id();

Scrittura degli handler

Un handler è qualsiasi classe che implementa l'interfaccia Queuety\Handler:

PHP
use Queuety\Handler;

class SendEmailHandler implements Handler {
    public function handle( array $payload ): void {
        wp_mail( $payload['to'], $payload['subject'], $payload['body'] );
    }

    public function config(): array {
        return [
            'queue'           => 'emails',
            'max_attempts'    => 5,
            'backoff'         => 'exponential',
        ];
    }
}

Il metodo handle() riceve il payload del job ed esegue il lavoro. Il metodo config() restituisce eventuali valori predefiniti:

Chiave Tipo Descrizione
queue string Nome predefinito della coda
max_attempts int Numero massimo di esecuzioni, compresa la prima, prima che il job venga messo in archivio
backoff string Strategia di attesa tra i nuovi tentativi: exponential, linear oppure fixed
rate_limit array Limite di frequenza come [max_executions, window_seconds]
concurrency_group string Gruppo condiviso di ammissione dei worker per handler costosi
concurrency_limit int Numero massimo di job elaborati contemporaneamente nel gruppo
cost_units int Costo relativo di esecuzione usato da code ponderate o budget dei provider, budget dei flussi di lavoro e criteri euristici di ammissione

Queste chiavi delle risorse fanno parte del più ampio modello Gestione delle risorse di Queuety, in cui i metadati dei job, i budget dei flussi di lavoro e l'ammissione dei worker operano insieme.

Registrate l'handler:

PHP
Queuety::register( 'send_email', SendEmailHandler::class );

Oppure usate gli attributi PHP per la registrazione automatica.

Priorità

I job hanno quattro livelli di priorità. I worker acquisiscono per primi i job con priorità più alta.

PHP
use Queuety\Enums\Priority;

Queuety::dispatch( 'handler', $payload )->with_priority( Priority::Urgent );
Livello Valore Descrizione
Priority::Low 0 Predefinita. Lavoro in background che può attendere.
Priority::Normal 1 Priorità standard.
Priority::High 2 Elaborata prima di Low e Normal.
Priority::Urgent 3 Elaborata per prima. Usatela con moderazione.

Ritardo

Ritardate un job in modo che diventi disponibile solo dopo il numero di secondi specificato:

PHP
Queuety::dispatch( 'send_reminder', $payload )->delay( 3600 ); // 1 hour

Il job viene inserito immediatamente, ma i worker non lo acquisiscono finché non è trascorso il ritardo.

Job univoci

Impedite invii duplicati per lo stesso handler e payload. Se esiste già un job in attesa o in elaborazione con lo stesso handler e payload, viene restituito l'ID del job esistente invece di crearne uno nuovo.

PHP
Queuety::dispatch( 'sync_inventory', [ 'sku' => 'ABC-123' ] )->unique();

Dipendenze tra job

Un job può dipendere da un altro job. Un worker non lo acquisirà finché la dipendenza non sarà stata completata.

PHP
$first_id = Queuety::dispatch( 'import_data', $payload )->id();

Queuety::dispatch( 'send_notification', [ 'import_id' => $first_id ] )
    ->after( $first_id );

Invio in batch

Inviate più job con un singolo INSERT su più righe per ottenere il massimo throughput:

PHP
$job_ids = Queuety::batch( [
    [ 'handler' => 'send_email', 'payload' => [ 'to' => 'a@example.com' ] ],
    [ 'handler' => 'send_email', 'payload' => [ 'to' => 'b@example.com' ] ],
    [ 'handler' => 'send_email', 'payload' => [ 'to' => 'c@example.com' ] ],
] );

Ogni elemento può includere chiavi facoltative: queue, priority, delay, max_attempts.

Nuovi tentativi e attesa progressiva

Quando un job genera un'eccezione, Queuety lo rimette in pending con un available_at ritardato, finché non è stato eseguito max_attempts volte in totale (si conta anche la prima esecuzione, quindi max_attempts( 3 ) consente fino a due nuovi tentativi). Il ritardo prima di ogni nuovo tentativo dipende dalla strategia di attesa progressiva, in cui attempt indica il numero dell'esecuzione appena fallita:

Strategia Formula del ritardo Esempio (dopo le esecuzioni 1, 2, 3)
exponential 2^attempt * 30 secondi, fino a un massimo di 3600 60s, 120s, 240s
linear attempt * 60 secondi 60s, 120s, 180s
fixed 60 secondi 60s, 60s, 60s

Impostate il valore predefinito globale in wp-config.php:

PHP
define( 'QUEUETY_RETRY_BACKOFF', 'linear' );

Oppure per handler in config():

PHP
public function config(): array {
    return [ 'backoff' => 'exponential', 'max_attempts' => 5 ];
}

Oppure per singolo invio:

PHP
Queuety::dispatch( 'handler', $payload )->max_attempts( 10 );

Processi sepolti

Dopo aver esaurito tutti i tentativi, un job viene contrassegnato come messo in archivio. I job archiviati restano nel database per essere ispezionati, ma non vengono elaborati. Potete riprovarli manualmente. Un nuovo tentativo manuale rende di nuovo il job pending senza azzerarne il conteggio dei tentativi; pertanto, un job archiviato e riprovato viene eseguito un'altra volta e rimesso in archivio se anche quell'esecuzione fallisce:

PHP
// Retry a specific buried job
Queuety::retry( $job_id );

// Retry all buried jobs
$count = Queuety::retry_buried();

Oppure tramite CLI:

Shell
wp queuety retry 42
wp queuety retry-buried

Timeout

Queuety attiva un pcntl_alarm() per la durata massima di esecuzione e dovrebbe far fallire il tentativo con un TimeoutException se il limite viene superato. Al momento il timeout non viene applicato (#10): il worker non abilita i segnali asincroni, quindi un handler di lunga durata viene eseguito fino al completamento, mentre un handler bloccato in sleep() viene interrotto quando scatta l'allarme e poi contrassegnato come completato. Configurate il timeout globale:

PHP
define( 'QUEUETY_MAX_EXECUTION_TIME', 300 ); // 5 minutes

I job che restano nello stato processing per più di QUEUETY_STALE_TIMEOUT secondi vengono recuperati automaticamente:

PHP
define( 'QUEUETY_STALE_TIMEOUT', 600 ); // 10 minutes

Ripristinate manualmente i job obsoleti:

Shell
wp queuety recover

Code

I job vengono inviati a code denominate. Ogni worker elabora una coda oppure un elenco ordinato di code (consultate Worker):

PHP
Queuety::dispatch( 'handler', $payload )->on_queue( 'emails' );
Shell
wp queuety work --queue=emails

La coda predefinita è default. Potete eseguire processi worker separati per ogni coda, così da isolare i carichi di lavoro.

Limitazione della frequenza

Limitate la frequenza con cui un handler può essere eseguito nell'arco di un intervallo di tempo:

PHP
Queuety::dispatch( 'call_api', $payload )
    ->rate_limit( 10, 60 ); // max 10 executions per 60 seconds

Per i dettagli, consultate Limitazione della frequenza.

Classi di job inviabili

Queuety v0.6.0 introduce una classe API per i job. Invece di separare il nome dell'handler, il payload e la classe dell'handler, definite un'unica classe che racchiude tutto: il payload (come proprietà pubbliche tipizzate), la logica di esecuzione e il middleware facoltativo.

L'interfaccia Contracts\Job

PHP
use Queuety\Contracts\Job;

interface Job {
    public function handle(): void;
}

Una classe Job contiene il proprio payload in proprietà pubbliche ed esegue la propria logica in handle(). Non è necessario un metodo config() separato né registrare un handler.

Il trait Dispatchable

Aggiungete il trait Dispatchable per ottenere un metodo statico dispatch() nella classe del job:

PHP
use Queuety\Contracts\Job;
use Queuety\Dispatchable;

class SendEmailJob implements Job {
    use Dispatchable;

    public function __construct(
        public readonly string $to,
        public readonly string $subject,
        public readonly string $body,
    ) {}

    public function handle(): void {
        wp_mail( $this->to, $this->subject, $this->body );
    }
}

Invitatelo passando gli argomenti del costruttore:

PHP
SendEmailJob::dispatch( 'user@example.com', 'Welcome!', 'Hello there.' )
    ->on_queue( 'emails' )
    ->with_priority( Priority::High );

Il trait Dispatchable chiama internamente Queuety::dispatch(), quindi tutte le opzioni PendingJob (queue, priority, delay, max_attempts, unique, after) funzionano esattamente allo stesso modo.

Come funziona la serializzazione

Quando inviate un'istanza di Job, Queuety estrae il nome completo della classe come handler e tutte le proprietà pubbliche come payload tramite il suo JobSerializer. Sul worker, il serializzatore ricostruisce l'istanza associando le chiavi del payload ai nomi dei parametri del costruttore. Gli enum con valore di supporto vengono serializzati nel valore sottostante e ripristinati automaticamente.

Proprietà del job

Le classi dei job possono dichiarare proprietà pubbliche per controllare il comportamento dei nuovi tentativi e dei timeout. Il worker le legge tramite reflection e le applica automaticamente.

PHP
use Queuety\Contracts\Job;
use Queuety\Dispatchable;

class ImportProductsJob implements Job {
    use Dispatchable;

    public int $tries = 5;
    public int $timeout = 120;
    public array $backoff = [ 10, 30, 60, 120 ];
    public string $concurrency_group = 'imports';
    public int $concurrency_limit = 2;
    public int $cost_units = 4;

    public function __construct(
        public readonly int $supplier_id,
    ) {}

    public function handle(): void {
        // Import products...
    }
}
Proprietà Tipo Descrizione
$tries int Numero massimo di tentativi prima che il job venga messo in archivio. Sostituisce il valore max_attempts definito al momento dell'invio.
$timeout int Durata massima di esecuzione in secondi. Sostituisce la costante globale QUEUETY_MAX_EXECUTION_TIME. Richiede pcntl e al momento non viene applicata (consultate Timeout).
$backoff array Array di valori di ritardo in secondi per ogni nuovo tentativo. Il valore dell'ultimo elemento viene riutilizzato per i tentativi successivi.
$concurrency_group string Gruppo condiviso di ammissione dei worker per i job costosi.
$concurrency_limit int Numero massimo di job processing simultanei nel gruppo. Ignorato senza $concurrency_group.
$cost_units int Costo relativo di esecuzione usato dai budget dei flussi di lavoro e dall'ammissione dei worker.

L'array $backoff definisce ritardi crescenti tra i nuovi tentativi. Per esempio, [10, 30, 60, 120] indica che il primo nuovo tentativo attende 10 secondi, il secondo 30 secondi, il terzo 60 secondi e gli eventuali ulteriori tentativi attendono 120 secondi.

Le proprietà relative alle risorse sono utili quando le classi dei job rappresentano profili di costo diversi. Un aggiornamento rapido della cache e una chiamata di grandi dimensioni a un provider possono essere entrambi «un job», ma non dovrebbero necessariamente avere lo stesso peso quando un budget del flusso di lavoro o una decisione di ammissione dei worker mirano a mantenere il processo entro limiti sicuri.

Per il modello completo, compresi i budget dei flussi di lavoro e l'ammissione lato worker, consultate Gestione delle risorse.

Hook di errore

Quando un job viene messo in archivio dopo aver esaurito tutti i tentativi, Queuety chiama un metodo failed() sull'istanza del job, se definito. Usatelo per la pulizia, gli avvisi o la registrazione nei log.

PHP
use Queuety\Contracts\Job;
use Queuety\Dispatchable;

class ProcessPaymentJob implements Job {
    use Dispatchable;

    public int $tries = 3;

    public function __construct(
        public readonly int $order_id,
    ) {}

    public function handle(): void {
        // Process the payment...
    }

    public function failed( \Throwable $exception ): void {
        // Notify the team that payment processing failed permanently.
        error_log( "Payment failed for order {$this->order_id}: {$exception->getMessage()}" );
    }
}

Il metodo failed() riceve l'eccezione che ha causato l'errore finale. L'istanza del job viene deserializzata con il payload originale, quindi tutte le proprietà pubbliche sono disponibili.

Invio condizionale

Il trait Dispatchable fornisce dispatch_if() e dispatch_unless() per l'invio condizionale. Entrambi accettano come primo argomento una condizione booleana, seguita dagli argomenti del costruttore del job.

PHP
// Only dispatch if the user has opted in to notifications.
SendEmailJob::dispatch_if( $user->wants_notifications, $user->email, 'Update', $body );

// Dispatch unless the feature flag is disabled.
SyncInventoryJob::dispatch_unless( $maintenance_mode, $sku );

dispatch_if() invia il job se la condizione è true e restituisce PendingJob. Se la condizione è false, restituisce null.

dispatch_unless() invia il job se la condizione è false e restituisce PendingJob. Se la condizione è true, restituisce null.

Entrambi i metodi supportano la concatenazione delle opzioni PendingJob al momento dell'invio:

PHP
SendEmailJob::dispatch_if( $should_send, $to, $subject, $body )
    ?->on_queue( 'emails' )
    ?->with_priority( Priority::High );

Invio sincrono

Usate Queuety::dispatch_sync() per eseguire subito un job nel processo corrente senza aggiungerlo alla coda. È utile per i test e per i job che devono essere eseguiti inline.

PHP
use Queuety\Queuety;

// With a Job instance
Queuety::dispatch_sync( new SendEmailJob( to: 'user@example.com', subject: 'Hello', body: 'Hi!' ) );

// With a handler name and payload
Queuety::dispatch_sync( 'send_email', [ 'to' => 'user@example.com' ] );

Il metodo handle() del job viene chiamato direttamente. Non viene creata alcuna riga nel database, non interviene alcun worker e non si applica il middleware.

Concatenamento dei job

Il concatenamento dei job esegue una sequenza di job uno dopo l'altro. Ogni job della catena dipende dal completamento corretto del precedente. Se un job non riesce, i job successivi della catena restano in attesa.

Uso di Queuety::chain()

PHP
use Queuety\Queuety;

$first_job_id = Queuety::chain( [
    new FetchDataJob( $url ),
    new ProcessDataJob(),
    new NotifyCompleteJob(),
] )
    ->on_queue( 'pipeline' )
    ->catch( ChainFailedHandler::class )
    ->dispatch();

Queuety::chain() restituisce un ChainBuilder con metodi fluenti per impostare la coda e un handler degli errori. Il metodo dispatch() crea tutti i job con relazioni depends_on sequenziali e restituisce l'ID del primo job.

Uso di with_chain()

Il trait Dispatchable fornisce un metodo with_chain(). Al momento equivale a Queuety::chain( $jobs ): la classe su cui viene chiamato non viene aggiunta alla catena (#13), quindi includete anche il primo job nell'array:

PHP
FetchDataJob::with_chain( [
    new FetchDataJob(),
    new ProcessDataJob(),
    new NotifyCompleteJob(),
] )
    ->on_queue( 'pipeline' )
    ->dispatch();

Gestione degli errori della catena

Registrate una classe handler degli errori usando catch(). La classe handler deve avere un metodo handle(). Viene chiamato quando un qualsiasi job della catena viene definitivamente messo in archivio. Il worker supporta handle(), handle( Job $job ) e handle( Job $job, \Throwable $exception ).

PHP
class ChainFailedHandler {
    public function handle(): void {
        // Alert, clean up, etc.
    }
}

Aggiunta di middleware

Le classi dei job possono definire un metodo middleware() per restituire middleware che avvolge l'esecuzione:

PHP
use Queuety\Contracts\Job;
use Queuety\Dispatchable;
use Queuety\Middleware\RateLimited;
use Queuety\Middleware\Timeout;

class CallOpenAIJob implements Job {
    use Dispatchable;

    public function __construct(
        public readonly string $prompt,
    ) {}

    public function handle(): void {
        // Call the API...
    }

    public function middleware(): array {
        return [
            new RateLimited( max: 60, window: 60 ),
            new Timeout( seconds: 120 ),
        ];
    }
}

Per i dettagli sul middleware integrato e su come scriverne di personalizzato, consultate Middleware.

Confronto con i job basati su handler

Basato su handler (v0.1+) Basato su classi (v0.6+)
Dispatch Queuety::dispatch( 'send_email', $payload ) SendEmailJob::dispatch( $to, $subject )
Handler Classe separata che implementa Handler La stessa classe implementa Contracts\Job
Payload array $payload non tipizzato Proprietà pubbliche tipizzate
Registrazione Queuety::register() o #[QueuetyHandler] Non richiesta (usa FQCN)
Middleware Non disponibile Metodo middleware() facoltativo
Configurazione Metodo config() Proprietà del job + catena PendingJob

Entrambi gli approcci sono pienamente supportati. I job esistenti basati su handler continuano a funzionare senza modifiche. Potete combinare entrambi gli stili nello stesso progetto.

Ciclo di vita dei job

Codice
dispatch() -> pending -> processing -> completed
                  |           |
                  |           +-> exception -> pending (retry after backoff)
                  |                       |
                  |                       +-> buried (max attempts reached)
                  |
                  +-> delayed (available_at in the future)
Stato Descrizione
pending In attesa di essere preso in carico da un worker
processing In fase di esecuzione
completed Completato correttamente
failed Riservato; al momento il worker non lo imposta. Un tentativo non riuscito che verrà riprovato torna a pending
buried Tutti i tentativi di ripetizione esauriti