کار در پس زمینه با فریمورک جنگو

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

حدود یک سال پیش که می‌خواستم برای یکی از پروژه‌هایی که دستم بود سرویس پوش نوتیفیکیشن راه اندازی کنم. به یه مشکل برخوردم.
مدیر اپلیکیشن می‌خواست با اپلیکیشن مدیریت یک ریکوئست بده و پیغامی رو برای تمام کاربران اپلیکیشن ارسال بکنه. برای ارسال نوتیفیکیشن نیاز داشتم پیغام رو به سرور firebase برسونم که توسط سرویس firebase cloud messaging به دست کاربران برسه. و برای این کار باید یک ریکوئست به سرور firebase ارسال می‌کردم.
این روند به نظر ساده و روون میاد اما مشکل دقیقا اونجا بود که بعضی اوقات سرور firebase دیر جواب ریکوئست من رو می‌داد و از اون طرف هم مدیر اپلیکیشن منتظر بود تاییدیه ارسال پوش رو بگیره و خب مشکل پیش می‌اومد دیگه =)))
یکمی سرچ کردم و راه حلش رو پیدا کردم و مشکل رو حل کردم ، هم من راضی ، هم مدیر اپلیکیشن راضی و...

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


توضیح کلی روند کار

در کنار جنگو از دو پکیج دیگه استفاده می‌کنیم

اولی که Celery نام داره. یک "صف اعمال درست کن" ( task queue ) هست. ما یک سری عمل یا همون تسک رو براش تعیین می‌کنیم و اون یه سری صف درست میکنه. هرموقع که ما یکی از عمل هامون رو صدا زدیم. اون عمل رو می‌اندازه توی صف تا وقتی که نوبتش رسید انجام بشه و وقت کار اصلی ما رو نگیره.

دومی هم redis نام داره ردیس در اصل یک ساختمان داده نگهدارنده درون حافظه ای هست ( مثل یک دیتابیس که درون حافظه کار میکنه ).
ما از ردیس برای انتقال اطلاعات بین اپلیکیشن جنگو و celery استفاده می‌کنیم ( راستی این هم بدونید که ردیس به عنوان یک message broker یا همون انتقال دهنده پیام هم استفاده میشه )

فکر می‌کنم توی همین دو مورد بالا روند کار هم به صورت خلاصه توضیح دادم بهتون =)))


فرضیات و اهداف

فرض می‌کنیم که یک فضای ایزوله (virtual environment) به نام venv و درون اون یک پروژه جنگو به نام prj و درون اون یک اپلیکیشن جنگو به نام prapp داریم.
پس اطلاعات ما به صورت زیر هستن :


venv
prj
├── manage.py
├── prj
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── prapp
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── models.py
│   ├── tests.py
│   └── views.py


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


شروع کار

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

sudo apt install redis-server

بعد از اون باید ماژول‌های ردیس و سلری برای پایتون رو نصب بکنیم :

pipenv install celery redis

سپس با اضافه کردن تنظیمات سلری به settings پروژه کار رو ادامه میدیم :


BROKER_URL = 'redis://localhost:6379'
CELERY_RESULT_BACKEND = 'redis://localhost:6379'
CELERY_ACCEPT_CONTENT = ['application/json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TIMEZONE = 'Asia/Tehran'

الان باید یک فایل celery.py توی آدرس prj/prj بسازیم و محتویات زیر رو توی اون بنویسیم :

from __future__ import absolute_import
import os
from celery import Celery
from django.conf import settings

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'prj.settings')
app = Celery('prj')

app.config_from_object('django.conf.settings')
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)

هدف از ساخت این فایل این هست که با اجرا کردن این فایل با هربار اجرا شدن پروژه جنگو خودمون تنظیمات سلری بر اساس تنظیماتی که توی فایل settings.py اضافه کردیم انجام بشه و سلری ما آماده استفاده باشه. حالا برای صدا زدن این فایل و اجرا شدن سلری کافیه که به فایل prj/prj/__init__.py اطلاعات زیر رو اضافه بکنیم :

from __future__ import absolute_import
from .celery import app as celery_app

__all__ = ('celery_app',)

خب حالا که تمام تنظیمات اولیه انجام شده ، کافیه که اعمال مورد نیاز برای انجام شدن در پس زمینه رو برای سلری مشخص کنیم. برای این کار یک فایل به نام tasks.py در آدرس prj/prapp می‌سازیم و تسک‌های خودمون رو توی اون می‌نویسیم ( به عنوان مثال ما در این آموزش می‌خواستیم یک تسک برای جمع کردن دو عدد و ذخیره اون بنویسیم ) :

from celery import shared_task
import time

@shared_task()
def plus(x, y):
    time.sleep(5)
    alaki = open(&quotalaki.txt&quot, &quota+&quot)
    alaki.write(str( x + y ) + &quot\n&quot)
    alaki.close()

خب ما از یک decorator به نام shared_task استفاده کردیم که این تابع رو به یک عمل معتبر celery تبدیل می‌کنه و ما می‌تونیم با هر Celery که داریم این تسک رو صدا بزنیم.
همچنین برای بهتر شدن تستمون ۵ ثانیه توقف گذاشتیم که تفاوت انجام شدن عمل در background و foreground رو متوجه بشیم.

و در انتها ویو هم می‌نویسم تا با نحوه صدا زدن یک تسک سلری آشنا بشید.
این هم از محتویات فایل views.py :

from .tasks import plus
from django.http import HttpResponse

def alaki(request):
    for i in range(100):
        plus.delay(1398, 1)
    return HttpResponse(&quotOK&quot)

خب باقی تنظیمات جنگو و ران کردن پروژه رو به عهده خودتون باقی می‌گذارم اما اینو بگم که شما بعد از ران کردن پروژه جنگو باید worker سلری رو ران بکنید تا تسک های شما رو انجام بده. برای ران کردن worker سلری از دستور زیر می‌تونید استفاده بکنید :

celery -A prj worker -l info

تامام تامام =)))

نتیجه مطلوب

همونطور که به احتمال زیاد خودتون می‌دونید ما می‌تونستیم ویو خودمون رو به شکل زیر بنویسیم :

from django.http import HttpResponse
import time

def alaki(request):
    for i in range(100):
        time.sleep(5)
        alaki = open(&quotalaki.txt&quot, &quota+&quot) 
        alaki.write(str( x + y ) + &quot\n&quot)
        alaki.close()
    return HttpResponse(&quotOK&quot)

اما در این صورت وقتی که به این ویو ریکوئست ارسال می‌کردیم باید ۵۰۰ ثانیه صبر می‌کردیم تا یک OK تحویل بگیریم
در حالی که الان با استفاده از celery می‌تونیم همون لحظه OK رو تحویل بگیریم و باقی کار ها توی پس زمینه انجام بشه =))



ممنونم از این که این پست رو مطالعه کردید
امیدوارم که براتون مفید باشه =))
لایک و کامنت فراموش نشه...
سوالی بود در خدمتم.
نوشته شده با ❤️ توسط کوچیکتون حمیدرضا شجراوی =)))