Асинхронные задачи в Django с Redis и Celery
Введение
В этой статье я расскажу о очередях сообщений celery и о том как использовать celery в сочетании с Redis в приложении Django. Чтобы продемонстрировать особенности реализации, создадим небольшое приложение для обработки изображений, которое генерирует эскизы изображений из изображений представленных пользователями.
В статье будут рассмотрены следующие темы:
- Справочная информация об очередях сообщений (Message Queues) с Celery и Redis
- Локальная настройка Django, Celery и Redis
- Создание миниатюр изображений в задаче Celery
- Развертывание приложение на сервере Ubuntu
Код для этого примера можно найти на GitHub вместе с инструкциями по установке и настройке, если вы просто хотите сразу перейти к функционально завершенному приложению, в противном случае в оставшейся части статьи я расскажу, как собрать все с нуля.
Справочная информация об очередях сообщений (Message Queues) с Celery и Redis
Celery — это программный пакет для организации очередей задач на основе Python, который позволяет выполнять асинхронные задачи, основанные на информации, содержащейся в сообщениях, которые генерируются в коде приложения (в нашем примере Django), предназначенном для очереди задач Celery. Celery также может быть использован для выполнения повторяемых, периодических (то есть запланированных) задач, но это мы не будем рассматривать в этой статье.
Celery лучше всего использовать в сочетании с решением для хранения сообщений, которое часто называют брокером сообщений. Распространенным посредником сообщений, который используется с celery, является Redis, который является быстродействующим хранилищем данных по типу ключ-значение. Redis также служит хранилищем результатов, поступающих из очередей celery, которые затем извлекаются потребителями очереди.
Локальная настройка с Django, Celery и Redis
Сначала начнем с самой сложной части — установки Redis.
Установка Redis в Windows
- Скачайте Redis zip файл и распакуйте в какой-нибудь каталог
- Найдите файл с именем redis-server.exe и дважды щелкните, чтобы запустить сервер в командном окне.
- Аналогичным образом найдите другой файл с именем redis-cli.exe и дважды щелкните его, чтобы открыть программу в отдельном командном окне.
- В командном окне, в котором запущен клиент Cli, проверьте, чтобы клиент мог общаться с сервером, введя команду ping, и, если все пойдет хорошо, ответ PONG должен быть возвращен.
Установка Redis на Mac OSX / Linux
- Загрузите файл архива Redis и распакуйте его в какой-нибудь каталог
- Запустите команду make install, чтобы собрать программу
- Откройте окно терминала и выполните команду redis-server.
- В другом окне терминала запустите redis-cli
- В окне терминала, в котором работает клиент Cli, проверьте, чтобы клиент мог общаться с сервером, выполнив команду ping, и, если все пойдет хорошо, ответ PONG должен быть возвращен.
Установите Python Virtual Env и зависимости
Теперь перейдем к созданию виртуальной среды Python3 и установке пакетов зависимостей, необходимых для этого проекта.
Для начала создадим каталог проекта с именем image_parroter, а затем внутри него создадим свою виртуальную среду. Все дальнейшие команды будут только в 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 — это не связанный с celery пакет Python для обработки изображений, который будет использовать позже в этом уроке для демонстрации использования задач celery.
- 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
Чтобы интегрировать Celery в проект Django, добавим новый файл imageparroter/imageparrroter/celery.py в соответствии с соглашениями, описанными в документации Celery. Внесите в него следующий код:
# 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()
В этом новом файле Python импортируется пакет os и класс Celery из пакета celery. Модуль os используется для связывания переменной среды Celery под названием DJANGO_SETTINGS_MODULE с модулем настроек проекта Django. После этого создается экземпляр класса Celery для создания переменной celery_app. Затем обновляется конфигурацию приложения Celery настройками, которые вскоре добавим в файл настроек проекта Django, идентифицируемые префиксом CELERY_. Наконец, недавно созданный экземпляр celery_app запускается для автоматического обнаружения задач в проекте.
Теперь в модуле settings.py проекта, в самом низу, определим раздел для настроек celery и добавим настройки, которые вы видите ниже. Эти настройки говорят Celery использовать Redis в качестве посредника сообщений, а также как к нему подключиться. Они также говорят Celery ожидать, что сообщения будут передаваться между очередями задач Celery и брокером сообщений Redis в mime-типе application/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 при его запуске. Это можно сделать, импортировав приложение Celery в основной скрипт __init__.py проекта Django и явно зарегистрировав его как символ пространства имен в пакете Django «image_parroter».
# image_parroter/image_parroter/__init__.py from .celery import celery_app __all__ = ('celery_app',)
Продолжим следовать предложенным соглашениям, добавляя новый модуль tasks.py в приложение thumbnailer. Внутри модуля tasks.py импортируется функция-декоратор shared_tasks и используется для определения функции задачи celery с именем add_task, как показано ниже.
# image_parroter/thumbnailer/tasks.py from celery import shared_task @shared_task def adding_task(x, y): return x + y
Наконец, нужно добавить приложение thumbnailer в список INSTALLED_APPS в модуле settings.py проекта image_parroter. Также добавим приложение 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), запустить программу celery.
(venv) $ celery worker -A image_parroter --loglevel=info -------------- celery@Adams-MacBook-Pro-191.local 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 и протестировать add_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 (…) в объекте add_task. Это распространенный способ передачи любых необходимых параметров объекту задачи, с которым он работает, а также инициирования отправки его посреднику сообщений и очереди задач. Результатом вызова метода .delay (…) является возвращаемое значение promise типа celery.result.AsyncResult. Это возвращаемое значение содержит такую информацию, как идентификатор задачи, ее состояние выполнения и состояние задачи, а также возможность доступа к любым результатам, полученным задачей, с помощью метода .get(), как показано в примере.
Создание миниатюр изображений в задаче Celery
Теперь перейдем к созданию некоторых более полезных функций в приложением thumbnailer.
Вернувшись в модуль tasks.py, импортируем класс Image из пакета PIL, затем добавим новую задачу под названием make_thumbnails, которая принимает путь к файлу изображения и список из двух измерений ширины и высоты для создания миниатюр.
# 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
Вышеуказанная задача make_thumbnails просто загружает файл входного изображения в экземпляр Pillow Image, затем перебирает список измерений, переданный в задачу, создавая миниатюру для каждого, добавляя каждый эскиз в zip-архив, а также очищает промежуточные файлы. Возвращается простой словарь с указанием URL-адреса, с которого можно скачать zip-архив миниатюр.
Далее перейдем к созданию представлений (views) Django для предоставления шаблона с формой загрузки файла.
Для начала укажем в проекте Django местоположение MEDIA_ROOT, в котором будут находиться файлы изображений и zip-архивы, а также укажим MEDIA_URL, откуда может быть скачен контент. В модуле image_parroter/settings.py добавим местоположения настроек MEDIA_ROOT, MEDIA_URL, IMAGES_DIR и затем определим команды для создания этих каталогов, если они не существуют.
# 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.View который используется для создания класса HomeView, содержащего методы get и post, как показано ниже.
Метод get просто возвращает шаблон home.html, который вскоре будет создан, и передает ему FileUploadForm, состоящий из поля ImageField.
Метод post создает объект FileUploadForm, используя данные, отправленные в запросе, проверяет его достоверность, затем, если он действителен (то есть верный), сохраняет загруженный файл в IMAGES_DIR и запускает задачу make_thumbnails, одновременно захватывая идентификатор и статус задачи для передачи в шаблон, или возвращает форму с ошибками в шаблон 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 размещен класс TaskView, который будет использоваться через запрос 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/<str:task_id>/', views.TaskView.as_view(), name='task'), ]
Затем в конфигурации основного URL-адреса проекта включим URL-адреса уровня приложения thumbnailer.urls, а также сообщить ему 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-страницы опишем заголовок, заполнитель для отображения сообщения о результатах и форму загрузки файла.
<!-- templates/thumbnailer/home.html --> {% 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 я добавил JavaScript, чтобы обеспечить дополнительное поведение. В нем сначала создается ссылка на поле ввода файла и регистрируется прослушиватель (listener) изменений, который просто добавляет имя выбранного файла в UI после его выбора.
Далее идет более актуальная часть. Я использовал шаблонный оператор if для проверки наличия task_id, передаваемого из представления класса HomeView. Он указывает на ответ после того, как задача make_thumbnails была отправлена. Затем я использовал тег шаблона url, чтобы создать соответствующий URL-адрес для проверки состояния задачи и начало интервального AJAX-запроса к этому URL-адресу с помощью библиотеки Axios, о которой я упоминал ранее.
Если состояние задачи возвращается как «SUCCESS», вставляется ссылка на загрузку в DOM и затем она запускается, вызывая загрузку и сбрасывая интервальный таймер. Если статус «FAILURE», просто очищается интервал, а если статус не «SUCCESS» или «FAILURE», то ничего не делается, пока не будет вызван следующий интервал.
На этом этапе откройте еще один терминал, еще раз с активной виртуальной средой Python, и запустите сервер разработки Django, как показано ниже:
(venv) $ python manage.py runserver
- Терминалы задач redis-server и celery, описанные ранее, также должны быть запущены, и если вы не перезапустили worker Celery с момента добавления задачи make_thumbnails, вам нужно нажать Ctrl + C, чтобы остановить worker, а затем выполнить celery worker -A image_parroter —loglevel=info снова, чтобы перезапустить его. Worker celery должны быть перезапущены каждый раз, когда производится изменение кода, связанного с задачей celery.
Теперь можно загрузить представление (views) home.html в своем браузере по адресу http://localhost:8000, и отправить файл изображения, а приложение должно ответить архивом results.zip, содержащим исходное изображение и уменьшенное изображение размером 128×128 пикселей.
Развертывание на сервере Ubuntu
В завершение этой статьи рассмотрим, как установить и настроить это наше приложение на сервере Ubuntu v18 LTS.
После того, как подключимся через SSH к серверу, обновим его и затем устанавим все необходимые пакеты.
# apt-get update # apt-get install python3-pip python3-dev python3-venv nginx redis-server -y
Также создадим пользователя с именем webapp, у которого будет свой домашний каталог для установки проекта Django.
# adduser webapp
Затем добавим пользователя webapp в группы sudo и www-data . После переключимся на пользователя webapp и зайдем в его домашний каталог.
# usermod -aG sudo webapp # usermod -aG www-data webapp $ su webapp $ cd
Внутри каталога клонируем репозиторий GitHub image_parroter, затем создадим виртуальную среду 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
В дополнение к только что установленным requirements добавим еще одну библиотеку, uwsgi (контейнер веб-приложений ), который будет обслуживать приложение Django.
(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-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 настроены, и можно перейти к настройке сервера redis.
Я лично предпочитаю использовать службы systemd, поэтому отредактируем файл /etc/redis/redis.conf, установим параметр supervised, равный systemd. После этого перезагрузим redis-сервер, проверяя его состояние и включим его в автозагрузку.
(venv) $ sudo systemctl restart redis-server (venv) $ sudo systemctl status redis-server (venv) $ sudo systemctl enable redis-server
Далее следует настроить celery. Начнем этот процесс с создания каталога логов для 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"
Чтобы завершить настройку celery, добавим собственный файл службы 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_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-адрес вашего сервера Ubuntu и протестировать приложение thumbnailer.
Заключение
В этой статье описывается, зачем и как использовать Celery для запуска асинхронной задачи. Это делается для значительного улучшения взаимодействия с пользователем, и уменьшая влияние блокирующего кода веб-приложения во время обработки дальнейших запросов.
Я приложил все усилия, чтобы предоставить подробное объяснение процесса начала до конца, начиная с настройки среды разработки, реализации задач celery, создания задач в коде приложения Django, а также получения результатов с помощью Django и некоторого простого JavaScript.
Оригинальная статья: Adam McQuistan Asynchronous Tasks in Django with Redis and Celery
Толково! Спасибо!!!!
Кто объяснит какую роль здесь играет Redis?
Если упрощенно Redis это простая и быстрая база данных для хранения временных данных.
выполняю все в точности по инструкции, но когда дохожу до delay в shell, этот метод не отрабатывает. т.е. буквально программа замирает. без него работает. В чем может быть причина?
Стоит обратить внимание на логи celery. По идее, когда все в порядке, в консоли вываливается сообщение, что «такая-то задача была обработана за столько-то секунд». Еще, как вариант, стоит перепроверить точно ли поднялся redis и на правильном ли порту, т.к. «замирание» программы может говорить о бесконечном ожидании ответа от чего-то(например от redisa). Но это все догадки. По идее все должно быть в консоли celery, так что вам туда.
Тоже была такая проблема. Решилась добавлением опции —pool=solo в строчку запуска celery под windows. Полная строка запуска у меня выглядела так — celery -A image_parroter worker —loglevel=info —pool=solo.
где набрать команду make install, когда скачиваешь redis?