- Laravel offre un'API di coda unificata con più driver (database, Redis, SQS...) e separazione tra connessioni e code logiche.
- I lavori consentono di spostare in background le attività più impegnative, con supporto per modelli di serializzazione, middleware, lavori una tantum, crittografia, batch e stringhe.
- I lavoratori vengono gestiti tramite queue:work, Supervisor e comandi quali queue:pause/queue:continue, controllando i tentativi, i timeout e le priorità.
- Esiste un ecosistema completo per errori, monitoraggio e test (failed_jobs, queue:retry, queue:monitor, Queue::fake) che rende le code affidabili in produzione.
Lavorare con le code in Laravel è quasi obbligatorio in qualsiasi progetto che si rispetti: inviare email, elaborare file CSV, generare PDF, ridimensionare immagini, chiamare API esterne... Se tutto ciò viene fatto nella richiesta HTTP principale, il sito web rallenta, compaiono errori 500 e l'utente si ritrova a fissare disperatamente una schermata vuota.
La buona notizia è che Laravel offre un'infrastruttura di accodamento potente e flessibile che consente di delegare le operazioni più complesse ai processi in background, gestirli per priorità, raggrupparli in batch, monitorarli, riprovarli in caso di errore e persino sospenderli temporaneamente per manutenzione. Analizziamo nel dettaglio come sfruttarla al meglio, compresi i metodi per sospenderla, riprenderla e controllarne il comportamento in produzione.
Concetti di base: connessioni, code e driver
In Laravel, prima di mettere in pausa qualsiasi cosa, è necessario comprendere la differenza tra connessioni di coda e code stesse. In config/queue.php hai una matrice connections dove definisci ogni backend: database, redis, beanstalkd, sqs, sync, null, failover, rabbitmq (se lo aggiungi), ecc.
Ogni connessione può avere più code logiche (nomi come default, emails, high, low, importsL'attributo queue Ogni connessione indica qual è la coda predefinita in cui verranno indirizzati i processi se non ne viene specificata un'altra. onQueue()In questo modo puoi fare cose come:
- Una coda veloce per e-mail o transazioni brevi.
- Un'altra bassa priorità per attività lunghe come l'elaborazione di video o report di grandi dimensioni.
- Code segmentate per tipo di attività:
imports,notificaciones,reportes, ecc.
Quando si avvia il lavoratore con php artisan queue:workPer impostazione predefinita, ascolta la coda specificata come predefinito sulla connessione selezionata. Se si desidera stabilire una priorità, è possibile passare più code separate da virgole:
php artisan queue:work redis --queue=high,default,low
In questo modo, il worker elabora prima le priorità più alte , poi quelle predefinite e solo infine quelle più basse , il che è fondamentale quando il sistema è sovraccarico e non si vuole che un report di 30 minuti blocchi le email ad alta priorità.
Driver di coda e prerequisiti
Laravel offre un'API unificata per diversi driver di coda , ma ognuno ha le proprie caratteristiche e dipendenze specifiche. Scegliere il driver giusto è fondamentale prima di parlare di come mettere in pausa i worker.
Banca dati dei conducenti
L'autista banca dati Memorizza i lavori in una tabella SQL (di solito jobsÈ facile da configurare ed è perfetto per piccoli progetti o ambienti di sviluppo in cui non si vuole perdere tempo con Redis o SQS.
Passi di base:
- Generare la migrazione:
php artisan queue:table(o nelle versioni più recenti, si presenta comecreate_jobs_tablepredefinito). - Eseguire le migrazioni:
php artisan migrate. - Configurare QUEUE_CONNECTION=database in
.enve confermare inconfig/queue.phpche'default' => env('QUEUE_CONNECTION','database').
È comodo e sufficientemente robusto, ma con molte code e un traffico elevato può diventare un collo di bottiglia , poiché tutto passa attraverso lo stesso database relazionale.
Autista Redis
Il driver Redis è lo standard di riferimento negli ambienti di produzione. È veloce, supporta funzionalità avanzate (blocco, limitazione della velocità, blocchi atomici, Horizon, ecc.) e si adatta molto bene alle diverse esigenze.
Punti importanti:
- È necessario configurare la connessione Redis in
config/database.php. - Le opzioni
serializerycompressionda Redis Non sono compatibili con il gestore della coda. - Se usi Redis ClusterI nomi delle code devono avere un hashtag, ad esempio
{default}in modo che tutte le chiavi in quella coda cadano nello stesso slot. - l'opzione
block_forPermette al lavoratore di bloccarsi mentre attende i lavori per alcuni secondi, invece di effettuare continuamente il polling: risparmia CPU e riduce le query al server Redis.
Ad esempio:
'redis' => ,
Se metti blocco_per = 0Il lavoratore rimane bloccato a tempo indeterminato finché non diventa disponibile un lavoro, cosa da tenere a mente se si desidera che risponda rapidamente a segnali come SIGTERM o mettere in pausa i comandi.
Altri driver: SQS, Beanstalkd, null, failover…
Oltre ai database e a Redis, Laravel supporta altri sistemi :
- Amazon SQSCode gestite su AWS, con supporto per FIFOGruppi di messaggi, deduplicazione e impostazioni di visibilità avanzate. Richiede l'SDK.
aws/aws-sdk-php. - BeanstalkdMolto veloce e facile, utilizzando il pacchetto
pda/pheanstalk. - sync: esegue i compiti inmediatamente nello stesso processo. Utile nello sviluppo o nei test, ma in produzione è praticamente come non usare affatto le code.
- nullo: scarta i lavori non appena vengono "spediti". Utilizzato per disattivare completamente il sistema di accodamento.
- failoverCiò consente di configurare più connessioni e, se quella primaria fallisce, Laravel tenta di inviare il processo a quella successiva nell'elenco. Perfetto per ambienti in cui alta disponibilità È fondamentale.
A livello di sospensione e ripresa dei worker, la logica è praticamente la stessa indipendentemente dal driver, perché viene gestita dai comandi Artisan e dalla classe Worker , non dal backend della coda.
Creazione di posti di lavoro e serializzazione del modello
Un Lavoro In Laravel, è semplicemente una classe che incapsula un'attività da eseguire in una coda. In genere viene generata con Artisan e salvata in app/Jobs.
Per crearne uno:
php artisan make:job ProcessPodcast
La classe generata attrezzi ShouldQueue e di solito usa tratti Dispatchable, InteractsWithQueue, Queueable y SerializesModelsIl cuore è nel metodo handle()che è ciò che fa il lavoratore quando arriva il lavoro.
Una funzionalità particolarmente potente è la serializzazione automatica dei modelli Eloquent . Se si accetta un modello nel costruttore del Job, Laravel non aggiunge l'intero oggetto alla coda, ma solo il suo identificatore. Quando il job viene elaborato, ricostruisce il modello dal database con le relative relazioni caricate.
Esempio tipico (in pseudocodice, per evitare di ripeterlo alla lettera):
- Il costruttore riceve Podcast $podcast.
- Il tratto
SerializesModelsÈ responsabile del salvataggio dell'ID. - En
handle(AudioProcessor $processor)Tu fai il lavoro più duro (ad esempio, elaborare l'audio).
Se non si desidera che le relazioni in un modello siano serializzate (in modo che il payload non sia enorme), è possibile utilizzare $model->withoutRelations() o in PHP 8 l'attributo # sulla proprietà promossa dal costruttore.
Iniezione di dipendenza nel metodo handle
Un altro vantaggio è che puoi iniettare i servizi direttamente nel metodo handle (come se fosse un controller), perché Laravel utilizza anche il contenitore di servizi in Jobs. Ad esempio, è possibile richiedere un AudioProcessorClient HTTP, repository, ecc. e il framework si occupa di risolverli.
Se hai bisogno di un controllo ultra-fine di quello che viene chiamato handle, Puoi usare Container::bindMethod() in un fornitore di servizi e definire autonomamente la logica di iniezione, ma nella maggior parte dei casi non è necessario complicare così tanto le cose.
Middleware di lavoro
Proprio come i percorsi hanno un middleware, anche i lavori possono avere middleware specifico che vengono eseguiti prima e dopo il lavoro. Ciò riduce notevolmente il codice ripetitivo nei metodi handle() ed è particolarmente utile per:
- Limitazione della velocità con Redis.
- Evitare si sovrappone di opere che toccano la stessa risorsa.
- Eccezioni regolari per API instabili.
- Saltare i lavori in determinate condizioni (
Skipmiddleware).
Il middleware è solitamente posizionato in app/Jobs/Middleware e vengono restituiti dal metodo middleware() Da Giobbe. Esempio concettuale:
public function middleware(): array { return ;
Tipi speciali di lavori: unici, crittografati e bloccati
Laravel ti consente di contrassegnare determinati lavori come unico per garantire che due istanze identiche non vengano mai messe in coda contemporaneamente. Ciò si ottiene implementando l'interfaccia ShouldBeUnique o ShouldBeUniqueUntilProcessinge supporta la definizione di una chiave uniqueId() e un tempo uniqueFor.
Questa è la soluzione ideale per cose come:
- Ricalcola l'indice di ricerca di un prodotto: solo una volta per prodotto finché non sarà finita.
- Sincronizzazioni con servizi esterni che non consentono richieste simultanee per la stessa risorsa.
Sotto, Laravel usa un serratura atomica di cache (Redis, memcached, database, ecc.). Puoi anche scegliere il driver della cache per quel blocco con uniqueVia().
Inoltre, c'è l'interfaccia ShouldBeEncrypted, con cui indichi che il lavoro completo è Inserisci il tuo codice prima di entrare nella codaMolto utile quando si passano dati sensibili e non si vuole che vengano trasmessi in formato testo normale tramite Redis, SQS o il database.
Inviare, ritardare ed eseguire lavori in diverse modalità
Una volta definito il tuo lavoro, lo avvii con Job::dispatch() da controller, ascoltatori di eventi, comandi Artisan o persino chiusure. I parametri che passi a dispatch Si rivolgono direttamente al costruttore di Giobbe.
Varianti interessanti:
dispatchIf()ydispatchUnless()per condizionare la spedizione in un'unica linea.delay()in modo che il lavoro non possa essere elaborato fino a una data/ora specifica.dispatchAfterResponse()oafterResponse()endispatch()Chiusure, che rimandano l'esecuzione finché non viene inviata la risposta HTTP, ma all'interno dello stesso processo PHP.dispatchSync()per eseguire il Job in modo sincrono (senza passare dalla coda) ma riutilizzando la stessa classe.- Collegamenti speciali come
deferredobackgroundda eseguire dopo la risposta, nello stesso processo o in un nuovo processo PHP.
Infine, è possibile specificare la connessione e la coda con metodi concatenati onConnection() y onQueue(), sia durante la spedizione che all'interno del Job Builder.
Controllo preciso: nuovi tentativi, backoff, timeout e gestione degli errori
Una parte importante del lavoro con le code in Laravel consiste nel decidere quante volte riprovare un'operazione , quanto tempo aspettare tra un tentativo e l'altro e quando considerarla fallita.
Opzioni disponibili:
--triesenqueue:workper impostare il numero massimo di tentativi per tutti i lavori di quel worker (a meno che il lavoro non ne definisca uno proprio)$triesotries()).$trieso un metodotries()nella classe Job per il controllo per ogni singolo lavoro.retryUntil()per dire: "riprova quanto vuoi fino a questa volta, dopodiché è un fallimento". Se coincide contries, comandoretryUntil.$maxExceptionsper interrompere quando il problema è dovuto a ripetute eccezioni non gestite.$timeouto--timeoutper indicare quanti secondi può durare un Job prima che il worker interrompa il processo figlio.$failOnTimeoutper contrassegnare il lavoro come fallito in caso di timeout.$backoffil metodobackoff()per definire quanto tempo attendere tra i nuovi tentativi, anche con valori esponenziali.
Se un processo genera un'eccezione non rilevata, Laravel... rilascia di nuovo nella coda fino all'esaurimento dei tentativi configurati. È possibile controllarlo manualmente con release($segundos) o fail($exception) quando si desidera contrassegnare deliberatamente un lavoro come fallito.
Lavori non riusciti, tabella failed_jobs e potatura
Quando un Giobbe esaurisce i suoi tentativi, viene considerato lavoro fallito e Laravel lo inserisce nella tabella failed_jobs (o in DynamoDB se lo si configura in questo modo). Questa tabella memorizza UUID, connessione, coda, payload del job ed eccezione generata.
Comandi utili:
php artisan queue:failedper elencare quelli falliti.php artisan queue:retry <id|all>per riprovarli.php artisan queue:forget <id>per eliminarne uno.php artisan queue:flushper eliminare tutto (con opzione per le ore nelle versioni più recenti).php artisan queue:prune-failed --hours=48per eliminare automaticamente i vecchi record.
In Jobs puoi definire un metodo failed(Throwable $exception) Per una pulizia extra: notificare l'utente, annullare azioni parziali, scrivere log specializzati, ecc. Notare che viene creata una nuova istanza del Job per chiamare failed()Pertanto, non fare affidamento sulle modifiche di proprietà apportate in handle().
Batch di lavori: batch di lavori e loro combinazione con stringhe
Quando è necessario avviare centinaia o migliaia di processi correlati (ad esempio, importare un file CSV di grandi dimensioni a blocchi, elaborare file in batch, pulire dati, ecc.), è consigliabile utilizzare la funzionalità batch di Laravel .
Flusso di base:
- creare la tabella
job_batchesconphp artisan queue:batches-table && php artisan migrate. - Contrassegna i lavori che faranno parte dei batch con la caratteristica
Batchable. - Applica
Bus::batch()per inviare un set di lavori, con callbackthen,catchyfinally. - Accedi al batch da ogni lavoro con
$this->batch()e controlla se è stato annullato. - Possibilità di aggiungere più lavori a un batch in corso con
$this->batch()->add().
I batch si integrano perfettamente con le catene : è possibile avere catene all'interno di un batch o batch come elementi di una catena più grande. Questo è particolarmente utile quando si desidera che più catene vengano eseguite "in parallelo" e attivino un'azione al termine di ciascuna.
Esecuzione, priorità e distribuzione dei lavoratori
Tutta la magia delle code avviene perché hai uno o più lavoratori eseguendo il comando queue:work (o queue:listen(sebbene quest'ultimo sia meno efficiente). Questi processi sono demoni di lunga durata che ascoltano le rispettive code.
Opzioni chiave di queue:work:
- Collegamento:
php artisan queue:work redis,queue:work database, ecc. - Unità:
--queue=high,default,lowper stabilire le priorità. - -undici: elabora un singolo lavoro ed esce.
- –max-jobs: elabora X lavori e termina (per riavviare periodicamente i worker e liberare memoria).
- –tempo massimo: elabora per N secondi e poi esce.
- -sonno: secondi di attesa quando non ci sono lavori disponibili.
- –fermarsi quando è vuoto: svuota la coda e la chiude in modo pulito (ideale negli ambienti Docker o per lavori una tantum).
- -tempo scaduto: secondi di timeout per processo, come abbiamo detto prima.
- -vigore: per elaborare i lavori anche se l'app è in modalità di manutenzione.
Poiché questi processi sono di lunga durata, in produzione si utilizza quasi sempre un monitor di processi come Supervisor (su Linux) per avviare i worker X, riavviarli in caso di arresto anomalo e gestire i log. Un tipico blocco di configurazione di Supervisor per Laravel definisce:
- command dell'
php artisan queue:work ...calcestruzzo. - numproc per indicare quanti worker avviare in parallelo.
- Percorsi di log, utente, riavvii automatici, ecc.
Mettere in pausa e riprendere le code in Laravel
Passiamo ora a un aspetto spesso importante nella manutenzione: come sospendere i processi in coda senza arrestarli bruscamente e senza lasciare attività incompiute. Laravel offre diversi meccanismi, alcuni più recenti, altri più classici, per controllare questo aspetto.
Pausa: segnali di coda:pausa e interruzione
Nelle versioni più recenti, Laravel ha aggiunto comandi specifici per mettere in pausa e riprendere l'esecuzione dei worker:
php artisan queue:pause: indica al sistema di smettere di accettare nuovi lavori, ma di lasciare che quelli già in corso vengano completati.php artisan queue:continue: riprende l'elaborazione dei nuovi lavori dal punto in cui era stata interrotta.
Questi comandi funzionano inviando segnali ai worker tramite il meccanismo di caching utilizzato internamente da Laravel. Ecco perché è importante avere un driver di cache configurato correttamente (memcached, redis, database, file, ecc.) anche se la coda di cache effettiva è diversa.
Importante: la sospensione di una coda o di un worker non annulla né elimina i processi già in coda ; impedisce semplicemente che altri processi vengano rimossi dalla coda per tutta la durata della sospensione.
Sondaggio delle interruzioni e impatto sulle prestazioni
Ogni volta che un worker termina un'attività, Laravel controlla la cache per i segnali di riavvio, pausa o continuazione . Questo ha un costo trascurabile, ma esiste. Se sei estremamente concentrato sulle prestazioni e sai che non avrai bisogno di mettere in pausa o riavviare i worker tramite comandi, puoi disabilitare questo polling chiamando:
Queue::withoutInterruptionPolling();
Oppure addirittura regolare le proprietà statiche di Illuminate\Queue\Worker per disabilitare tocchi specifici ($riavviabile, $pausableTuttavia, se fai questo e poi provi a usare queue:pause o queue:restarti lavoratori Non lo scoprirannoSi tratta di una decisione progettuale che dovrebbe essere attentamente ponderata.
Metti in pausa manualmente con Supervisor o Docker
Oltre ai comandi di Laravel , negli ambienti di produzione i processi vengono spesso "sospesi" indirettamente:
- Arresto del programma in Supervisor (
supervisorctl stop laravel-worker:*), che uccide o ferma i lavoratori. - utilizzando –fermarsi quando è vuoto nelle distribuzioni per consentire ai lavoratori di terminare ciò che è in coda e poi spegnersi.
- In Docker, lasciare che il contenitore elabori con
--stop-when-emptye poi si spegne.
La differenza con queue:pause Arrestando completamente i processi, si interromperà l'elaborazione dei lavori finché i worker non verranno riavviati. pausa interna È possibile mantenere i processi in memoria, in attesa del comando continueQuesta funzionalità è utile per implementazioni rapide di rollback o brevi periodi di manutenzione.
Transazioni del database e after_commit
Un errore classico: inviare processi all'interno di transazioni di database . Se non si presta attenzione, il processo di lavoro potrebbe eseguire il processo prima che venga effettuato il commit, e il modello o i dati attesi potrebbero non essere ancora presenti nel database.
Per evitare questa danza, hai due possibilità:
- attivare
'after_commit' => truenella configurazione della connessione alla coda. Pertanto, qualsiasi Job distribuito all'interno di una transazione viene posticipato internamente fino alla sua conferma. - Utilizzare metodi per lavoro
afterCommit()obeforeCommit()durante la distribuzione, per forzare il comportamento solo in Job specifici.
Se la transazione viene annullata, i processi inviati durante essa vengono scartati senza mai entrare nella coda, che è esattamente ciò che solitamente si desidera per mantenere la coerenza.
Monitoraggio, pulizia delle code e test
Una volta che le code sono operative e funzionanti a pieno regime, non basta semplicemente inserire i Job e dimenticarsene; è necessario monitorare e testare il comportamento di tutto il sistema.
Strumenti e comandi utili:
queue:clearper svuotare una coda specifica (SQS, Redis, database).queue:monitorper monitorare il numero di lavori in determinate code e attivare l'eventoQueueBusyse superano una soglia.- Eventi di lavoro come
Queue::before,Queue::after,Queue::loopingyQueue::failingper agganciare logica personalizzata (log, metriche, rollback, ecc.). - Falsi nei test utilizzando
Queue::fake()yBus::fake()per verificare che i lavori, le stringhe o i batch siano stati distribuiti senza effettivamente eseguirli.
Nei test è possibile dichiarare cose come "questo lavoro è stato messo in coda X volte" , "una catena con questi lavori è stata inviata in questo ordine" o "nessun lavoro specifico è stato messo in coda", il che offre un elevato livello di sicurezza nei flussi complessi.
Con questo intero ecosistema, driver come Redis o SQS, lavori ben progettati, middleware per la messa a punto, batch, catene, configurazione dei nuovi tentativi, lavoratori gestiti dal supervisore e comandi come queue:pause y queue:continue— Laravel ti consente di padroneggiare l'elaborazione in background fino al millimetro, dal tipico invio di e-mail alle architetture di code distribuite con failover, garantendo che la tua applicazione continui a rispondere rapidamente a livello HTTP mentre vengono eseguiti lavori pesanti al di sotto, senza bloccare l'utente.