Очереди, события, планировщик
Jobs и воркеры, события и слушатели, cron-планировщик, уведомления.
Junior Зачем нужны очереди и как отправить задачу в очередь?
Очереди выносят долгие операции из HTTP-запроса: отправка писем, ресайз картинок, обращения к внешним API, генерация отчётов. Ответ пользователю — мгновенно, работа — асинхронно воркером.
php artisan make:job ProcessPodcast
class ProcessPodcast implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public Podcast $podcast) {} // модель сериализуется как id
public function handle(): void { /* тяжёлая работа */ }
}
ProcessPodcast::dispatch($podcast);
ProcessPodcast::dispatch($podcast)->delay(now()->addMinutes(10))->onQueue('slow');
Запуск обработчика:
php artisan queue:work --queue=high,default
Драйверы: database, redis, sqs, sync (синхронно, для dev/test). Конфиг — config/queue.php, QUEUE_CONNECTION в .env.
На проде воркеры держат под Supervisor; для Redis — панель Horizon. Воркеры долгоживущие: после деплоя — queue:restart.
Junior Как работает планировщик задач (Scheduler)? Чем он заменяет cron?
Вместо десятков cron-записей — одна на сервере:
* * * * * cd /var/www/app && php artisan schedule:run >> /dev/null 2>&1
Задачи описываются в routes/console.php:
Schedule::command('reports:sync')->dailyAt('03:00');
Schedule::command('queue:prune-batches')->daily();
Schedule::job(new CleanupOldFiles)->hourly();
Schedule::call(fn () => DB::table('logs')->where('created_at', '<', now()->subMonth())->delete())
->weekly();
// Модификаторы:
->everyFiveMinutes() ->weekdays() ->between('8:00', '17:00')
->timezone('Europe/Moscow')
->withoutOverlapping() // не запускать, пока предыдущий не завершился (lock)
->onOneServer() // для кластера — только один сервер (нужен общий cache)
->runInBackground()
->emailOutputOnFailure('admin@example.com')
Отладка: php artisan schedule:list, schedule:test.
Преимущества над cron: расписание — в коде и под git, условия/локи/уведомления из коробки, единая точка входа.
Middle Как обрабатываются ошибки и повторные попытки в очередях?
class ProcessPodcast implements ShouldQueue
{
public $tries = 5; // всего попыток
public $backoff = [10, 60, 300]; // паузы между попытками (сек)
public $maxExceptions = 3; // max не-обработанных исключений
public $timeout = 120; // лимит времени выполнения
public function retryUntil(): \DateTime // альтернатива tries — дедлайн по времени
{
return now()->addHours(2);
}
public function failed(?Throwable $e): void // хук после окончательного провала
{
Log::error('Job failed', ['e' => $e]);
}
}
Провалившиеся задачи — в таблице failed_jobs:
php artisan queue:failed # список
php artisan queue:retry 5 # повторить
php artisan queue:retry all
php artisan queue:flush # очистить
Нюансы:
- идемпотентность: задача может выполниться больше одного раза (
ShouldBeUnique— защита от дублей в очереди); - модели сериализуются по id (
SerializesModels) — если запись удалили до выполнения, джоба получитModelNotFoundException(можноdeleteWhenMissingModels); - исключения в
middleware()джобы,->onConnection('redis'), цепочкиBus::chain([...])и батчиBus::batch([...]).
Middle События и слушатели: как устроено событийное программирование?
События развязывают код: «что-то произошло» → реакции в слушателях.
php artisan make:event OrderShipped
php artisan make:listener SendShipmentNotification --event=OrderShipped
class OrderShipped
{
use Dispatchable;
public function __construct(public Order $order) {}
}
class SendShipmentNotification
{
public function handle(OrderShipped $event): void
{
Mail::to($event->order->user)->send(...);
}
}
Регистрация (Laravel 11+ — автообнаружение по type-hint'ам; явно — в EventServiceProvider или Event::listen):
Event::listen(OrderShipped::class, SendShipmentNotification::class);
event(new OrderShipped($order)); // или OrderShipped::dispatch($order);
Ключевые возможности:
- слушатель может быть queued (
implements ShouldQueue) — реакция уйдёт в очередь; Event::fake()в тестах:Event::assertDispatched(OrderShipped::class);- подписчики (
Event::subscribe) — один класс на много событий; - события моделей (created/updated) — частный случай.
События ≠ очереди: событие — факт, очередь — способ асинхронно выполнить реакцию.
Middle Notifications: как отправлять письма, SMS и Slack-сообщения единообразно?
Notification — абстракция «уведомить пользователя» поверх каналов (mail, database, broadcast, SMS через Vonage, Slack, кастомные):
php artisan make:notification InvoicePaid
class InvoicePaid extends Notification implements ShouldQueue
{
public function __construct(public Invoice $invoice) {}
public function via(object $notifiable): array
{
return ['mail', 'database']; // каналы
}
public function toMail(object $notifiable): MailMessage
{
return (new MailMessage)
->subject('Счёт оплачен')
->line("Сумма: {$this->invoice->total}")
->action('Открыть', url("/invoices/{$this->invoice->id}"));
}
public function toArray(object $notifiable): array
{
return ['invoice_id' => $this->invoice->id]; // в таблицу notifications
}
}
$user->notify(new InvoicePaid($invoice));
Notification::send($users, new InvoicePaid($invoice)); // группе
Модель использует трейт Notifiable. Красивые письма — Markdown mailables / MailMessage с темой. Для сложных писем отдельно есть Mailable (php artisan make:mail). Notification::fake() + assertSentTo() — в тестах.
Senior Что такое Laravel Horizon и когда он нужен?
Horizon — панель управления и оркестратор воркеров для очередей на Redis.
Что даёт:
- дашборд: пропускная способность, время выполнения, failed jobs, ретраи;
- автобалансировка: стратегии
simple,auto(подстраивает число процессов под нагрузку); - конфигурация воркеров кодом (
config/horizon.php): сколько процессов на какую очередь, таймауты, tries; - мониторинг тегов: следить за джобами конкретного пользователя/заказа (
Horizon::tag); - метрики и уведомления при «зависших» очередях.
'environments' => [
'production' => [
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['high', 'default'],
'balance' => 'auto',
'maxProcesses' => 10,
],
],
],
php artisan horizon # вместо queue:work; под Supervisor — один процесс horizon
Когда нужен: Redis-очереди + нагрузка/важность мониторинга. Для database-драйвера или мелких проектов — избыточен, хватает queue:work под Supervisor.