En HTTP-request bør aldrig vente på at din server sender 2.000 e-mails, henter data fra tre tredjeparts-API'er og genererer en PDF-rapport. Alligevel sker det i rigtig mange Laravel-applikationer - og det er præcis det problem, Laravel Queues løser.
Hvad sker der, når du behandler tungt arbejde synkront
Synkron behandling betyder at din applikation udfører en opgave fra start til slut, inden den returnerer et svar til brugeren. For simple operationer er det uproblematisk. For tunge operationer er det en katastrofe i tre akter.
Timeout. PHP og webservere har eksekveringstidsgrænser. Overskrides de, får brugeren en tom side eller en 504 Gateway Timeout. Opgaven er muligvis halvt udført - eller slet ikke udført.
Brugeroplevelsen forringes drastisk. En bruger der klikker "Send rapport" og derefter stirrer på en spinner i 45 sekunder, er ikke en tilfreds bruger. Selv hvis operationen lykkes, er oplevelsen ødelagt.
Serverbelastning og skalerbarhed. Tunge synkrone operationer blokerer PHP-processer. Hvis ti brugere starter tunge operationer samtidig, er ti PHP-processer optaget - og alle andre requests sættes i kø på webserveren i stedet for i en kontrolleret jobkø.
Løsningen er at flytte det tunge arbejde ud af request-cyklussen og ind i en asynkron jobkø.
Konceptet bag Laravel Queues
Laravel Queues bygger på et simpelt princip: i stedet for at udføre en opgave med det samme, gemmer du den i en kø og returnerer straks et svar til brugeren. En separat proces - en queue worker - henter opgaver fra køen og udfører dem i baggrunden, uafhængigt af HTTP-requests.
Arkitekturen består af tre elementer:
Job-klassen indkapsler den logik der skal udføres - f.eks. afsendelse af en e-mail eller kald til et eksternt API. Den er selvstændig og bærer alle de data, den har brug for.
Queue-driveren er det lager, hvor jobs gemmes, mens de venter på at blive behandlet. Laravel understøtter database, Redis, Amazon SQS, Beanstalkd og mere.
Queue workeren er en langtidskørende PHP-proces der lytter på køen og udfører jobs, efterhånden som de ankommer.
Denne adskillelse er det centrale arkitektoniske princip. Din webapplikation og dine baggrundsjobs skalerer uafhængigt af hinanden, og en fejl i et job påvirker ikke brugerens oplevelse direkte.
Opsætning: Database-driver vs. Redis
Queue-driveren konfigureres i config/queue.php og vælges via QUEUE_CONNECTION i din .env-fil.
Database-driveren gemmer jobs i en tabel i din eksisterende database. Den er nem at komme i gang med og kræver ingen ekstra infrastruktur. I Laravel 13 er jobs-tabellen som standard allerede inkluderet i default-migrationen 0001_01_01_000002_create_jobs_table.php, så ofte er php artisan migrate nok. Mangler migrationen, genererer du den med:
php artisan queue:table
php artisan migrateSæt herefter driveren i .env:
QUEUE_CONNECTION=databaseDatabase-driveren er velegnet til mindre applikationer og udviklingsmiljøer. Ulempen er at databasen hurtigt bliver en flaskehals ved høj jobvolumen, fordi workers konstant poller tabellen.
Redis-driveren er det rette valg til produktion med nogen som helst volumen. Redis er en in-memory datastruktur der er bygget til præcis denne slags opgaver - hurtig læsning, hurtig skrivning, lav latency. Siden Laravel 10 er den officielt anbefalede klient phpredis-extensionen frem for Predis, da den er hurtigere - men Predis fungerer fint, hvis du ikke kan installere PHP-extensions. Installer først pakken:
composer require predis/predisOg sæt driveren:
QUEUE_CONNECTION=redisRedis er også en forudsætning for Laravel Horizon, som beskrives senere. Tommelfingerreglen er enkel: brug database under udvikling og til prototyper, brug Redis i produktion.
Oprettelse af et job
Artisan genererer job-klassen for dig:
php artisan make:job SendWelcomeEmailDet opretter filen app/Jobs/SendWelcomeEmail.php. En typisk job-klasse ser sådan ud:
<?php
namespace App\Jobs;
use App\Models\User;
use App\Mail\WelcomeMail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Mail;
class SendWelcomeEmail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(
protected User $user
) {}
public function handle(): void
{
Mail::to($this->user->email)->send(new WelcomeMail($this->user));
}
}Interfacet ShouldQueue fortæller Laravel at dette job skal sendes til køen frem for at blive udført synkront. SerializesModels sørger for at Eloquent-modeller serialiseres korrekt - kun model-ID'et gemmes i køen, og modellen hentes på ny, når jobbet udføres.
Du dispatcher jobbet fra en controller eller et andet sted i applikationen:
// Dispatch til køen med det samme
SendWelcomeEmail::dispatch($user);
// Dispatch med forsinkelse på 5 minutter
SendWelcomeEmail::dispatch($user)->delay(now()->addMinutes(5));
// Dispatch til en specifik kø
SendWelcomeEmail::dispatch($user)->onQueue('emails');Fejlhåndtering: Retries, MaxExceptions og failed_jobs
Jobs fejler. API'er er utilgængelige, databaseforbindelser droppes, og tredjeparts-services returnerer uventede svar. Laravel har en gennemtænkt mekanisme til at håndtere det.
Du definerer antal forsøg og timeout direkte på job-klassen:
class SendWelcomeEmail implements ShouldQueue
{
// Maksimalt antal forsøg
public int $tries = 3;
// Sekunder før jobbet anses for at have fejlet pga. timeout
public int $timeout = 60;
// Maksimalt antal exceptions før jobbet markeres som fejlet
public int $maxExceptions = 2;
// Vent 30 sekunder mellem hvert forsøg
public function backoff(): int
{
return 30;
}
}Når et job har opbrugt alle forsøg, flyttes det til tabellen failed_jobs. Opret den med:
I Laravel 13 oprettes failed_jobs-tabellen automatisk af default-migrationen sammen med jobs-tabellen, så php artisan migrate er normalt nok. Mangler den, genererer du den med:
php artisan queue:failed-table
php artisan migrate
Du kan herefter liste fejlede jobs, forsøge dem igen eller slette dem:
# List alle fejlede jobs
php artisan queue:failed
# Forsøg et specifikt job igen via dets ID
php artisan queue:retry 5
# Forsøg alle fejlede jobs igen
php artisan queue:retry all
# Slet et fejlet job
php artisan queue:forget 5Du kan også definere en failed-metode på jobbet der kaldes, når alle forsøg er opbrugt - f.eks. til at notificere et team eller logge fejlen struktureret:
public function failed(\Throwable $exception): void
{
// Send notifikation, log til fejlsporingssystem osv.
logger()->error('SendWelcomeEmail fejlede', [
'user_id' => $this->user->id,
'error' => $exception->getMessage(),
]);
}Queue workers i produktion
Under udvikling starter du en worker med:
php artisan queue:workDenne kommando lytter kontinuerligt på køen og udfører jobs, efterhånden som de ankommer. I produktion skal workeren holdes kørende selv efter nedbrud - og det er her Supervisor kommer ind.
Supervisor er en procesovervågningsdæmon til Linux der automatisk genstarter processer, hvis de stopper. En typisk Supervisor-konfiguration til Laravel ser sådan ud:
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/html/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=4
redirect_stderr=true
stdout_logfile=/var/www/html/storage/logs/worker.log
stopwaitsecs=3600numprocs=4 starter fire parallelle worker-processer, hvilket firedobler throughput. --max-time=3600 genstarter workeren efter én time for at undgå hukommelseslækager i langtidskørende processer. Husk at genindlæse Supervisor efter konfigurationsændringer:
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start laravel-worker:*Når du deployer ny kode, skal workers genstartes, så de indlæser den opdaterede applikationskode. Det gøres med:
php artisan queue:restartLaravel Horizon: Overblik over dine queues
Laravel Horizon er et dashboard til overvågning og konfiguration af Redis-baserede queues. Det giver realtidsindsigt i jobgennemstrømning, fejlrate, ventetid og worker-status - alt det, du ellers ville skulle samle fra logfiler manuelt.
Installer Horizon via Composer:
composer require laravel/horizon
php artisan horizon:install
php artisan migrateHorizon konfigureres i config/horizon.php, hvor du definerer dine environments og worker-pools:
'environments' => [
'production' => [
'supervisor-1' => [
'maxProcesses' => 10,
'balanceMaxShift' => 1,
'balanceCooldown' => 3,
],
],
'local' => [
'supervisor-1' => [
'maxProcesses' => 3,
],
],
],Start Horizon i stedet for den almindelige queue worker:
php artisan horizonDashboardet er tilgængeligt på /horizon i din applikation. I produktion bør du beskytte det med en gate i din AppServiceProvider, så kun autoriserede brugere har adgang. Horizon er ikke blot et nice-to-have - det er et egentligt driftsværktøj der giver dig synlighed i, hvad der faktisk sker i din applikations baggrundslag.
Tre use cases der altid bør bruge queues
Queues er relevante i mange situationer, men tre kategorier skiller sig ud som næsten universelle kandidater.
E-mail-afsendelse. SMTP-forbindelser kan tage fra 200 ms til flere sekunder. Transactional e-mails som velkomstmails, ordrebekræftelser og password-reset-links behøver ikke at blokere brugerens request. Dispatch jobbet, returner et svar med det samme, og lad workeren sende mailen. Brugeren oplever øjeblikkelig respons, og du undgår at en langsom mailserver tager din applikation med i faldet.
API-kald til tredjepart. Webhooks, betalingsgateways, CRM-systemer og lagerstyringsplatforme er alle eksterne afhængigheder med deres egne svartider og nedetider. Et API-kald der normalt tager 300 ms kan pludselig tage 8 sekunder - eller fejle helt. Når du isolerer disse kald i jobs med retry-logik, bliver din applikation resilient over for ustabile tredjeparter. Workeren prøver igen, din bruger mærker ingenting.
Rapport-generering. PDF-rapporter, dataeksporter til CSV og komplekse aggregeringer over store datasæt er klassiske eksempler på operationer der aldrig hører hjemme i en HTTP-request. Dispatch et rapport-job, gem resultatet til disk eller cloud storage, og send brugeren en notifikation - eller vis rapporten, når den er klar. Denne mønsterstruktur er identisk uanset om rapporten tager 5 sekunder eller 5 minutter at generere.
Laravel Queues er ikke avanceret funktionalitet forbeholdt store applikationer - det er en fundamental del af at bygge applikationer der opfører sig korrekt under belastning. Når du forstår skellet mellem synkron og asynkron behandling, og ved hvornår det ene er mere hensigtsmæssigt end det andet, skriver du kode der er mere pålidelig, mere vedligeholdelsesvenlig og mere behagelig at bruge. Opsætningen er minimal, gevinsterne er umiddelbare, og fejlhåndteringen er allerede bygget ind. Det er sjældent at en arkitektonisk beslutning er så entydig som denne.
Har du brug for hjælp til at designe og implementere en queue-arkitektur der passer til din specifikke applikation, kan du læse mere om Kodesmedens Laravel-udvikling eller se automatisering og integrationer for mere komplekse asynkrone flows.