آموزش مقدماتی لاراول - پیادهسازی و اجرای صف Queue
در آموزشهای قبلی در مورد نحوه کار با رخدادها نکاتی رو مطرح کردیم و دیدیم که چطور میتونیم با کمک Event و Listener ها کدهای تمیزتری داشته باشیم. در ادامه و در این مقاله میخواهیم با کمک صفها برخی از عملیات زمانبر مانند ارسال ایمیل یا عملیاتی زمانبر همچون کار با تصاویر رو بهتر مدیریت کنیم.
خب بیایید برای شروع یک رخداد دیگری در سیستم ایجاد کنیم به این صورت که اگر کاربری در سیستم آهنگی ذخیره کرد به ادمین یک ایمیل ارسال بشه. برای این کار همانطور که در مقاله قبل گفتم وارد آرایه تعریف رخدادها در مسیر app/Providers/EventServiceProvider.php
میشیم و موادی که نیاز داریم رو وارد میکنیم:
\App\Events\NewSongCreatedEvent::class => [
\App\Listeners\NotifyNewSongCreatedListener::class,
]
و بعد دستور زیر رو وارد میکینم:
php artisan event:generate
دستور فوق موادی که میخواهیم رو برای ما تولید میکنه حالا کافیه یک ایمیل ساده ایجاد کنیم و دستور listener رو بصورت زیر تعریف کنیم:


در مورد ایجاد ایمیل هم کار سادست در صورت نیاز میتونید به کدهای برنامه در مخزن گیتهاب مراجعه کنید.
همانطور که مشاهده می کنید من برای اینکه عملیات زمان بیشتری بگیره یک وقفه ۱۰ ثانیه هم ایجاد کردم که با کمک تابع sleep انجام شد.
حالا وارد کنترلر آهنگامون میشیم و رخداد مورد نظرمون رو در تابع store وارد میکنیم:
public function store()
{
$data = \request()->validate([
'name' => 'required|min:3|max:120',
'genre_id' => 'required'
]);
$song = Auth::user()->songs()->create($data);
event(new NewSongCreatedEvent(Auth::user(), $song));
return redirect('songs');
}
اگه همه چیز درست پیش رفته باشه بعد از تقریبا ۱۰ ثانیه آهنگ شما ذخیره خواهد شد. خب این مورد برای کاربر خوشایند نیست و باید جوری دیگری هندل بشه. اینجاست که باید سراغ ابزاری دیگر به نام queue برویم .
زمانی که یک عملیات در در صف قرار می گیرد اجرای دستورات در پس زمینه انجام خواهد شد در این صورت اگر در فرآیند ذخیره یک آهنگ رخداد ارسال پیام به ادمین در پس زمینه ارائه شود فرآیند ذخیره آهنگ بسیار سریع تر انجام خواهد شد در واقع کاربر منتظر ارسال ایمیل نخواهد ماند و فرایند ارسال به سرعت اتفاق خواهد افتاد.
برای پیاده سازی سیستم صف بندی وظایف میتونیم از سیستم های مختلفی چون Amazon SQS ، Beanstalk که یک جور api خارجی هستند و یا Redis و هر دیتابیس relational استفاده کنیم. برای تعریف اینکه از چه سیستم میخواهیم استفاده کنیم لازمه که وارد فایل env. بشیم و مقدار کلید QUEUE_CONNECTION رو تغییر بدیم. در اینجا ما با دیتابیس رو انتخاب میکنیم و مقدار کلید رو برابر با database قرار میدهیم. سپس دستور زیر رو اجرا میکنیم تا که یک فایل migration برای ما ایجاد شود این فایل برای ساخت یک جدول ساده که وظایف رو ذخیره میکنه استفاده میشه
php artisan queue:table
خوب حالا مایگریت میکنیم تا جدول مورد نیاز ایجاد بشه!
php artisan migrate
در نهایت برای اینکه که هر یک از Listener هامون از طریق queue اجرا بشه کافیه قابلیت queue شدن رو به کلاس Listener مورد نظرمون انتقال بدیم برای این منظور به صورت زیر عمل می کنیم:


خوب حالا بار دیگر به صفحه تولید آهنگ برمیگردیم و یک آهنگ ایجاد میکنیم خواهید دید که این بار به سرعت ذخیره آهنگ انجام می شه. حالا اگه جدول jobs رو که در مرحله قبل ساختیم رو مشاهده کنید، میبینید که یک ردیف در آن ایجاد شده که در واقع به همین listener اشاره میکنه:


ما تا به الان تنها موفق شدیم که وظایف رو در قالب یکسری رکورد در جدول jobs ذخیره کنیم. حالا برای اجرای وظایفی که در صف ذخیره شدهاند باید دستور زیر رو اجرا کنید.
php artisan queue:work
پس از اجرای دستور فوق خواهید دید که ترمینال شما پس از اجرای وظایفی که در جدول jobs تعریف شده به صورت live منتظر وظایف جدیدتر خواهد ماند و اگر وارد جدول jobs شوید خواهید دید که هیچ رکوردی در آن وجود ندارد چرا که وظایفی که قبلا در صف بودند حالا اجرا شدند.
همانطور که مشاهده می کنید در صورتی که مجددا آهنگ جدیدی ذخیره کنیم فرایند ارسال ایمیل در پس زمینه انجام خواهد شد و ذخیره آهنگ هم به سرعت اتفاق میافته.
اجازه دهید کمی دقیق تر به دستور بالا نگاه کنیم:
php artisan queue:work -h


همانطور که در توضیحات artisan مشاهده می کنید این دستور یک فرایند (process) بعنوان یک برنامه (daemon) در سیستم شما بصورت پیوسته اجرا می کند که منتظر jobهای جدید در صف است. همچنین برای این دستور فلگهای متعددی تعریف شده که با کمک آنها میتونید این دستور رو تنظیم کنید، برای نمونه میزان رم اختصاص داده شده و کلی خاصیت دیگه که در بالا به اونها اشاره شده.
اما چنانچه ترمینال بسته بشه این برنامه هم متوقف خواهد شد برای جلوگیری از این موضوع کافیه به انتهای دستور یک & اضافه کنیم.


در این صورت دستور با بسته شدن ترمینال هم بکار خودش ادامه میده. خروجی دستور فوق یک id به ما میدهد که این id به process که ما اجرا کردیم اشاره میکنه. اگر دستور زیر رو هم بزنیم نشون میده در حال حاضر چه برنامههایی در حال اجرا هستند( l- هم id هارو به لیست نمایشی اضافه میکنه):


و در نهایت اگر خواستید این برنامه رو متوقف کنید کافیه دستور زیر رو همراه با id بزنید:
kill <id of daemon>


امکان این هم وجود دارد که شما خروجی عملیات در این برنامه رو ذخیره کنید تنها کافیه مسیر ذخیره لاگهای برنامه حین اجرا رو تعریف کنید تا در صورت نیاز لاگهارو بررسی کنیم:
php artisan queue:work > storage/logs/jobs.log &
اما اگر این برنامه با خطایی مواجه شود یا به هر نحوی متوقف بشه (مانند ریبوت سرور) نیاز داریم که مجدد اون رو راهاندازی کنیم. برای این منظور ابزار دیگری به نام supervisor هست که برای مدیریت فرایندها مورد استفاده قرار میگیره. البته این ابزار برای سیستم عاملهای unix ساخته شده ولی مشابههایی هم در ویندوز داریم مانند pm2 یا سرویسهای داخلی ویندوز.
اگر شما هم مانند من از لینوکس استفاده میکنید من در اینجا نحوه پیادهسازی supervisor رو آموزش خواهم داد.
۱. ابتدا با دستور زیر supervisor رو نصب کنید:
apt-get install supervisor
۲. سپس وارد مسیر /etc/supervisor/conf.d بشید و یک فایل تنظیمات بصورت زیر بنویسید:
nano song-queue.conf
در اینجا به مسیر اجرای پروژتون برای اجرای دستور artisan و محل ذخیره لاگ دقت کنید:
[program:song]
process_name=%(program_name)s_%(process_num)02d
command=php /root/src/codefarm/tutorials/laravel-basic/artisan queue:work --sleep=3 --tries=3
autostart=true
autorestart=true
user=root
numprocs=2
redirect_stderr=true
stdout_logfile=/root/src/codefarm/tutorials/laravel-basic/storage/logs/jobs.log
۳. حالا برای اینکه تنظیماتی که وارد کردین به این سرویس اضافه بشه دستور زیر رو وارد کنید:
supervisorctl reread
۴. نهایتا هم سرویس رو بروزرسانی کنید تا فرایند در پسزمینه سیستمون اضافه بشه:
supervisorctl update
من هم این عملیات رو در سیستم خودم پیاده کردم که خروجی بصورت بود:


در نهایت برای اینکه مشخص بشه آیا این عملیات اجرا میشه یا من یکبار سیستمم رو ریستارت کردم و بعد وارد شدم و یک آهنگ اضافه کردم. نتیجه رضایت بخش بود چرا که ایمیل مورد نظر ما پس از ۱۰ثانیه به mailtrap ارسال شد. اگر فایل لاگی هم که در نظر گرفتیم مشاهده میکنید عملیات بدرستی اجرا میشه.
درصورتی هم که خواستید با لیست دستورات supervisor آشنا بشید اینجا رو مطالعه کنید
در این آموزش مفهوم بسیار مهم ایجاد صف و مدیریت اجرای صحیح آن با کمک supervisor رو پیادهسازی کردیم.
امیدوارم از این آموزش هم لذت برده باشید.