Сделай сам веб-сервис с асинхронными очередями и параллельным исполнением

  • Tutorial

rq Каждый должен делать свою работу качественно и в срок. Допустим, вам нужно сделать веб-сервис классификации картинок на базе обученной нейронной сети с помощью библиотеки caffe. В наши дни качество — это асинхронные неблокирующие вызовы, возможность параллельного исполнения нескольких заданий при наличии свободных процессорных ядер, мониторинг очередей заданий… Библиотека RQ позволяет реализовать все это в сжатые сроки без изучения тонны документации.


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




Постановка задачи


Входными данными является файл (например, картинка в формате JPEG). Для простоты считаем, что ее уже разместили в выделенную директорию. Выходными данными является строка в формате JSON. Для солидности будем пользоваться стандартными кодами результатов HTTP.


Веб-сервис будет реализовывать два HTTP-вызова (назовём это API):


  • /process/[имя файла] — обработать файл (предварительно загружаем входной файл в выделенную директорию, возвращаем идентификатор задания)
  • /result/[идентификатор задания] — получить результат (если результат не готов, возвращаем код 202 «Not ready», если результат готов — возвращаем json, если идентификатор задания не существует, возвращаем код 404 «Not found»)

Инсталлируем компоненты в Ubuntu


В качестве HTTP-сервера будем использовать Flask. Установка:


Если не установлен pip:


sudo apt-get install python-pip
sudo apt-get install --upgrade pip

Собственно, установка Flask:


pip install flask

Теперь нужно установить Redis — хранилище данных и брокер сообщений:


wget http://download.redis.io/redis-stable.tar.gz
tar xvzf redis-stable.tar.gz
cd redis-stable
make
sudo make install

Установка библиотеки RQ (Redis Queue):


pip install rq

Для автоматического запуска и конфигурации всех компонентов будем использовать Supervisor:


sudo apt-get install supervisor

Пишем наш сервис


Это легко в Flask. Создадим файл deep_service.py:


#Путь к директории, выделенной под хранение входных файлов
BASEDIR = '/home/sergey/verysecure'

#Импортируем служебные библиотеки
import argparse
import os
import json

#Импортируем только что установленные компоненты нашего сервиса
from flask import Flask
app = Flask(__name__)
from redis import Redis
from rq import Queue

#Импортируем функцию, реализующую наш вычислительный процесс, который будет выполняться асинхронно
#classify.py - модуль, поставляемый с библиотекой машинного обучения caffe
#Естественно, вместо него можно импортировать собственные разработки
from classify import main

#Подключаемся к базе данных Redis
q = Queue(connection=Redis(), default_timeout=3600)

#Реализуем первый вызов нашего API

@app.route('/process/<path:file_path>')
def process(file_path):
    full_path = os.path.join(BASEDIR, file_path)    #входной файл лежит в директории BASEDIR
    argv = {'input_file': full_path,
                'gpu': True}
    args = argparse.Namespace(**argv)
    r = q.enqueue_call(main, args=(args,), result_ttl=86400)
    return r.id

#В порядке обмена опытом: ограничим 4-мя цифрами после запятой вещественные числа,
#при сериализации в JSON из массива numpy
def decimal_default(obj):
    if isinstance(obj, float32):
        return round(float(obj), 4)
    else:
        raise TypeError()

#Реализуем второй вызов нашего API

@app.route('/result/<id>')
def result(id):
    try:    
        job = q.fetch_job(id)
        if job.is_finished:
            return json.dumps(job.result, ensure_ascii=False, default=decimal_default)
        else:
            return 'Not ready', 202
    except:
        return "Not found", 404

if __name__ == '__main__':
    app.run()
    #app.run(debug=False, host='0.0.0.0')

Запуск вручную — проверяем как работает


На этом этапе можно проверить, работает ли наш веб-сервис. Запускаем Redis:


redis-server

Запускаем один рабочий процесс (их можно запускать несколько, например по количеству процессорных ядер или по наличию нескольких видеокарт, если они требуются для обработки данных). Процесс лучше запускать из той директории, в которой будут запускаться вычислительные функции, в нашем случае это там, где лежит classif.py:


rq worker

Запускаем http-сервер:


python deep_service.py

Записываем в директорию для входных данных картинку cat.jpg и выполняем запрос к сервису:


wget 127.0.0.1/process/cat.jpg

В ответ получаем идентификатор задания. Копируем идентификатор и выполняем второй запрос к сервису:


wget 127.0.0.1/result/[идентификатор]

В ответ получаем строку JSON с весовыми коэффициентами принадлежности картинки категориям IMAGENET.
Теперь осталось сконфигурировать автоматический запуск компонентов нашего сервера.


Автозапуск


Настройка supervisor — возможно, самая сложная часть этого пути. Хороший обучающий материал по настройке supervisor тут.


Прежде всего нужно понять, что supervisor запускает каждый процесс в собственном окружении. В большинстве случаев сложных вычислений программа, их реализующая, зависит от ряда настроек, например путей. Эти настройки обычно хранятся в файле /home/usersname/.bashrc


Например, библиотека нейросетевых вычислений caffe и питоновские модули к ней потребовали дописать в этот файл следующие строки:


export PATH=/usr/local/cuda-7.5/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/cuda-7.5/lib64:$LD_LIBRARY_PATH
PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig
export PKG_CONFIG_PATH
LD_LIBRARY_PATH=/home/username/caffe/build/lib:$LD_LIBRARY_PATH
export PYTHONPATH="${PYTHONPATH}:/home/username/caffe/python"

Скопируйте эти строки в буфер обмена!


В директории /usr/local/bin создайте файл deep_worker.sh


#!/bin/bash
cd /home/username/caffe/python

export PATH=/usr/local/cuda-7.5/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/cuda-7.5/lib64:$LD_LIBRARY_PATH
PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig
export PKG_CONFIG_PATH
LD_LIBRARY_PATH=/home/username/caffe/build/lib:$LD_LIBRARY_PATH
export PYTHONPATH="${PYTHONPATH}:/home/username/caffe/python"

rq worker

Ну вы поняли — в первой строчке мы переходим в рабочую директорию, затем вставляем переменные окружения, скопированные из .bashrc, затем запускаем процесс.


В директории /usr/local/bin создайте файл deep_flask.sh


#!/bin/bash
cd /home/username/caffe/python

export PATH=/usr/local/cuda-7.5/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/cuda-7.5/lib64:$LD_LIBRARY_PATH
PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig
export PKG_CONFIG_PATH
LD_LIBRARY_PATH=/home/username/caffe/build/lib:$LD_LIBRARY_PATH
export PYTHONPATH="${PYTHONPATH}:/home/username/caffe/python"

python deep_service.py

Опять — в первой строчке мы переходим в рабочую директорию, затем вставляем переменные окружения, скопированные из .bashrc, затем запускаем наш Flask-сервер.


Немного системного администрирования:


sudo chmod +x /usr/local/bin/deep_flask.sh
sudo chmod +x /usr/local/bin/deep_worker.sh
mkdir /var/log/deepservice

В директории /etc/supervisor/conf.d создайте файл deepservice.conf:


[program:redis]
command=/usr/local/bin/redis-server
autostart=true
autorestart=true
stderr_logfile=/var/log/deepservice/redis.err.log
stdout_logfile=/var/log/deepservice/redis.out.log

[program:worker1]
command=/usr/local/bin/deep_worker.sh
autostart=true
autorestart=true
stderr_logfile=/var/log/deepservice/worker1.err.log
stdout_logfile=/var/log/deepservice/worker1.out.log
user=username
directory=/home/username/caffe/python

[program:flask]
command=/usr/local/bin/deep_flask.sh
autostart=true
autorestart=true
stderr_logfile=/var/log/deepservice/flask.err.log
stdout_logfile=/var/log/deepservice/flask.out.log
user=username
directory=/home/username/caffe/python

Наконец, запустим всю эту конструкцию:


sudo supervisorctl reread
sudo supervisorctl update

Всё!

Поделиться публикацией

Комментарии 20

    0
    А каково оно в сравнении, скажем, с Celery?
      +1
      Celery больше функций, но она сложнее. Rq позволяет с минимальными затратами времени реализовать рабочее решение, Здесь очень хорошо описаны Про и Контра celery vs. rq, перевожу кратко:
      • Документация RQ полная и простая. Документация Celery сложнее для восприятия, хотя тоже подробная.
      • Средства мониторинга Celery и RQ легко устанавливаются и оба хороши.
      • Поддержка брокеров очередей: у RQ только Redis, Celery выигрывает (Redis, RabbitMQ). Redis, а значит RQ не гарантирует доставку 100% сообщений.
      • Приоритетные очереди. Подходы разные, но оба работают.
      • Операционные системы. RQ только unix-подобные (fork), Celery побеждает.
      • Языки программирования. RQ только Python, Celery позволяет слать сообщения из одного языка другому (сорри).
      • API. Celery гибче, RQ проще.
      • Celery поддерживает подзадания, RQ — неизвестно (мне тоже неизвестно).
      • Сообщество. Celery активнее, но оба проекта вполне активны.

      У всех нас ограничено место в голове, поэтому если организация асинхронных очередей заданий для вас не основная работа, то RQ позволяет легко и просто построить качественное решение, за которое не будет стыдно. Celery конечно круче.
        –4
        Статья любопытная, не знал про RQ. За статью — 5!) Но надо было остановиться после краткого сравнения.

        Вот не надо опять про какие то там мистические ограничения в голове и особенно про «у всех нас».

        Ограничения в голове только у всех вас, а за всех нас говорить нехорошо. Была бы карма, влепил бы комменту минус((
        –3
        > sudo pip install flask

        Дальше не читал.
        • НЛО прилетело и опубликовало эту надпись здесь
            –1
            А как же бывшая?
            • НЛО прилетело и опубликовало эту надпись здесь
              • НЛО прилетело и опубликовало эту надпись здесь
                  0
                  Це по мови як мене в дитячому садочку клыкалы
                  • НЛО прилетело и опубликовало эту надпись здесь
            –1
            helloworld.py
              +1
              Для большей гибкости деплоя проекта, лучше изпользовать virtual env, а не ставить пакеты глобально.
              • НЛО прилетело и опубликовало эту надпись здесь
                  +1

                  При чём здесь классификация картинок? Это отдельная тема. Дядя же ясно написал:


                  Естественно, его применение не ограничивается этими вашими нейронными сетями.

                  Смысл статьи в построении системы выполнения сравнительно длительных заданий. Вместо классификации картинок сервис может их ресайзить, например.

                  • НЛО прилетело и опубликовало эту надпись здесь
                  0
                  Это тянет на enterprise или все такие вариант прототипирования?
                  Формально такой функционал можно стандартными http.server и threading уложить строк в 30.
                    0
                    Enterprise конечно звучит гордо применительно к open-source но это конечно более устойчивое решение чем http.server и threading.
                    Пространство HTTPD полностью отделено от потоков «тяжелых» задач, есть средства контроля приоритета и мониторинга для админов.
                    0
                    Скорее всего мне нужен так называемый data orchestration framework, но всё равно спрошу.

                    Есть ли возможность подключить несколько серверов разных конфигураций?
                    Есть возможность задавать разные типы тасков и разные типы воркеров?
                    Есть ли возможность запускать задачи которые описаны через directed acyclic graph? В этой связи больше интересует запуск с места в графе где задача зафейлилась.
                    Как это всё дебажить и мониторить(хотелось бы что то типа дашборда с метриками и т.д.)?

                      0

                      RQ работает на одном сервере (насколько мне известно). Вашему списку требований удовлетворяет, например, Akka.

                        0
                        Похоже на что то низкоуровневое, мне скорее нужен готовый тул который можно просто под себя настроить, или я не правильно понял, нет ли какого то простого примера?

                        А так мы пробовали https://github.com/apache/incubator-airflow на том же celery, но как то это всё сложно и непонятно как дебажить.

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

                    Самое читаемое