Зроби сам веб-сервіс з асинхронними чергами і паралельним виконанням

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

Все!
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.