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:


sudo pip install flask

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


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

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


sudo 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

Всё!

Поделиться с друзьями
-->

Комментарии (17)


  1. AndersonDunai
    05.08.2016 02:11

    А каково оно в сравнении, скажем, с Celery?


  1. sergeypid
    05.08.2016 06:10
    +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 конечно круче.


    1. Toshiro
      05.08.2016 09:49
      -4

      Статья любопытная, не знал про RQ. За статью — 5!) Но надо было остановиться после краткого сравнения.

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

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


  1. kolyaflash
    05.08.2016 10:01
    -3

    > sudo pip install flask

    Дальше не читал.


    1. yamatoko
      05.08.2016 13:03
      -1

      > sudo pip install flask
      А я дальше пошел гулять с пацанами.


      1. sergeypid
        05.08.2016 14:47
        -1

        А как же бывшая?


        1. yamatoko
          05.08.2016 16:53
          -1

          А с ней все хорошо.


        1. yamatoko
          05.08.2016 16:58
          -1

          Серега, а почему ты Пид.?


          1. sergeypid
            08.08.2016 14:46

            Це по мови як мене в дитячому садочку клыкалы


            1. yamatoko
              11.08.2016 06:36

              Ого как ты долго вспоминал.


  1. bestfriend
    05.08.2016 11:04
    -1

    helloworld.py


  1. zoonim116
    05.08.2016 12:39
    +1

    Для большей гибкости деплоя проекта, лучше изпользовать virtual env, а не ставить пакеты глобально.


  1. yamatoko
    05.08.2016 13:05

    вы бы написали, что он вообще делает? то есть, как он именно классифицирует картинки.


    1. Realmixer
      05.08.2016 22:23
      +1

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


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

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


      1. yamatoko
        06.08.2016 09:45

        а при том, что «вам нужно сделать веб-сервис классификации картинок на базе обученной нейронной сети „


  1. r66qq3Ek
    05.08.2016 22:23

    Это тянет на enterprise или все такие вариант прототипирования?
    Формально такой функционал можно стандартными http.server и threading уложить строк в 30.


    1. sergeypid
      08.08.2016 15:02

      Enterprise конечно звучит гордо применительно к open-source но это конечно более устойчивое решение чем http.server и threading.
      Пространство HTTPD полностью отделено от потоков «тяжелых» задач, есть средства контроля приоритета и мониторинга для админов.