اگر توسعه دهنده وب باشید حتما براتون پیش اومده که یه کارایی رو بخوایین بر اساس ریکوست کاربر انجام بدین که زمان طولانی ای رو نیاز داره و خب چون ریکوست کاربری که داره توی فضای وب دریافت میشه رو نمیشه خیلی معطل نگه داشت این کارها باید به صورت غیر همزمان انجام بشه. معروفترین مثال این قضیه فرستادن ایمیل بر اساس اتفاقاتی هست که توی سیستم می افته یا مثلا دادن خروجی فایل حجیم به کاربر.
من قبلا این کار رو زیاد با PHP و Gearman انجام داده بودم ولی اخیرا که یه پروژه ای رو با Django تموم کردم و مجبور شدم براش Job Queue درست کنم از ابزارهای جدیدی استفاده کردم که میخوام تجربه ام رو اینجا به اشتراک بزارم.
کلیت کار این هست که ما یه Producer داریم که کارها رو ایجاد میکنه (همون ریکوست کاربر) یه «صف» داریم که این کارها در اون قرار میگیره و یه Worker هم که باید کارها رو دونه دونه از توی صف برداره و انجام بده.
ابتدا باید ابزارهای مورد نیاز رو نصب کنیم. اولیش Rabbitmq هست که برای انتقال پیام از Producer به «صف» استفاده میشه:
sudo yum install rabbitmq-server
بعد از اون باید Celery رو نصب کنید که همون Queue manager ماست و همینطور مسئول اجرای Worker هم هست (اگر با جنگو کار کرده باشید باید بدونید که توی Virtual Environment باید نصب بشه :
pip install celery
فرض من اینه که شما یک پروژه Django ایجاد کردین که ساختار فایل هاش به این صورته:
- sampleproject /
- manage.py
- sampleproject/
- __init__.py
- settings.py
- urls.py
اول یه فایل celery.py رو ایجاد کنید:
برای اینکه اینستنس Celery همیشه آپ باشه باید توی sampleproject/sampleproject/__init__.py ایمپورت بشه:
خب، حالا وقتش هست که Worker رو بنویسیم. برای این کار توی پوشه قبلی یه فایل به اسم tasks.py ایجاد میکنیم که با یک دکوریتور shared_task@ یک متد رو تبدیل به یک task میکنه که میتونه به صورت تکراری و غیر همزمان انجام بشه:
حالا هر جا که بخوایید create_report رو در background یا به صورت غیر همزمان (Async) انجام بدین کافیه اون رو با متد delay صدا بزنید:
البته خب فعلا کار نمیکنه! هنوز worker استارت نشده و سرویس Rabbitmq هم همینطور. استارت کردن Rabbitmq که خب خیلی آسونه و نیازی به تنظیمات اضافه نداره فعلا:
systemctl start rabbitmq-server
فقط باید توی فایل setting.py یه آدرسی برای کانال ارسال پیام ها بذارید:
و حالا وقت اون هست که worker مون رو راه بندازیم:
celery -A sampleproject worker -l info
بعد از اجرای این کامند celery وضعیت worker و همینطور لیست کارهایی که به اون ارجاع شده رو بهتون نمایش میده هر کاری هم که انجام بشه رو باز لیست میکنه به صورت زنده و مستقیم! :))
حتما متوجه شدین که اگر shell بسته بشه worker هم از کار می افته و این یه مشکل بزرگه. راه حل اینه که process مربوط به celery که مسئول اجرای worker ماست رو daemonize کنیم. که در هر صورت این پروسه در هر لحظه در حال اجرا باشه و منتظر باشه تا هر task ای که به صف اضافه شد رو انجام بده.
اینجا نحوه دمونایز کردن celery توضیح داده شده ولی خب چون خیلی گنگ بود و من راهی که رفتم رو توضیح میدم. از اینجا فایل celeryd رو دانلود کنید و توی سرور در پوشه /etc/inint.d/ کپی کنید. برای اینکه فایل قابلیت اجرا داشته باشه تنظیمات permission فایل رو به این صورت تغییر بدین:
sudo chmod 755 /etc/init.d/celeryd
sudo chown root:root /etc/init.d/celeryd
فایلی که ایجاد شد فایلی هست که میتونه در background اجرا بشه ولی این فایل نیاز به یک فایل دیگر داره که تنظیمات worker رو از روی اون بگیره. فایل تنظیمات رو باید در پوشه /etc/default ایجاد کنید این کار رو با vi یا nano می تونید به این شکل انجام بدین:
sudo vi /etc/default/celeryd
فرمت فایل تنظیمات هم به این صورت هستش:
مسیرهایی که در این فایل داده شده برای هر پروژه و سرور ممکنه متفاوت باشه و خیلی دقت کنید که همشون درست باشه در غیر این صورت فایل celeryd اجرا نمیشه. البته من خودم یه تنظیمات اشتباهی داشتم که با اجرای فایل etc/init.d/celeryd/ به صورت زیر تونستم خطاها رو ببینم و اصلاح کنم:
sh -x /etc/init.d/celeryd start
اگر این کامند بدون مشکل اجرا بشه کار دیگه تمام هست و worker ما یه لنگ پا منتظر خواهد بود تا task های توی صف رو انجام بده.
ابزارها و کارهایی که معرفی شد صرفا برای ایجاد ساده ترین حالت یک Job Queue و مکانیزم انجام کار به صورت غیرهمزمان بود. کارهای پیچیده تر و جزئیات بسیار دیگه ای هست که نیاز به مطالعه و توضیح بیشتر داره که شاید زمان دیگه ای بهش پرداختم مثل اینکه شما بتونید چند صف و چند worker داشته باشید و یا اینکه task هایی رو به صورت زمانبندی شده انجام بدین مثلا هر 24 ساعت یکبار بروزرسانی هایی رو در سیستم ها تون داشته باشید.