در خیلی از اپلیکیشنهای Laravel، بخشی از کارها نباید همزمان با پاسخ HTTP انجام شوند؛ مثل ارسال ایمیل، پردازش تصویر، همگامسازی با API خارجی یا ساخت گزارش. اگر این کارها را داخل Controller نگه دارید، کاربر منتظر میماند و احتمال Timeout بالا میرود.
Laravel برای همین سناریو، سیستم Queue و کلاسهای Job را فراهم کرده است. در این مقاله قدمبهقدم میبینیم Job چیست، چطور ساخته میشود، چطور Dispatch میشود و چه نکاتی را در Production باید رعایت کنید.
یک Job واحد کاری است که میتواند بلافاصله یا با تأخیر، در پسزمینه اجرا شود. معمولاً منطق سنگین یا وابسته به سرویس خارجی را داخل Job میگذارید و از Request فقط آن را Dispatch میکنید.
مزیت اصلی این الگو:
- پاسخ سریعتر به کاربر
- امکان Retry هنگام خطا
- جدا کردن منطق سنگین از لایه HTTP
- کنترل بهتر Rate Limit و بار سرور
قبل از شروع مطمئن شوید:
- یک پروژه Laravel (نسخه ۸ به بالا پیشنهاد میشود) دارید
- در
.env درایور Queue را مشخص کردهاید
- برای محیط واقعی، یک Worker در حال اجرا دارید
در فایل .env معمولاً یکی از این گزینهها را میبینید:
sync یعنی Job همان لحظه در همان Process اجرا میشود و برای Production مناسب نیست. برای شروع توسعه، database یا redis گزینههای رایجاند.
اگر از database استفاده میکنید، جدول jobs را بسازید:
با Artisan یک Job بسازید:
فایل معمولاً در مسیر app/Jobs/SendWelcomeEmail.php ساخته میشود. ساختار پایه چیزی شبیه این است:
اگر کلاس Job اینترفیس ShouldQueue را پیادهسازی کند، Laravel آن را به Queue میفرستد. بدون این اینترفیس، Job بهصورت همزمان (Synchronous) اجرا میشود.
از Controller، Listener یا Service میتوانید Job را Dispatch کنید:
جدا کردن صفها (مثلاً emails، reports، default) کمک میکند کارهای سنگین، کارهای سبک را بلوکه نکنند.
Job فقط وقتی اجرا میشود که Worker در حال گوشدادن به Queue باشد:
در Production معمولاً از Supervisor یا systemd استفاده میشود تا Worker بعد از Crash دوباره بالا بیاید و بعد از Deploy ریاستارت شود.
داخل خود Job میتوانید رفتار خطا را دقیقتر تعریف کنید:
برای دیدن Jobهای شکستخورده، جدول failed jobs را بسازید:
اینجا منطق ساخت گزارش داخل Service است و Job فقط orchestration میکند؛ این جداسازی تستپذیری و خوانایی را بهتر میکند.
- Job را کوچک نگه دارید: یک مسئولیت مشخص داشته باشد.
- Model را با SerializesModels پاس بدهید: Laravel فقط ID را در Queue ذخیره میکند و هنگام اجرا دوباره Model را میخواند.
- دادههای حساس را در Payload نگذارید: رمز عبور، توکن خام یا اطلاعات محرمانه را Serialize نکنید.
- Idempotent بنویسید: اگر Job دوباره اجرا شد، نتیجه تکراری یا خراب نسازد.
- صفها را جدا کنید: ایمیل، گزارش و کارهای سنگین را در Queueهای مجزا بگذارید.
- لاگ و Monitoring داشته باشید: Horizon (برای Redis)، failed jobs و لاگ اپلیکیشن را جدی بگیرید.
- استفاده از
QUEUE_CONNECTION=sync در Production و فکر کردن که Queue دارید
- فراموش کردن اجرای
queue:work یا Supervisor
- گذاشتن منطق خیلی سنگین داخل Controller بهجای Job
- Pass کردن Objectهای غیرقابل Serialize
- نداشتن استراتژی Retry و متد
failed()
- یک صف واحد برای همه کارها و ایجاد گلوگاه
برای نوشتن Job در Laravel این مسیر کافی است: Job بسازید، ShouldQueue را پیادهسازی کنید، منطق را در handle() بنویسید، با dispatch() ارسال کنید و Worker را اجرا نگه دارید. اگر کارهای پسزمینه را از Request جدا کنید، اپلیکیشن سریعتر، پایدارتر و مقیاسپذیرتر میشود.
Listener به Event واکنش نشان میدهد. اگر Listener اینترفیس ShouldQueue داشته باشد، خودش مثل یک Job صف میشود. Job را معمولاً مستقیم Dispatch میکنید؛ Listener را با Fire شدن Event.
وقتی چند Job مرتبط دارید و میخواهید پیشرفت، تکمیل یا شکست کل مجموعه را یکجا مدیریت کنید، Batch مناسب است.
بله. درایور database برای شروع کاملاً قابل استفاده است. برای حجم بالاتر، Redis معمولاً عملکرد بهتری میدهد.