وبلاگ رسانگار
با ما حرفه ای باشید

سرور مجازی NVMe

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

0 37
زمان لازم برای مطالعه: 15 دقیقه


معرفی

در این آموزش من یک درک کلی از اینکه چرا صف پیام های کرفس ارزشمند هستند همراه با روش استفاده از کرفس در ارتباط با Redis در یک برنامه جنگو ارائه خواهم کرد. برای نشان دادن ویژگی‌های پیاده‌سازی، یک برنامه پردازش تصویر مینیمالیستی می‌سازم که تصاویر کوچکی از تصاویر ارسال شده توسط کاربران را تولید می‌کند.

موضوعات زیر پوشش داده خواهد شد:

کد این مثال را می توان یافت روی GitHub به همراه دستورالعمل‌های نصب و راه‌اندازی اگر می‌خواهید مستقیماً به یک برنامه کاربردی کامل بروید، در غیر این صورت در ادامه مقاله شما را با روش ساخت همه چیز از ابتدا آشنا خواهم کرد.

زمینه روی صف های پیام با Celery و Redis

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

کرفس به بهترین وجه همراه با محلول ذخیره سازی که اغلب به عنوان واسطه پیام از آن یاد می شود استفاده می شود. یک کارگزار پیام رایج که برای کرفس استفاده می‌شود Redis است که یک ذخیره‌سازی اطلاعات کلیدی-مقدار عملکردی در حافظه است. به طور خاص، Redis برای ذخیره پیام‌های تولید شده توسط کد برنامه استفاده می‌شود که کار را در صف وظایف Celery توصیف می‌کند. Redis همچنین به‌عنوان ذخیره‌سازی نتایجی که از صف‌های کرفس خارج می‌شوند و سپس توسط مصرف‌کنندگان صف بازیابی می‌شوند، عمل می‌کند.

Local Dev Setup با Django، Celery و Redis

من ابتدا با سخت ترین قسمت که نصب Redis است شروع می کنم.

نصب Redis روی پنجره ها

  1. Redis را دانلود کنید فایل فشرده و از حالت فشرده خارج کنید
  2. فایل نامگذاری شده را پیدا کنید redis-server.exe و دوبار کلیک کنید تا سرور در یک پنجره فرمان راه اندازی شود
  3. به طور مشابه، فایل دیگری به نام redis-cli.exe را پیدا کنید و روی آن دوبار کلیک کنید تا برنامه در یک پنجره دستور جداگانه باز شود.
  4. در پنجره فرمانی که کلاینت cli را اجرا می کند، آزمایش کنید تا مطمئن شوید که مشتری می تواند با صدور فرمان با سرور صحبت کند ping و اگر همه چیز خوب پیش برود یک پاسخ از PONG باید برگردانده شود

نصب Redis روی Mac OSX / Linux

  1. Redis را دانلود کنید فایل تربال و آن را در یک دایرکتوری استخراج کنید
  2. فایل را با make install برای ساختن برنامه
  3. a را باز کنید terminal پنجره و اجرا کنید redis-server فرمان
  4. در دیگری terminal اجرای پنجره redis-cli
  5. در داخل terminal پنجره ای که کلاینت cli را اجرا می کند، تست کنید تا اطمینان حاصل شود که مشتری می تواند با صدور دستور با سرور صحبت کند ping و اگر همه چیز خوب پیش برود یک پاسخ از PONG باید برگردانده شود

Python Virtual Env و Dependencies را نصب کنید

الان میتونم حرکت کنم روی برای ایجاد یک محیط مجازی Python3 و نصب بسته های وابستگی لازم برای این پروژه.

برای شروع من یک دایرکتوری برای قرار دادن چیزهایی به نام image_parroter ایجاد می کنم سپس در داخل آن محیط مجازی خود را ایجاد می کنم. تمام دستورات از اینجا به بعد فقط از نوع یونیکس خواهند بود، اما اکثر آنها اگر نگوییم همه برای یک محیط ویندوز یکسان خواهند بود.

$ mkdir image_parroter
$ cd image_parroter
$ python3 -m venv venv
$ source venv/bin/activate

با فعال شدن محیط مجازی می توانم بسته های پایتون را نصب کنم.

(venv) $ pip install Django Celery redis Pillow django-widget-tweaks
(venv) $ pip freeze > requirements.txt
  • Pillow یک بسته پایتون غیر مرتبط با کرفس برای پردازش تصویر است که بعداً در این آموزش برای نشان دادن یک مورد استفاده در دنیای واقعی برای کارهای کرفس استفاده خواهم کرد.
  • Django Widget Tweaks یک پلاگین جنگو برای ارائه انعطاف پذیری در روش ارائه ورودی های فرم است.

راه اندازی پروژه جنگو

در حال حرکت روی، من یک پروژه جنگو با نام image_parroter و سپس یک برنامه جنگو به نام thumbnailer ایجاد می کنم.

(venv) $ django-admin startproject image_parroter
(venv) $ cd image_parroter
(venv) $ python manage.py startapp thumbnailer

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

$ tree -I venv
.
└── image_parroter
    ├── image_parroter
    │   ├── __init__.py
    │   ├── settings.py
    │   ├── urls.py
    │   └── wsgi.py
    ├── manage.py
    └── thumbnailer
        ├── __init__.py
        ├── admin.py
        ├── apps.py
        ├── migrations
        │   └── __init__.py
        ├── models.py
        ├── tests.py
        └── views.py

برای ادغام Celery در این پروژه جنگو، ماژول جدیدی اضافه کردم image\_parroter/image\_parrroter/celery.py کنوانسیون های زیر شرح داده شده در اسناد کرفس. در این ماژول جدید پایتون I import را os بسته و Celery کلاس از بسته کرفس.

این os ماژول برای مرتبط کردن یک متغیر محیطی Celery به نام استفاده می شود DJANGO_SETTINGS_MODULE با ماژول تنظیمات پروژه جنگو. پس از آن من نمونه ای از آن را نمونه می کنم Celery کلاس برای ایجاد celery_app متغیرنمونه. سپس پیکربندی برنامه Celery را با تنظیماتی که به زودی در فایل تنظیمات پروژه جنگو قابل شناسایی با پیشوند “CELERY_” قرار خواهم داد، به روز می کنم. در نهایت به تازه ایجاد شده می گویم celery_app نمونه ای برای کشف خودکار وظایف در پروژه.

تکمیل شده است celery.py ماژول در زیر نشان داده شده است:



import os
from celery import Celery

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'image_parroter.settings')

celery_app = Celery('image_parroter')
celery_app.config_from_object('django.conf:settings', namespace='CELERY')
celery_app.autodiscover_tasks()

در حال حاضر در پروژه است settings.py ماژول، در پایین، یک بخش برای تنظیمات کرفس تعریف می کنم و تنظیماتی را که در زیر می بینید اضافه می کنم. این تنظیمات به Celery می‌گوید که از Redis به عنوان واسطه پیام و همچنین محل اتصال به آن استفاده کند. آنها همچنین به Celery می گویند که انتظار داشته باشد پیام ها بین صف های وظیفه Celery و واسطه پیام Redis در نوع mime ارسال شوند. application/json.



... skipping to the bottom


CELERY_BROKER_URL = 'redis://localhost:6379'
CELERY_RESULT_BACKEND = 'redis://localhost:6379'
CELERY_ACCEPT_CONTENT = ('application/json')
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TASK_SERIALIZER = 'json'

در مرحله بعد، باید مطمئن شوم که برنامه cellery که قبلاً ایجاد و پیکربندی شده است، هنگام اجرا به برنامه جنگو تزریق می شود. این کار با وارد کردن برنامه Celery در برنامه اصلی پروژه جنگو انجام می شود \_\_init\_\_.py اسکریپت و ثبت صریح آن به عنوان یک نماد فضای نام در داخل image\_parroter پکیج جنگو.



from .celery import celery_app

__all__ = ('celery_app',)

من همچنان با اضافه کردن یک ماژول جدید به نام، به دنبال قراردادهای پیشنهادی هستم tasks.py در برنامه “تصویر کوچک” درون tasks.py ماژول I import را shared_tasks تابع دکوراتور و استفاده از آن برای تعریف تابع وظیفه کرفس به نام adding_task، همانطور که در زیر نشان داده شده است.



from celery import shared_task

@shared_task
def adding_task(x, y):
    return x + y

در نهایت، من باید برنامه تصویر کوچک را به لیست اضافه کنم INSTALLED_APPS در پروژه image_parroter settings.py مدول. در حالی که من در آنجا هستم، باید برنامه “widget_tweaks” را نیز اضافه کنم تا برای کنترل رندر ورودی فرم استفاده شود که بعداً از آن استفاده خواهم کرد تا به کاربران اجازه آپلود فایل ها را بدهم.



... skipping to the INSTALLED_APPS

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'thumbnailer.apps.ThumbnailerConfig',
    'widget_tweaks',
)

اکنون می توانم با استفاده از چند دستور ساده در سه ترمینال چیزها را آزمایش کنم.

در یک terminal من باید داشته باشم redis-server دویدن، مانند این:

$ redis-server
48621:C 21 May 21:55:23.706 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
48621:C 21 May 21:55:23.707 # Redis version=4.0.8, bits=64, commit=00000000, modified=0, pid=48621, just started
48621:C 21 May 21:55:23.707 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
48621:M 21 May 21:55:23.708 * Increased maximum number of open files to 10032 (it was originally set to 2560).
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 4.0.8 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                   
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
 |    `-._   `._    /     _.-'    |     PID: 48621
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           http://redis.io        
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               

48621:M 21 May 21:55:23.712 # Server initialized
48621:M 21 May 21:55:23.712 * Ready to accept connections

در یک ثانیه terminal، با یک نمونه فعال از محیط مجازی پایتون که قبلاً در پروژه نصب شده است root دایرکتوری بسته (همان موردی که شامل manage.py ماژول) برنامه cellery را راه اندازی می کنم.

(venv) $ celery worker -A image_parroter --loglevel=info
 
 -------------- (email protected) v4.3.0 (rhubarb)
---- **** ----- 
--- * ***  * -- Darwin-18.5.0-x86_64-i386-64bit 2019-05-22 03:01:38
-- * - **** --- 
- ** ---------- (config)
- ** ---------- .> app:         image_parroter:0x110b18eb8
- ** ---------- .> transport:   redis://localhost:6379//
- ** ---------- .> results:     redis://localhost:6379/
- *** --- * --- .> concurrency: 8 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** ----- 
 -------------- (queues)
                .> celery           exchange=celery(direct) key=celery
                

(tasks)
  . thumbnailer.tasks.adding_task

در سومین و پایانی terminal، دوباره با فعال بودن محیط مجازی پایتون، می‌توانم پوسته جنگو پایتون را راه‌اندازی کنم و خودم را آزمایش کنم. adding_task، مانند:

(venv) $ python manage.py shell
Python 3.6.6 |Anaconda, Inc.| (default, Jun 28 2018, 11:07:29) 
>>> from thumbnailer.tasks import adding_task
>>> task = adding_task.delay(2, 5)
>>> print(f"id={task.id}, state={task.state}, status={task.status}") 
id=86167f65-1256-497e-b5d9-0819f24e95bc, state=SUCCESS, status=SUCCESS
>>> task.get()
7

به استفاده از .delay(...) روش روی را adding_task هدف – شی. این روش متداول برای ارسال هر پارامتر لازم به شیء وظیفه ای است که با آن کار می شود و همچنین ارسال آن به کارگزار پیام و صف کار آغاز می شود. نتیجه تماس با .delay(...) متد یک مقدار بازگشتی وعده مانند از نوع است celery.result.AsyncResult. این مقدار بازگشتی اطلاعاتی مانند شناسه کار، وضعیت اجرای آن، و وضعیت وظیفه را به همراه توانایی دسترسی به نتایج تولید شده توسط کار از طریق .get() روش همانطور که در مثال نشان داده شده است.

ایجاد ریز عکسها در یک کار Celery

اکنون که راه‌اندازی صفحه دیگ برای ادغام نمونه Celery با پشتوانه Redis در برنامه جنگو، دیگر نمی‌توانم حرکت کنم. روی برای نشان دادن برخی عملکردهای مفیدتر با برنامه بندانگشتی که قبلا ذکر شد.

برگشت در tasks.py ماژول، I import را Image کلاس از PIL بسته، سپس یک کار جدید به نام اضافه کنید make_thumbnails، که مسیر فایل تصویر و لیستی از ابعاد عرض و ارتفاع 2 تایی را برای ایجاد ریز عکسها می پذیرد.


import os
from zipfile import ZipFile

from celery import shared_task
from PIL import Image

from django.conf import settings

@shared_task
def make_thumbnails(file_path, thumbnails=()):
    os.chdir(settings.IMAGES_DIR)
    path, file = os.path.split(file_path)
    file_name, ext = os.path.splitext(file)

    zip_file = f"{file_name}.zip"
    results = {'archive_path': f"{settings.MEDIA_URL}images/{zip_file}"}
    try:
        img = Image.open(file_path)
        zipper = ZipFile(zip_file, 'w')
        zipper.write(file)
        os.remove(file_path)
        for w, h in thumbnails:
            img_copy = img.copy()
            img_copy.thumbnail((w, h))
            thumbnail_file = f'{file_name}_{w}x{h}.{ext}'
            img_copy.save(thumbnail_file)
            zipper.write(thumbnail_file)
            os.remove(thumbnail_file)

        img.close()
        zipper.close()
    except IOError as e:
        print(e)

    return results

کار تصویر کوچک بالا به سادگی فایل تصویر ورودی را در یک نمونه Pillow Image بارگذاری می‌کند، سپس بر روی لیست ابعاد ارسال شده به کار حلقه می‌زند و یک تصویر کوچک برای هر کدام ایجاد می‌کند، هر تصویر کوچک را به بایگانی فشرده اضافه می‌کند و در عین حال فایل‌های میانی را نیز تمیز می‌کند. یک فرهنگ لغت ساده بازگردانده می شود که نشانی اینترنتی را مشخص می کند که بایگانی فشرده تصاویر کوچک را می توان از آن دانلود کرد.

با تعیین تکلیف کرفس حرکت می کنم روی برای ایجاد نماهای جنگو برای ارائه یک الگو با فرم آپلود فایل.

برای شروع به پروژه جنگو a MEDIA_ROOT مکانی که فایل های تصویری و بایگانی های فشرده می توانند در آن قرار گیرند (من از این در مثال بالا استفاده کردم) و همچنین مشخص کنید MEDIA_URL از جایی که می توان محتوا را ارائه کرد. در image\_parroter/settings.py ماژول را اضافه می کنم MEDIA_ROOT، MEDIA_URL، IMAGES_DIR مکان‌های تنظیمات سپس منطق ایجاد این مکان‌ها را در صورت عدم وجود آنها فراهم می‌کنند.



... skipping down to the static files section




STATIC_URL = '/static/'
MEDIA_URL = '/media/'

MEDIA_ROOT = os.path.abspath(os.path.join(BASE_DIR, 'media'))
IMAGES_DIR = os.path.join(MEDIA_ROOT, 'images')

if not os.path.exists(MEDIA_ROOT) or not os.path.exists(IMAGES_DIR):
    os.makedirs(IMAGES_DIR)

درون thumbnailer/views.py ماژول، I import را django.views.View کلاس و استفاده از آن برای ایجاد یک HomeView کلاس حاوی get و post روش ها، همانطور که در زیر نشان داده شده است.

این get متد به سادگی یک الگوی home.html را که قرار است به زودی ایجاد شود، برمی گرداند و آن را a FileUploadForm شامل یک ImageField میدان همانطور که در بالا مشاهده می شود HomeView کلاس

این post روش ساخت FileUploadForm شیء با استفاده از داده های ارسال شده در درخواست، اعتبار آن را بررسی می کند، سپس اگر معتبر باشد، فایل آپلود شده را در فایل ذخیره می کند. IMAGES_DIR و الف را شروع می کند make_thumbnails وظیفه در حالی که گرفتن کار id و وضعیت را به قالب منتقل کند، یا فرم را با خطاهایش به قالب home.html برمی گرداند.



import os

from celery import current_app

from django import forms
from django.conf import settings
from django.http import JsonResponse
from django.shortcuts import render
from django.views import View

from .tasks import make_thumbnails

class FileUploadForm(forms.Form):
    image_file = forms.ImageField(required=True)

class HomeView(View):
    def get(self, request):
        form = FileUploadForm()
        return render(request, 'thumbnailer/home.html', { 'form': form })
    
    def post(self, request):
        form = FileUploadForm(request.POST, request.FILES)
        context = {}

        if form.is_valid():
            file_path = os.path.join(settings.IMAGES_DIR, request.FILES('image_file').name)

            with open(file_path, 'wb+') as fp:
                for chunk in request.FILES('image_file'):
                    fp.write(chunk)

            task = make_thumbnails.delay(file_path, thumbnails=((128, 128)))

            context('task_id') = task.id
            context('task_status') = task.status

            return render(request, 'thumbnailer/home.html', context)

        context('form') = form

        return render(request, 'thumbnailer/home.html', context)


class TaskView(View):
    def get(self, request, task_id):
        task = current_app.AsyncResult(task_id)
        response_data = {'task_status': task.status, 'task_id': task.id}

        if task.status == 'SUCCESS':
            response_data('results') = task.get()

        return JsonResponse(response_data)

زیر HomeView کلاسی که من قرار داده ام TaskView کلاس که از طریق یک درخواست AJAX برای بررسی وضعیت استفاده می شود make_thumbnails وظیفه. در اینجا متوجه خواهید شد که من آن را وارد کرده ام current_app شی از بسته کرفس استفاده کرد و از آن برای بازیابی وظیفه استفاده کرد AsyncResult شی مرتبط با task_id از درخواست من ایجاد می کنم response_data فرهنگ لغت وضعیت و شناسه کار، سپس اگر وضعیت نشان می دهد که کار با موفقیت اجرا شده است، نتایج را با فراخوانی واکشی می کنم get() روش از AsyncResult شی اختصاص دادن آن به results کلید از response_data به عنوان JSON به درخواست کننده HTTP برگردانده شود.

قبل از اینکه بتوانم رابط کاربری قالب را بسازم، باید کلاس‌های نماهای جنگو را به چند URL معقول نگاشت کنم. من با اضافه کردن یک شروع می کنم urls.py ماژول داخل برنامه thumbnailer و URL های زیر را تعریف کنید:



from django.urls import path

from . import views

urlpatterns = (
  path('', views.HomeView.as_view(), name='home'),
  path('task/<str:task_id>/', views.TaskView.as_view(), name='task'),
)

سپس در پیکربندی URL اصلی پروژه، باید URL های سطح برنامه را اضافه کنم و همچنین آن را از URL رسانه آگاه کنم، مانند:



from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = (
    path('admin/', admin.site.urls),
    path('', include('thumbnailer.urls')),
) + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

در مرحله بعد من شروع به ایجاد یک نمای قالب ساده برای کاربر می کنم تا بتواند یک فایل تصویری ارسال کند و همچنین وضعیت فایل ارسالی را بررسی کند. make_thumbnails وظایف و دانلود ریز عکسهای حاصل را آغاز کنید. برای شروع، من باید یک دایرکتوری ایجاد کنم تا این الگوی واحد را در فهرست بندانگشتی قرار دهد، به شرح زیر:

(venv) $ mkdir -p thumbnailer/templates/thumbnailer

سپس در این دایرکتوری templates/thumbnailer یک الگو به نام home.html اضافه می کنم. Inside home.html با بارگیری برچسب های قالب “widget_tweaks” شروع می کنم، سپس حرکت می کنم روی برای تعریف HTML با وارد کردن یک چارچوب CSS به نام bulma CSSو همچنین یک کتابخانه جاوا اسکریپت به نام Axios.js. در بدنه HTML page من یک عنوان، یک مکان نگهدار برای نمایش پیام نتایج در حال پیشرفت و فرم آپلود فایل ارائه می کنم.


{% load widget_tweaks %}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Thumbnailer</title>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.min.css">
  <script src="https://cdn.jsdelivr.net/npm/vue"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
  <script defer src="https://use.fontawesome.com/releases/v5.0.7/js/all.js"></script>
</head>
<body>
  <nav class="navbar" role="navigation" aria-label="main navigation">
    <div class="navbar-brand">
      <a class="navbar-item" href="/">
        Thumbnailer
      </a>
    </div>
  </nav>
  <section class="hero is-primary is-fullheight-with-navbar">
    <div class="hero-body">
      <div class="container">
        <h1 class="title is-size-1 has-text-centered">Thumbnail Generator</h1>
        <p class="subtitle has-text-centered" id="progress-title"></p>
        <div class="columns is-centered">
          <div class="column is-8">
            <form action="{% url 'home' %}" method="POST" enctype="multipart/form-data">
              {% csrf_token %}
              <div class="file is-large has-name">
                <label class="file-label">
                  {{ form.image_file|add_class:"file-input" }}
                  <span class="file-cta">
                    <span class="file-icon"><i class="fas fa-upload"></i></span>
                    <span class="file-label">Browse image</span>
                  </span>
                  <span id="file-name" class="file-name" 
                    style="background-color: white; color: black; min-width: 450px;">
                  </span>
                </label>
                <input class="button is-link is-large" type="submit" value="Submit">
              </div>
              
            </form>
          </div>
        </div>
      </div>
    </div>
  </section>
  <script>
  var file = document.getElementById('{{form.image_file.id_for_label}}');
  file.onchange = function() {
    if(file.files.length > 0) {
      document.getElementById('file-name').innerHTML = file.files(0).name;
    }
  };
  </script>

  {% if task_id %}
  <script>
  var taskUrl = "{% url 'task' task_id=task_id %}";
  var dots = 1;
  var progressTitle = document.getElementById('progress-title');
  updateProgressTitle();
  var timer = setInterval(function() {
    updateProgressTitle();
    axios.get(taskUrl)
      .then(function(response){
        var taskStatus = response.data.task_status
        if (taskStatus === 'SUCCESS') {
          clearTimer('Check downloads for results');
          var url = window.location.protocol + '//' + window.location.host + response.data.results.archive_path;
          var a = document.createElement("a");
          a.target = '_BLANK';
          document.body.appendChild(a);
          a.style = "display: none";
          a.href = url;
          a.download = 'results.zip';
          a.click();
          document.body.removeChild(a);
        } else if (taskStatus === 'FAILURE') {
          clearTimer('An error occurred');
        }
      })
      .catch(function(err){
        console.log('err', err);
        clearTimer('An error occurred');
      });
  }, 800);

  function updateProgressTitle() {
    dots++;
    if (dots > 3) {
      dots = 1;
    }
    progressTitle.innerHTML = 'processing images ';
    for (var i = 0; i < dots; i++) {
      progressTitle.innerHTML += '.';
    }
  }
  function clearTimer(message) {
    clearInterval(timer);
    progressTitle.innerHTML = message;
  }
  </script> 
  {% endif %}
</body>
</html>

در پایین body عنصر جاوا اسکریپت را برای ارائه برخی رفتارهای اضافی اضافه کرده ام. ابتدا یک مرجع به فیلد ورودی فایل ایجاد می کنم و یک شنونده تغییر ثبت می کنم، که به سادگی نام فایل انتخاب شده را پس از انتخاب به UI اضافه می کند.

بعد قسمت مرتبط تر می آید. من از قالب منطقی جنگو استفاده می کنم if اپراتور برای بررسی وجود a task_id در حال تحویل از HomeView نمای کلاس این نشان دهنده پاسخ پس از a است make_thumbnails وظیفه ارائه شده است. سپس از جنگو استفاده می کنم url تگ الگو برای ساختن یک URL بررسی وضعیت وظیفه مناسب و شروع یک درخواست AJAX زمان بندی شده به آن URL با استفاده از Axios کتابخانه ای که قبلا ذکر کردم

اگر وضعیت یک کار به عنوان “موفقیت” گزارش شود، من یک لینک دانلود را به DOM تزریق می کنم و باعث روشن شدن آن می شوم، بارگیری را آغاز می کند و تایمر فاصله را پاک می کند. اگر وضعیت یک “شکست” باشد، من به سادگی فاصله را پاک می کنم، و اگر وضعیت نه “موفقیت” یا “شکست” باشد، تا زمانی که بازه بعدی فراخوانی نشود، کاری انجام نمی دهم.

در این مرحله می توانم یکی دیگر را باز کنم terminal، یک بار دیگر با محیط مجازی پایتون فعال، و سرور توسعه دهنده جنگو را مانند شکل زیر راه اندازی کنید:

(venv) $ python manage.py runserver
  • این redis-server و پایانه‌های وظیفه cellery که قبلا توضیح داده شد نیز باید اجرا شوند، و اگر از زمان اضافه کردن آن، Celery worker را دوباره راه‌اندازی نکرده‌اید. make_thumbnails وظیفه ای که می خواهید Ctrl+C تا کارگر متوقف شود و سپس صادر شود celery worker -A image_parroter --loglevel=info دوباره برای راه اندازی مجدد هر بار که کد مربوط به کار کرفس تغییر می کند، کارگران کرفس باید دوباره راه اندازی شوند.

اکنون می توانم نمای home.html را در مرورگر خود بارگیری کنم http://localhost:8000، یک فایل تصویری ارسال کنید و برنامه باید با a پاسخ دهد results.zip بایگانی حاوی تصویر اصلی و یک تصویر کوچک 128×128 پیکسل.

استقرار در سرور اوبونتو

برای تکمیل این مقاله، روش نصب و پیکربندی این برنامه جنگو را نشان خواهم داد که از Redis و Celery برای کارهای پس زمینه ناهمزمان استفاده می کند. روی یک سرور Ubuntu v18 LTS.

هنگامی که SSH روی سرور قرار گرفت، آن را به روز می کنم و بسته های لازم را نصب می کنم.

# apt-get update
# apt-get install python3-pip python3-dev python3-venv nginx redis-server -y

یه یوزر هم میسازم به اسم webapp، که به من یک فهرست اصلی می دهد تا پروژه جنگو را در آن نصب کنم.

# adduser webapp

پس از وارد کردن اطلاعات کاربر، من آن را اضافه می کنم webapp کاربر به sudo و www-data گروه ها، به webapp کاربر، پس cd به فهرست اصلی آن

# usermod -aG sudo webapp
# usermod -aG www-data webapp
$ su webapp
$ cd

در داخل دایرکتوری برنامه وب می توانم مخزن image_parroter GitHub را کلون کنم، cd در مخزن، یک محیط مجازی پایتون ایجاد کنید، آن را فعال کنید، سپس وابستگی ها را از آن نصب کنید requirements.txt فایل.

$ git clone https://github.com/amcquistan/image_parroter.git
$ python3 -m venv venv
$ . venv/bin/activate
(venv) $ pip install -r requirements.txt

علاوه بر الزاماتی که به تازگی نصب کردم، می‌خواهم مورد جدیدی را برای برنامه وب uWSGI اضافه کنم container که به برنامه جنگو خدمت خواهد کرد.

(venv) $ pip install uWSGI

قبل از حرکت روی هر چه بیشتر، زمان خوبی برای به روز رسانی خواهد بود settings.py برای تبدیل مقدار DEBUG به False و افزودن آدرس IP به لیست ALLOWED_HOSTS.

پس از آن، به دایرکتوری پروژه Django image_parroter (که حاوی wsgi.py ماژول) و یک فایل جدید برای نگهداری تنظیمات پیکربندی uWSGI با نام اضافه کنید uwsgi.iniو موارد زیر را در آن قرار دهید:

# uwsgi.ini
(uwsgi)
chdir=/home/webapp/image_parroter/image_parroter
module=image_parroter.wsgi:application
master=True
processes=4
harakiri=20

socket=/home/webapp/image_parroter/image_parroter/image_parroter/webapp.sock  
chmod-socket=660  
vacuum=True
logto=/var/log/uwsgi/uwsgi.log
die-روی-term=True 

قبل از اینکه فراموش کنم باید ادامه دهم و دایرکتوری ورود به سیستم را اضافه کنم و مجوزها و مالکیت مناسب را به آن بدهم.

(venv) $ sudo mkdir /var/log/uwsgi
(venv) $ sudo chown webapp:www-data /var/log/uwsgi 

بعد من یک systemd فایل سرویس برای مدیریت سرور برنامه uWSGI که ​​در آن قرار دارد /etc/systemd/system/uwsgi.service و شامل موارد زیر است:

# uwsgi.service
(Unit)
Description=uWSGI Python container server  
After=network.target

(Service)
User=webapp
Group=www-data
WorkingDirectory=/home/webapp/image_parroter/image_parroter
Environment="/home/webapp/image_parroter/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin"
ExecStart=/home/webapp/image_parroter/venv/bin/uwsgi --ini image_parroter/uwsgi.ini

(Install)
WantedBy=multi-user.target

اکنون می توانم سرویس uWSGI را راه اندازی کنم، بررسی کنم که وضعیت آن خوب است و آن را فعال کنم تا به طور خودکار در هنگام بوت شروع شود.

(venv) $ sudo systemctl start uwsgi.service
(venv) $ sudo systemctl status uwsgi.service
(venv) $ sudo systemctl enable uwsgi.service

در این مرحله برنامه جنگو و سرویس uWSGI راه اندازی می شود و من می توانم حرکت کنم روی برای پیکربندی redis-server.

من شخصا ترجیح می دهم از آن استفاده کنم systemd خدمات، بنابراین من آن را ویرایش خواهم کرد /etc/redis/redis.conf فایل پیکربندی با تنظیم supervised پارامتر برابر است systemd. بعد از آن دوباره راه اندازی می کنم redis-server، وضعیت آن را بررسی کنید و آن را فعال کنید تا در هنگام بوت شروع شود.

(venv) $ sudo systemctl restart redis-server
(venv) $ sudo systemctl status redis-server
(venv) $ sudo systemctl enable redis-server

مرحله بعدی پیکربندی کرفس است. من این را شروع می کنم process با ایجاد یک مکان ورود به سیستم برای Celery و دادن مجوزها و مالکیت مناسب به این مکان، مانند:

(venv) $ sudo mkdir /var/log/celery
(venv) $ sudo chown webapp:www-data /var/log/celery

پس از آن من یک فایل پیکربندی Celery به نام اضافه می کنم celery.conf، در همان دایرکتوری uwsgi.ini فایلی که قبلا توضیح داده شد، با قرار دادن موارد زیر در آن:

# celery.conf

CELERYD_NODES="worker1 worker2"
CELERY_BIN="/home/webapp/image_parroter/venv/bin/celery"
CELERY_APP="image_parroter"
CELERYD_MULTI="multi"
CELERYD_PID_FILE="/home/webapp/image_parroter/image_parroter/image_parroter/%n.pid"
CELERYD_LOG_FILE="/var/log/celery/%n%I.log"
CELERYD_LOG_LEVEL="INFO"

برای تکمیل پیکربندی کرفس، خودش را اضافه می کنم systemd فایل سرویس در /etc/systemd/system/celery.service و موارد زیر را در آن قرار دهید:

# celery.service
(Unit)
Description=Celery Service
After=network.target

(Service)
Type=forking
User=webapp
Group=webapp
EnvironmentFile=/home/webapp/image_parroter/image_parroter/image_parroter/celery.conf
WorkingDirectory=/home/webapp/image_parroter/image_parroter
ExecStart=/bin/sh -c '${CELERY_BIN} multi start ${CELERYD_NODES} \
  -A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} \
  --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL}'
ExecStop=/bin/sh -c '${CELERY_BIN} multi stopwait ${CELERYD_NODES} \
  --pidfile=${CELERYD_PID_FILE}'
ExecReload=/bin/sh -c '${CELERY_BIN} multi restart ${CELERYD_NODES} \
  -A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} \
  --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL}'

(Install)
WantedBy=multi-user.target

آخرین کاری که باید انجام دهید این است که nginx را پیکربندی کنید تا به عنوان یک پروکسی معکوس برای برنامه uwsgi/django کار کند و همچنین محتوا را در فهرست رسانه ها ارائه دهد. من این کار را با اضافه کردن یک پیکربندی nginx در انجام می‌دهم /etc/nginx/sites-available/image_parroter، که شامل موارد زیر است:

server {
  listen 80;
  server_name _;

  location /favicon.ico { access_log off; log_not_found off; }
  location /media/ {
    root /home/webapp/image_parroter/image_parroter;
  }

  location / {
    include uwsgi_params;
    uwsgi_pass unix:/home/webapp/image_parroter/image_parroter/image_parroter/webapp.sock;
  }
}

در مرحله بعد، پیکربندی پیش‌فرض nginx را حذف می‌کنم که به من امکان استفاده از آن را می‌دهد server_name _; برای گرفتن تمام ترافیک http روی پورت 80، سپس یک پیوند نمادین بین پیکربندی که به تازگی در فهرست “sites-available” اضافه کردم، به فهرست “sites-enabled” مجاور آن ایجاد می کنم.

$ sudo rm /etc/nginx/sites-enabled/default
$ sudo ln -s /etc/nginx/sites-available/image_parroter /etc/nginx/sites-enabled/image_parroter

پس از انجام این کار، می‌توانم nginx را مجدداً راه‌اندازی کنم، وضعیت آن را بررسی کرده و آن را فعال کنم تا در بوت شروع شود.

$ sudo systemctl restart nginx
$ sudo systemctl status nginx
$ sudo systemctl enable nginx

در این مرحله می توانم مرورگر خود را به آدرس IP این سرور اوبونتو نشان دهم و برنامه بندانگشتی را آزمایش کنم.

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

نتیجه

این مقاله دلیل استفاده و همچنین روش استفاده از کرفس را برای هدف مشترک شروع یک کار ناهمزمان که خاموش می‌شود و به صورت سریالی کامل می‌شود، توضیح داد. این منجر به بهبود قابل توجهی در تجربه کاربر می‌شود و تأثیر مسیرهای کد طولانی‌مدت را کاهش می‌دهد که سرور برنامه وب را از رسیدگی به درخواست‌های بیشتر مسدود می‌کند.

من تمام تلاشم را کرده‌ام تا توضیح مفصلی از شروع تا پایان ارائه کنم process از تنظیم یک محیط توسعه، اجرای وظایف cellery، تولید وظایف در کد برنامه جنگو، و همچنین مصرف نتایج از طریق جنگو و چند جاوا اسکریپت ساده.

ممنون که خواندید و مثل همیشه از نظر دادن یا انتقاد در زیر خجالت نکشید.

(برچسب‌ها به ترجمه)# python



منتشر شده در 1403-01-22 17:41:03

امتیاز شما به این مطلب
دیدگاه شما در خصوص مطلب چیست ؟

آدرس ایمیل شما منتشر نخواهد شد.

لطفا دیدگاه خود را با احترام به دیدگاه های دیگران و با توجه به محتوای مطلب درج کنید