از طریق منوی جستجو مطلب مورد نظر خود در وبلاگ را به سرعت پیدا کنید
وظایف ناهمزمان در جنگو با ردیس و کرفس
سرفصلهای مطلب
معرفی
در این آموزش من یک درک کلی از اینکه چرا صف پیام های کرفس ارزشمند هستند همراه با روش استفاده از کرفس در ارتباط با Redis در یک برنامه جنگو ارائه خواهم کرد. برای نشان دادن ویژگیهای پیادهسازی، یک برنامه پردازش تصویر مینیمالیستی میسازم که تصاویر کوچکی از تصاویر ارسال شده توسط کاربران را تولید میکند.
موضوعات زیر پوشش داده خواهد شد:
کد این مثال را می توان یافت روی GitHub به همراه دستورالعملهای نصب و راهاندازی اگر میخواهید مستقیماً به یک برنامه کاربردی کامل بروید، در غیر این صورت در ادامه مقاله شما را با روش ساخت همه چیز از ابتدا آشنا خواهم کرد.
زمینه روی صف های پیام با Celery و Redis
Celery یک بسته نرمافزاری برای صفبندی وظایف مبتنی بر پایتون است که اجرای بارهای کاری محاسباتی ناهمزمان را ممکن میسازد که توسط اطلاعات موجود در پیامهایی که در کد برنامه تولید میشوند (جانگو در این مثال) که برای صف کار Celery تعیین شدهاند، هدایت میشوند. کرفس همچنین می تواند برای اجرای وظایف تکرار شونده (یعنی برنامه ریزی شده) استفاده شود، اما تمرکز این مقاله نخواهد بود.
کرفس به بهترین وجه همراه با محلول ذخیره سازی که اغلب به عنوان واسطه پیام از آن یاد می شود استفاده می شود. یک کارگزار پیام رایج که برای کرفس استفاده میشود Redis است که یک ذخیرهسازی اطلاعات کلیدی-مقدار عملکردی در حافظه است. به طور خاص، Redis برای ذخیره پیامهای تولید شده توسط کد برنامه استفاده میشود که کار را در صف وظایف Celery توصیف میکند. Redis همچنین بهعنوان ذخیرهسازی نتایجی که از صفهای کرفس خارج میشوند و سپس توسط مصرفکنندگان صف بازیابی میشوند، عمل میکند.
Local Dev Setup با Django، Celery و Redis
من ابتدا با سخت ترین قسمت که نصب Redis است شروع می کنم.
نصب Redis روی پنجره ها
- Redis را دانلود کنید فایل فشرده و از حالت فشرده خارج کنید
- فایل نامگذاری شده را پیدا کنید
redis-server.exe
و دوبار کلیک کنید تا سرور در یک پنجره فرمان راه اندازی شود - به طور مشابه، فایل دیگری به نام redis-cli.exe را پیدا کنید و روی آن دوبار کلیک کنید تا برنامه در یک پنجره دستور جداگانه باز شود.
- در پنجره فرمانی که کلاینت cli را اجرا می کند، آزمایش کنید تا مطمئن شوید که مشتری می تواند با صدور فرمان با سرور صحبت کند
ping
و اگر همه چیز خوب پیش برود یک پاسخ ازPONG
باید برگردانده شود
نصب Redis روی Mac OSX / Linux
- Redis را دانلود کنید فایل تربال و آن را در یک دایرکتوری استخراج کنید
- فایل را با
make install
برای ساختن برنامه - a را باز کنید terminal پنجره و اجرا کنید
redis-server
فرمان - در دیگری terminal اجرای پنجره
redis-cli
- در داخل 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