وقتی قرار باشد در نرمافزار یک فرایند طولانی(مثل وارد کردن اطلاعات از فایل اکسل) انجام شود، بهتر است که وقت کاربر را نگیریم و به جای نشان دادن صفحه لودینگ این عملیات را در پشت صحنه انجام دهیم. در لاراوال این کار با استفاده queue یا صف انجام می شود.
با استفاده از queue دیگر لازم نیست که کاربر منتظر بماند تا یک عملیات سنگین در برنامه تمام شود و بعد به ادامه کارش بپردازد. میتوانیم به او بگوییم که وقتی فرایند تمام شد با استفاده از نوتیفیکیشن او را خبر میکنیم. اینطوری هم منابع سرور بهینه مصرف میشود و هم کاربر تجربه بهتری خواهد داشت.
در ادامه روش کار با queue در لاراول را یاد میگیریم.
انتخاب درایور برای اجرای queue ها
اول باید یک درایور برای queue انتخاب کنیم. درایور یعنی جایی که فرایندهای مورد نظرما در آن ذخیره میشوند به ترتیب (پشت سر هم مثل یک اعضای یک صف(queue))از آنجا خوانده و اجرا میشوند.
در اکثر موارد از database به عنوان درایور استفاده میکنیم پس مقدار QUEUE_CONNECTION
در فایل env.
را برابر database قرار دهیم.
QUEUE_CONNECTION=database
ایجاد جدول در دیتابیس
نیاز به یک جدول داریم که تمام مواردی که قرار است وارد queue شوند آنجا قرار بگیرد. پس دستورات زیر را اجرا میکنیم تا جداول ساخته شوند.
php artisan queue:table
php artisan migrate
یک جدول به نام jobs ساخته می شود تا تمام مواردی که که قرار است در پس زمینه نرم افزار اجرا شوند در این جدول ذخیره شود. به هر موردی که که قرار باشد توسط queue شود یک job یا وظیفه می گوییم. به خاطر همین اسم این جدول را jobs گذاشته اند.
درایور پیش فرض در لاراول sync
است. این حالت به درد مواقعی می خوردکه در لوکال هاست کار میکنید. عملیاتی که قرار بود از طریق queue اجرا شود بلافاصله همزمان با ریکوئست اجرا میشود. چنین حالتی آنچیزی نیست که ما از queue انتظار داریم. قصد ما این است که فرایند در همان لحظه آغاز نشود و بعدا توسط سرور اجرا شود.
به همین دلیل اگر میخواهید مانند حالتی که در سرور واقعی رخ می دهد اینجا هم در پس زمینه فرایند انجام شود، مقدار driver را در فایل config/queue.php
یا env تغییر دهید.
ساخت کلاس Job
برای اینکه یک عملیات را وارد queue کنیم باید یک کلاس به نام Job بسازیم. داخل این کلاس، فرایندی که قرار است در queue اجرا شود را مینویسیم.
ابن کلاس یک کلاس دیگر به نام Illuminate\Contracts\Queue\ShouldQueue
را implement میکند.
دستور زیر چنین کلاسی را برای ما میسازد.
php artisan make:job ImportProducts
این کلاس در پوشه app\jobs
به نامی که نوشتیم ایجاد میشود.
داخل کلاسی که ساخته شد یک متد handle وجود دارد. در این متد عملیات مورد نظرمان را مینویسیم.
اسم این کلاس را ImportProducts
گذاشتم. هدف من این بوده که محصولات را از یک فایل اکسل وارد دیتابیس کنم. چنین فرایندی احتمالا زمان میبرد. پس تصمیم گرفتم آن از طریق queue اجرا کنم.
صرفا برای نشان دادن مثال با استفاده از تابع sleep زمان انجام چنین فرایندی را شبیه سازی میکنم.
public function handle()
{
sleep(10);
Log::info('Import Compeleted');
}
با این کار موقع اجرای این متد ابتدا 10 ثانیه طول می کشد و بعد باقی کد اجرا می شود و یک لاگ ثبت میشود.
اجرا کردن job ها
برای این که job هایی که ساختیم شروع به کار کنند باید آن ها را صدا بزنیم. برای این کار از متد dispatch استفاده میکنیم.
Route::get('/queue-test', function () {
\App\Jobs\ImportProducts::dispatch();
});
با صدا زدن این متد این job در دیتابیس در جدول jobs قرار میگیرد و آماده اجرا خواهد بود. اینجا دیگر دستوراتی که در متد handle نوشتیم بلافاصله اجرا نمی شوند و کاربر نیازی نیست منتظر اجرای ریکوئست باشد.
اگر به جدول jobs نگاه کنید می بینید که یک ردیف جدید در آن ایجاد شده که اطلاعات job در آن ثبت شده است.
حالا برای این که job های ثبت شده در دیتابیس اجرا شوند از این دستور استفاده می کنیم:
php artisan queue:work
با این کار job ها یکی یکی اجرا می شوند. تا پردازش یک job تمام نشده، بعدی شروع نخواهد شد. دقیقا مثل یک صف.
اگر دوست دارید این فرایند با تاخیر اجرا شود از متد delay در ادامه آن استفاده کنید به این شکل:
ImportProduct::dispatch($products)->delay(now()->addHours(2));
با این کدی که بالا نوشتیم تا 2 ساعت بعد از dispatch شدن این job اجازه اجرا نخواهد داشت.
تعیین زمان timeout
برای اینکه از درگیر شدن بیش ازحد سرور جلوگیری کنیم می توانیم یک حداکثر زمان مشخص برای اجرای job تعیین کنیم. تا اگر در این مدت به خاطر خطا یا هر علتی دیگری، پردازش تمام نشد لاراول این job را متوقف کند.
برای این کار به کلاس Job یک پراپرتی public به نام $timeout
اضافه میکنیم و مقدار آن به ثانیه برابر این زمان خواهد بود.
public $timeout = 60;
در کد بالا تعیین کردم که اگر بعد از گذشت 60 ثانیه فرایند تمام نشد پردازش متوقف میشود.
اجرای همه queue ها
اگر بخواهیم همه job ها را اجرا کنیم از این دستور استفاده می کنیم:
php artisan queue:work
با اجرای این دستور همه job هایی که در صف قرار گرفته اند به ترتسیب اجرا می شوند.
مدیریت job هایی که کامل اجرا نشدند
ممکن است بعضی از job ها به هر دلیلی حین اجرا دچار اشکال شده و متوقف شوند.
به طور پیش فرض یک migration برای ایجاد جدولی به نام failed_jobs
در لاراول وجود دارد. داخل این جدول لیست همه job هایی که اجرا نشده اند ثبت خواهد شد. اگر این migration را در لاراول مشاهده نکردید با دستور زیر آن را اجرا کنید.
php artisan queue:faild-table
تعیین تعداد اجرا قبل از fail شدن
اگر بخواهید می توانید مشخص کنید اگر یک job اجرا نشد، لاراول تا چندبار اجرای آن را امتحان کند. برای این کار یک متغیر public به نام $tries
در کلاس job تعریف کنید که مقدار آن برابر تعداد تکرار اجرای job است.
public $tries = 3;
اجرای دوباره همه job های fail شده
با این دستور لاراول دوباره همه job هایی که دچار مشکل شدند را اجرا میکند.
php artisan queue:retry --all
اجرای یکی از job های fail شده
ممکن است نیاز داشته باشید فقط یکی از این job های fail شده را اجرا کنید برای این کار از دستور زیر به همراه ID آن job استفاده کنید
php artisan queue:retry af11a89a-20c2-4756-996b-4ec8e6f66dd2
با دستور php artisan queue:failed
لیست همه job های fail شده به همراه ID آن ها را می بینید که از آن می توانید برای اجرای دوباره آن job استفاده کنید.
جمع بندی
با چیزهایی که در این مطلب یادگرفتیم می توانیم فرایندهای زمان بر را به سیستم queue در لاراول بسپاریم تا وقت کاربر را نگیریم و منابع سرور را بهینه تر مصرف کنیم.
حواسمان هم باید به job هایی که fail می شوند باشد. باید برای اجرای دوباره آنها اقدام کنیم یا اینکه تصمیم دیگری با توجه به نوع نرم افزارمان بگیریم.
سلام
وقت بخیر
چیزی که بنده در این متن دیدم اینه که اجرای دستور توسط همه قابل دسترسیه و از جهت امنیتی مشکل داره، درسته؟
اگر قرار باشه در زمان خاص و صرفا توسط سرور اجرا بشه چگونه هست؟
سلام
خیر این دستورات در ترمینال سرور اجرا میشه. هرکسی که به سرور دسترسی ادمین داشته باشه میتونه اونها رو اجرا کنه.
برای اجرای خودکار در سرور هم از یک چیزی به نام supervisor استفاده می کنیم. توی مستندات راجع بهش نوشته شده.(+)