Pull to refresh

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

Reading time 5 min
Views 28K

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

Всё!

Tags:
Hubs:
+19
Comments 20
Comments Comments 20

Articles