PHP / LARAVEL // LARAVELD::04 İLERİ
22m READCOMPLETION: 76%ID::PHP-301

LARAVEL QUEUES, JOBS VE TASK SCHEDULING

Queue Job sistemi, Batch dispatch, zincirli job'lar ve Supervisor

Laravel Queues, ağır işlemleri (e-posta gönderme, dosya işleme, API çağrıları) arka plana alarak kullanıcının HTTP isteğini hızlı tamamlamasına olanak tanır. Bu derste Job sistemi, Queue Worker ve zamanlanmış görevler konularını derinlemesine inceliyoruz.

İlk Job

// PHP //
// app/Jobs/MakaleEmailiGonder.php
namespace App\Jobs;
 
use App\Models\Makale;
use App\Models\Kullanici;
use App\Mail\YeniMakaleMail;
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 MakaleEmailiGonder implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
 
    public int   $tries   = 3;
    public int   $timeout = 60;
    public array $backoff = [60, 120, 300]; // 1dk, 2dk, 5dk
 
    public function __construct(
        public readonly Makale    $makale,
        public readonly Kullanici $alici,
    ) {}
 
    public function handle(): void
    {
        Mail::to($this->alici->email)
            ->send(new YeniMakaleMail($this->makale, $this->alici));
 
        $this->makale->bildirimler()->create([
            'kullanici_id'  => $this->alici->id,
            'kanal'         => 'email',
            'gonderildi_at' => now(),
        ]);
    }
 
    public function failed(\Throwable $istisna): void
    {
        \Log::error('Email gönderilemedi', [
            'makale_id' => $this->makale->id,
            'alici_id'  => $this->alici->id,
            'hata'      => $istisna->getMessage(),
        ]);
    }
}

Job Dispatch

// PHP //
class MakaleController extends Controller
{
    public function yayinla(Makale $makale): RedirectResponse
    {
        $makale->update(['yayinlandi' => true, 'yayinlandi_at' => now()]);
 
        // Tüm takipçilere 5dk gecikmeli gönder
        $makale->yazar->takipciler->each(function (Kullanici $takipci) use ($makale) {
            MakaleEmailiGonder::dispatch($makale, $takipci)
                ->delay(now()->addMinutes(5))
                ->onQueue('emailler');
        });
 
        // Koşullu dispatch
        MakaleEmailiGonder::dispatchIf(
            $makale->one_cikarmak_icin_onayli,
            $makale, $this->adminKullanici()
        );
 
        return redirect()->route('makaleler.goster', $makale);
    }
}

Batch Jobs — Toplu İşlem

// PHP //
// Toplu job dispatch — Bus::batch()
use Illuminate\Bus\Batch;
use Illuminate\Support\Facades\Bus;
 
class RaporController extends Controller
{
    public function tumKullanicilaraGonder(Request $request): JsonResponse
    {
        $kullanicilar = Kullanici::aktif()->cursor(); // Memory verimli
 
        $toplu = Bus::batch(
            $kullanicilar->map(fn(Kullanici $k) => new MakaleEmailiGonder($this->makale, $k))
        )
        ->name('Haftalık Rapor Dağıtımı')
        ->allowFailures()  // Bazı joblar başarısız olsa batch devam eder
        ->onQueue('emailler')
        ->then(function (Batch $batch) {
            // Tüm joblar başarıyla tamamlandı
            \Log::info("Batch tamamlandı: {$batch->id}");
        })
        ->catch(function (Batch $batch, \Throwable $e) {
            // İlk başarısızlıkta
            \Log::error("Batch hatası: {$e->getMessage()}");
        })
        ->finally(function (Batch $batch) {
            // Başarı/başarısızlıktan bağımsız
            RaporTamamlandi::dispatch($batch->id);
        })
        ->dispatch();
 
        return response()->json([
            'batch_id'    => $toplu->id,
            'toplam_is'   => $toplu->totalJobs,
        ]);
    }
 
    public function batchDurumu(string $batchId): JsonResponse
    {
        $batch = Bus::findBatch($batchId);
 
        return response()->json([
            'tamamlanan'   => $batch->processedJobs(),
            'basarisiz'    => $batch->failedJobs,
            'toplam'       => $batch->totalJobs,
            'yuzde'        => $batch->progress(),
            'tamamlandi'   => $batch->finished(),
        ]);
    }
}

Chained Jobs — Zincirleme

// PHP //
// Adım adım işleme — her job önceki tamamlanınca çalışır
MakaleYayinla::withChain([
    new GoruntuOnizlemeOlustur($makale),
    new SeoMetadataGuncelle($makale),
    new SosyalMedyayaPaylas($makale),
    new AbonelereEmailGonder($makale),
])->dispatch($makale);

Queue Worker Yönetimi

// INI //
# /etc/supervisor/conf.d/laravel-worker.conf
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/artisan queue:work redis --sleep=3 --tries=3 --timeout=60
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=8
redirect_stderr=true
stdout_logfile=/var/log/worker.log
stopwaitsecs=3600

Task Scheduling — Zamanlanmış Görevler

// PHP //
// routes/console.php (Laravel 11+)
use Illuminate\Support\Facades\Schedule;
 
Schedule::command('makale:bosluk-doldur')
    ->dailyAt('03:00')
    ->withoutOverlapping()   // Paralel çalışmayı engelle
    ->onOneServer()          // Çok sunuculu ortamda sadece bir kez
    ->appendOutputTo(storage_path('logs/scheduler.log'));
 
Schedule::job(new IstatistikleriTopla)
    ->hourly()
    ->between('08:00', '23:00')  // Gece çalışma
    ->onSuccess(fn() => cache()->put('son_istatistik', now()))
    ->onFailure(fn() => alert_team('İstatistik toplamada hata!'));
 
Schedule::call(function () {
    Makale::eskiler()->delete();
})->weekly()->sundays()->at('04:00');
 
// Dinamik schedule — veritabanından
Schedule::call(function () {
    ZamanlanmisIslem::bekleyen()->each(
        fn($is) => dispatch(new OzelIs($is))
    );
})->everyMinute();

Redis Queue ile Öncelik

// PHP //
// .env
QUEUE_CONNECTION=redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
 
// Job'a öncelik atama
class AcilBildirim implements ShouldQueue
{
    public function __construct(...) {}
 
    // Hangi queue'da çalışacağı
    public function viaQueues(): array
    {
        return ['acil']; // Worker --queue=acil,emailler,default
    }
}
 
// Dispatch sırasında queue belirt
AcilBildirim::dispatch($bildirim)->onQueue('acil');
NormalIslem::dispatch($veri)->onQueue('default');
 
// Worker — öncelik sırası (acil önce kontrol edilir)
// php artisan queue:work --queue=acil,emailler,default

Sonuç

Laravel Queues ile ağır işlemler arka plana alınır, kullanıcı HTTP isteği milisaniyeler içinde yanıt alır. Batch Jobs toplu işlemler için, Chain Jobs sıralı adımlar için, Scheduling ise tekrarlayan görevler için idealdir. Supervisor ile worker'ları güvenilir şekilde yönetin ve Redis'i production queue driver'ı olarak kullanın. Bir sonraki derste Laravel Broadcasting ve WebSocket ile gerçek zamanlı özellikler konusunu ele alacağız.