Рубрики
Без рубрики

Асинхронные задачи в Django с Redis и Сельдереем

Автор оригинала: Adam McQuistan.

Асинхронные задачи в Django с Redis и Сельдереем

Вступление

В этом уроке я дам общее представление о том, почему очередь сообщений сельдерея ценна, а также о том, как использовать сельдерей в сочетании с Redis в приложении Django. Чтобы продемонстрировать специфику реализации, я создам минималистичное приложение для обработки изображений, которое генерирует миниатюры изображений, представленных пользователями.

Будут рассмотрены следующие темы:

  • Предыстория очередей сообщений с сельдереем и Редисом
  • Локальная настройка разработчиков с помощью Django, Celery и Redis
  • Создание миниатюр изображений в задаче Сельдерея
  • Развертывание на сервере Ubuntu

Код для этого примера можно найти на GitHub вместе с инструкциями по установке и настройке, если вы просто хотите сразу перейти к функционально завершенному приложению, в противном случае в оставшейся части статьи я расскажу вам, как построить все с нуля.

Предыстория очередей сообщений с сельдереем и Редисом

Celery-это программный пакет для организации очередей задач на основе Python, который позволяет выполнять асинхронные вычислительные рабочие нагрузки, управляемые информацией, содержащейся в сообщениях, создаваемых в коде приложения (в данном примере Django), предназначенном для очереди задач Celery. Сельдерей также может быть использован для выполнения повторяющихся, периодических (то есть запланированных) задач, но это не будет предметом нашей статьи.

Сельдерей лучше всего использовать в сочетании с решением для хранения данных, которое часто называют брокером сообщений. Распространенным брокером сообщений, используемым с сельдереем, является Redis, который представляет собой хранилище данных “Ключ-значение” производительности в памяти. В частности, Redis используется для хранения сообщений, создаваемых кодом приложения, описывающим работу, которую необходимо выполнить в очереди задач Сельдерея. Redis также служит хранилищем результатов, поступающих из очередей сельдерея, которые затем извлекаются потребителями очереди.

Локальная настройка разработчиков с помощью Django, Celery и Redis

Сначала я начну с самой сложной части, которая заключается в установке Redis.

Установка Redis на Windows

  1. Скачайте Redis zip-файл и распакуйте в какой-нибудь каталог
  2. Найдите файл с именем redis-server.exe и дважды щелкните, чтобы запустить сервер в командном окне
  3. Аналогично найдите другой файл с именем redis-cli.exe и дважды щелкните по нему, чтобы открыть программу в отдельном командном окне
  4. В командном окне, запускающем клиент cli, проверьте, чтобы убедиться, что клиент может общаться с сервером, выполнив команду ping , и если все идет хорошо, должен быть возвращен ответ PONG

Установка Redis на Mac OSX/Linux

  1. Загрузите файл Redis tarball и распакуйте его в какой-нибудь каталог
  2. Запустите makefile с помощью make install для сборки программы
  3. Откройте окно терминала и выполните команду redis-server
  4. В другом окне терминала запустите redis-cli
  5. В окне терминала, запускающем клиент cli, проверьте, может ли клиент разговаривать с сервером, выполнив команду ping , и если все идет хорошо, то должен быть возвращен ответ PONG

Установка Python Virtual Env и зависимостей

Теперь я могу перейти к созданию виртуальной среды Python3 и установке пакетов зависимостей, необходимых для этого проекта.

Для начала я создам каталог для размещения вещей под названием image_parrot, а затем внутри него создам свою виртуальную среду. Все команды, начиная с этого момента, будут иметь только вкус unix, но большинство, если не все, будут одинаковыми для среды Windows.

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

Теперь, когда виртуальная среда активирована, я могу установить пакеты Python.

(venv) $ pip install Django Celery redis Pillow django-widget-tweaks
(venv) $ pip freeze > requirements.txt
  • Pillow-это не связанный с сельдереем пакет Python для обработки изображений, который я буду использовать позже в этом уроке для демонстрации реального варианта использования задач сельдерея.
  • Django Widget Tweaks-это плагин Django для обеспечения гибкости в том, как визуализируются входные данные формы.

Настройка проекта Django

Двигаясь дальше, я создаю проект Django с именем image_parroter, а затем приложение Django с именем 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

Чтобы интегрировать Сельдерей в этот проект Django я добавляю новый модуль image_parroter/image_parrroter/celery.py следующие соглашения описаны в документе Celery docs . В этом новом модуле Python я импортирую пакет os и класс Celery из пакета celery.

Модуль os используется для связывания переменной среды Celery с именем DJANGO_SETTINGS_MODULE с модулем настроек проекта Django. После этого я создаю экземпляр класса Celery для создания переменной экземпляра celery_app . Затем я обновляю конфигурацию приложения Celery настройками, которые вскоре помещу в файл настроек проекта Django, идентифицируемый префиксом ‘CELERY_’. Наконец, я говорю вновь созданному экземпляру celery_app автоматически обнаруживать задачи внутри проекта.

Завершенное celery.py модуль показан ниже:

# image_parroter/image_parroter/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 модуль, в самом низу, я определяю раздел для настроек сельдерея и добавляю настройки, которые вы видите ниже. Эти настройки говорят Сельдерею использовать Redis в качестве брокера сообщений, а также где к нему подключиться. Они также говорят Сельдерею ожидать, что сообщения будут передаваться туда и обратно между очередями задач Сельдерея и брокером сообщений Redis в mime-типе приложения/json.

# image_parroter/image_parroter/settings.py

... skipping to the bottom

# celery
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'

Затем мне нужно убедиться, что ранее созданное и настроенное приложение celery будет введено в приложение Django при его запуске. Это делается путем импорта приложения Сельдерея в рамках основного проекта Django __init__.py сценарий и явная регистрация его как символа пространства имен в пакете Django “image_parroter”.

# image_parroter/image_parroter/__init__.py

from .celery import celery_app

__all__ = ('celery_app',)

Я продолжаю следовать предложенным соглашениям, добавляя новый модуль с именем tasks.py в приложении “thumbnailer”. Внутри tasks.py модуль I импортирует декоратор функций shared_tasks и использует его для определения целевой функции сельдерея с именем adding_task , как показано ниже.

# image_parroter/thumbnailer/tasks.py

from celery import shared_task

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

Наконец, мне нужно добавить приложение thumbnailer в список INSTALLED_APPS в проекте image_parroter. settings.py модуль. Пока я там, я также должен добавить приложение “widget_tweaks”, которое будет использоваться для управления рендерингом входных данных формы, которые я буду использовать позже, чтобы позволить пользователям загружать файлы.

# image_parroter/image_parroter/settings.py

... 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',
]

Теперь я могу проверить все с помощью нескольких простых команд на трех терминалах.

В одном терминале мне нужно, чтобы redis-сервер работал, вот так:

$ 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

Во втором терминале, с активным экземпляром виртуальной среды Python, установленным ранее, в корневом каталоге пакета проекта (том же самом, который содержит manage.py модуль) Я запускаю программу сельдерея.

(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

В третьем и последнем терминале, опять же с активной виртуальной средой Python, я могу запустить оболочку Django Python и протестировать свой 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(...) method-это обещающее возвращаемое значение типа celery.result.AsyncResult . Это возвращаемое значение содержит такую информацию, как идентификатор задачи, состояние ее выполнения и статус задачи, а также возможность доступа к любым результатам, полученным задачей с помощью метода .get () , как показано в примере.

Создание миниатюр изображений в задаче Сельдерея

Теперь, когда шаблонная настройка для интеграции экземпляра Redis-backend Celery в приложение Django удалена, я могу перейти к демонстрации некоторых более полезных функций с помощью ранее упомянутого приложения thumbnailer.

Вернувшись в tasks.py модуль I импортирует класс Image из пакета PIL , а затем добавляет новую задачу с именем make_thumbnails , которая принимает путь к файлу изображения и список 2-кортежных размеров ширины и высоты для создания миниатюр.

# image_parroter/thumbnailer/tasks.py
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

Описанная выше задача миниатюр просто загружает входной файл изображения в экземпляр изображения подушки, затем циклически перебирает список измерений, переданный задаче, создавая миниатюру для каждого из них, добавляя каждую миниатюру в zip-архив, а также очищая промежуточные файлы. Возвращается простой словарь с указанием URL-адреса, с которого можно загрузить zip-архив миниатюр.

С определенной задачей сельдерея я перехожу к построению представлений Django, чтобы подать шаблон с формой загрузки файла.

Для начала я даю проекту Django местоположение MEDIA_ROOT , где могут находиться файлы изображений и zip-архивы (я использовал это в приведенном выше примере задачи), а также указываю MEDIA_URL , откуда может быть подан контент. В image_parroter/settings.py модуль I добавляет местоположения MEDIA_ROOT , MEDIA_URL , IMAGES_DIR settings, а затем предоставляет логику для создания этих местоположений, если они не существуют.

# image_parroter/settings.py

... skipping down to the static files section

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/

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 модуль, я импортирую django.views.Просмотр класса и использование его для создания Home View класс, содержащий методы get и post , как показано ниже.

Метод get просто возвращает a home.html шаблон, который будет создан в ближайшее время, и вручает ему Форму загрузки файла , состоящую из ImageField поле, как видно выше класса HomeView .

Метод post создает объект File Upload Form , используя данные, отправленные в запросе, проверяет его валидность, затем, если он действителен, сохраняет загруженный файл в IMAGES_DIR и запускает задачу make_thumbnails , захватывая задачу id и статус для передачи в шаблон, или возвращает форму с ее ошибками в шаблон. home.html шаблон.

# thumbnailer/views.py

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 я поместил класс Task View , который будет использоваться с помощью AJAX-запроса для проверки состояния задачи make_thumbnails . Здесь вы заметите, что я импортировал объект current_app из пакета celery и использовал его для извлечения объекта AsyncResult задачи, связанного с task_id из запроса. Я создаю response_data словарь статуса и идентификатора задачи, а затем, если статус указывает на то, что задача успешно выполнена, я извлекаю результаты, вызывая метод get() объекта AsynchResult , назначая его ключу results объекта response_data , который будет возвращен в виде JSON HTTP-запросчику.

Прежде чем я смогу создать пользовательский интерфейс шаблона, мне нужно сопоставить вышеупомянутые классы представлений Django с некоторыми разумными URL-адресами. Я начинаю с добавления urls.py модуль внутри приложения thumbnailer и определите следующие URL-адреса:

# thumbnailer/urls.py

from django.urls import path

from . import views

urlpatterns = [
  path('', views.HomeView.as_view(), name='home'),
  path('task//', views.TaskView.as_view(), name='task'),
]

Затем в основной конфигурации URL-адреса проекта мне нужно включить URL-адреса прикладного уровня, а также сделать так, чтобы он знал URL-адрес носителя, например:

# image_parroter/urls.py

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 задач и инициировать загрузку полученных миниатюр. Для начала мне нужно создать каталог для размещения этого единственного шаблона в каталоге thumbnailer следующим образом:

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

Затем в этом каталоге templates/thumbnailer я добавляю шаблон с именем home.html. Внутри home.html Я начинаю с загрузки тегов шаблонов “widget_tweaks”, а затем перехожу к определению HTML , импортируя CSS-фреймворк под названием bulma CSS , а также библиотеку JavaScript под названием Axios.js . В теле HTML-страницы я предоставляю заголовок, заполнитель для отображения сообщения о результатах выполнения и форму загрузки файла.


{% load widget_tweaks %}




  
  
  
  Thumbnailer
  
  
  
  


  
  

Thumbnail Generator

{% csrf_token %}
{% if task_id %} {% endif %}

В нижней части элемента body я добавил JavaScript, чтобы обеспечить некоторое дополнительное поведение. Сначала я создаю ссылку на поле ввода файла и регистрирую changelistener, который просто добавляет имя выбранного файла в пользовательский интерфейс после его выбора.

Далее следует более важная часть. Я использую шаблонный логический оператор Django if для проверки наличия task_id передаваемого из Home View class view. Это указывает на ответ после отправки задачи make_thumbnails . Затем я использую тег Django url template для создания соответствующего URL-адреса проверки состояния задачи и начинаю интервальный AJAX-запрос к этому URL-адресу с помощью библиотеки Axios, о которой я упоминал ранее.

Если состояние задачи сообщается как "УСПЕХ", я вводю ссылку для загрузки в DOM и вызываю ее срабатывание, запускаю загрузку и очищаю интервальный таймер. Если состояние является "НЕУДАЧЕЙ", я просто очищаю интервал, а если состояние не является ни "УСПЕХОМ", ни "НЕУДАЧЕЙ", то я ничего не делаю до тех пор, пока не будет вызван следующий интервал.

В этот момент я могу открыть еще один терминал, снова с активной виртуальной средой Python, и запустить сервер Django dev, как показано ниже:

(venv) $ python manage.py runserver
    Т е р м и н а л ы r e d i s - s e r v e r и c e l e r y t a s k , о п и с а н н ы е р а н е е , т а к ж е д о л ж н ы б ы т ь з а п у щ е н ы , и е с л и в ы н е п е р е з а п у с т и л и C e l e r y w o r k e r с м о м е н т а д о б а в л е н и я з а д а ч и | | m a k e _ t h u m b n a i l s | | , в а м н у ж н о б у д е т | | C t r l + C | | о с т а н о в и т ь w o r k e r , а з а т е м с н о в а з а п у с т и т ь | | c e l e r y w o r k e r - A i m a g e _ p a r r o t e r | | , ч т о б ы п е р е з а п у с т и т ь е г о . Р а б о т н и к и с е л ь д е р е я д о л ж н ы б ы т ь п е р е з а п у щ е н ы к а ж д ы й р а з , к о г д а п р о и с х о д и т и з м е н е н и е к о д а , с в я з а н н о г о с з а д а ч е й с е л ь д е р е я .

Теперь я могу загрузить home.html просмотр в моем браузере по адресу http://localhost:8000 , отправьте файл изображения, и приложение должно ответить results.zip архив, содержащий исходное изображение и миниатюру размером 128х128 пикселей.

Развертывание на сервере Ubuntu

Чтобы завершить эту статью, я продемонстрирую, как установить и настроить это приложение Django, которое использует Redis и Celery для асинхронных фоновых задач на сервере Ubuntu v18 LTS.

После того, как SSH'd на сервер, я обновляю его, а затем устанавливаю необходимые пакеты.

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

Я также создаю пользователя с именем "webapp", который дает мне домашний каталог для установки проекта Django.

# adduser webapp

После ввода пользовательских данных я добавляю пользователя webapp в студию и группы www-данных, переключаюсь на пользователя webapp, а затем cd в его домашний каталог.

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

Внутри каталога веб-приложений я могу клонировать репо image_parrot GitHub, cd в репо, создать виртуальную среду Python, активировать ее, а затем установить зависимости из нее. 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, который будет обслуживать приложение Django.

(venv) $ pip install uWSGI

Прежде чем двигаться дальше, было бы неплохо обновить settings.py файл, чтобы перевернуть значение ОТЛАДКИ на False и добавить IP-адрес в список ALLOWED_HOSTS .

После этого перейдите в каталог проекта Django image_parrot (тот, который содержит 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-on-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

На этом этапе приложение Django и сервис uwsgi настроены, и я могу перейти к настройке radius-сервера.

Лично я предпочитаю использовать сервисы systemd, поэтому отредактирую файл /etc/redis/redis.conf config, установив параметр supervised равным systemd . После этого я перезапускаю redis-сервер, проверяю его состояние и включаю запуск при загрузке.

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

Далее нужно настроить сельдерей. Я начинаю этот процесс с создания места регистрации для Сельдерея и даю этому месту соответствующие разрешения и права собственности, например:

(venv) $ sudo mkdir /var/log/celery
(venv) $ sudo chown webapp:www-data /var/log/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, а также обслуживать контент в каталоге media. Я делаю это , добавляя конфигурацию nginx в /etc/nginx/sites-available/image_parrot , которая содержит следующее:

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, а затем создаю символическую ссылку между конфигурацией, которую я только что добавил в каталог "сайты-доступны", и каталогом "сайты-включены", расположенным рядом с ним.

$ 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-адрес этого сервера Ubuntu и протестировать приложение thumbnailer.

Вывод

В этой статье описано, почему и как использовать Сельдерей для общей цели запуска асинхронной задачи, которая запускается и выполняется последовательно до завершения. Это приведет к значительному улучшению пользовательского опыта, уменьшая влияние длительных путей кода, которые блокируют сервер веб-приложений от обработки дальнейших запросов.

Я сделал все возможное, чтобы дать подробное объяснение процесса от начала до конца от создания среды разработки, реализации задач сельдерея, создания задач в коде приложения Django, а также использования результатов с помощью Django и некоторых простых JavaScript.

Спасибо за чтение и, как всегда, не стесняйтесь комментировать или критиковать ниже.