آموزش مقدماتی لاراول - پیاده‌سازی و اجرای صف Queue

در آموزش‌های قبلی در مورد نحوه کار با رخدادها نکاتی رو مطرح کردیم و دیدیم که چطور می‌تونیم با کمک Event و Listener ها کدهای تمیزتری داشته باشیم. در ادامه و در این مقاله می‌خواهیم با کمک صف‌ها برخی از عملیات زمان‌بر مانند ارسال ایمیل یا عملیاتی زمان‌بر همچون کار با تصاویر رو بهتر مدیریت کنیم.


خب بیایید برای شروع یک رخداد دیگری در سیستم ایجاد کنیم به این صورت که اگر کاربری در سیستم آهنگی ذخیره کرد به ادمین یک ایمیل ارسال بشه. برای این کار همانطور که در مقاله قبل گفتم وارد آرایه تعریف رخدادها در مسیر ‍app/Providers/EventServiceProvider.php میشیم و موادی که نیاز داریم رو وارد می‌کنیم:

\App\Events\NewSongCreatedEvent::class => [
	\App\Listeners\NotifyNewSongCreatedListener::class,
]

و بعد دستور زیر رو وارد می‌کینم:

php artisan event:generate

دستور فوق موادی که می‌خواهیم رو برای ما تولید می‌کنه حالا کافیه یک ایمیل ساده ایجاد کنیم و دستور listener رو بصورت زیر تعریف کنیم:

Image for post
Image for post

در مورد ایجاد ایمیل هم کار سادست در صورت نیاز می‌تونید به کدهای برنامه در مخزن گیت‌هاب مراجعه کنید.

همانطور که مشاهده می کنید من برای اینکه عملیات زمان بیشتری بگیره یک وقفه ۱۰ ثانیه هم ایجاد کردم که با کمک تابع 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 مورد نظرمون انتقال بدیم برای این منظور به صورت زیر عمل می کنیم:

Image for post
Image for post

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

Image for post
Image for post

ما تا به الان تنها موفق شدیم که وظایف رو در قالب یکسری رکورد در جدول jobs ذخیره کنیم. حالا برای اجرای وظایفی که در صف ذخیره شده‌اند باید دستور زیر رو اجرا کنید. 

php artisan queue:work

پس از اجرای دستور فوق خواهید دید که ترمینال شما پس از اجرای وظایفی که در جدول jobs تعریف شده به صورت live منتظر وظایف جدیدتر خواهد ماند و اگر وارد جدول jobs شوید خواهید دید که هیچ رکوردی در آن وجود ندارد چرا که وظایفی که قبلا در صف بودند حالا اجرا شدند. 

همانطور که مشاهده می کنید در صورتی که مجددا آهنگ جدیدی ذخیره کنیم فرایند ارسال ایمیل در پس زمینه انجام خواهد شد و ذخیره آهنگ هم به سرعت اتفاق می‌افته.

اجازه دهید کمی دقیق تر به دستور بالا نگاه کنیم:

php artisan queue:work -h
Image for post
Image for post

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

اما چنانچه ترمینال بسته بشه این برنامه هم متوقف خواهد شد برای جلوگیری از این موضوع کافیه به انتهای دستور یک & اضافه کنیم.

Image for post
Image for post

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

Image for post
Image for post

و در نهایت اگر خواستید این برنامه رو متوقف کنید کافیه دستور زیر رو همراه با id بزنید:

kill <id of daemon>
Image for post
Image for post

امکان این هم وجود دارد که شما خروجی عملیات در این برنامه رو ذخیره کنید تنها کافیه مسیر ذخیره لاگ‌های برنامه حین اجرا رو تعریف کنید تا در صورت نیاز لاگ‌هارو بررسی کنیم:

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

من هم این عملیات رو در سیستم خودم پیاده کردم که خروجی بصورت بود:

Image for post
Image for post

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

درصورتی هم که خواستید با لیست دستورات supervisor آشنا بشید اینجا رو مطالعه کنید


در این آموزش مفهوم بسیار مهم ایجاد صف و مدیریت اجرای صحیح آن با کمک supervisor رو پیاده‌سازی کردیم. 

 

امیدوارم از این آموزش هم لذت برده باشید.