В Django, как вызвать подпроцесс с медленным временем запуска
-
07-07-2019 - |
Вопрос
Предположим, вы запускаете Django в Linux, и у вас есть представление, и вы хотите, чтобы это представление возвращало данные из подпроцесса, вызываемого cmd который работает с файлом, созданным представлением, например, likeso:
def call_subprocess(request):
response = HttpResponse()
with tempfile.NamedTemporaryFile("W") as f:
f.write(request.GET['data']) # i.e. some data
# cmd operates on fname and returns output
p = subprocess.Popen(["cmd", f.name],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
out, err = p.communicate()
response.write(p.out) # would be text/plain...
return response
Теперь, предположим cmd имеет очень медленное время запуска, но очень быстрое время работы, и изначально у него нет режима демона.Я хотел бы улучшить время отклика этого представления.
Я хотел бы, чтобы вся система работала намного быстрее, запустив несколько экземпляров cmd в рабочем пуле пусть они ожидают ввода, и, имея вызов_процесса попросите один из этих процессов рабочего пула обрабатывать данные.
Это действительно 2 части:
Часть 1.Функция , которая вызывает cmd и cmd ожидает ввода.Это можно было бы сделать с помощью труб, т. е.
def _run_subcmd():
p = subprocess.Popen(["cmd", fname],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = p.communicate()
# write 'out' to a tmp file
o = open("out.txt", "W")
o.write(out)
o.close()
p.close()
exit()
def _run_cmd(data):
f = tempfile.NamedTemporaryFile("W")
pipe = os.mkfifo(f.name)
if os.fork() == 0:
_run_subcmd(fname)
else:
f.write(data)
r = open("out.txt", "r")
out = r.read()
# read 'out' from a tmp file
return out
def call_process(request):
response = HttpResponse()
out = _run_cmd(request.GET['data'])
response.write(out) # would be text/plain...
return response
Часть 2.Набор рабочих, работающих в фоновом режиме, которые ожидают получения данных.т. е.Мы хотим расширить вышесказанное, чтобы подпроцесс уже был запущен, напримеркогда экземпляр Django инициализируется, или это вызов_процесса при первом вызове создается набор этих рабочих
WORKER_COUNT = 6
WORKERS = []
class Worker(object):
def __init__(index):
self.tmp_file = tempfile.NamedTemporaryFile("W") # get a tmp file name
os.mkfifo(self.tmp_file.name)
self.p = subprocess.Popen(["cmd", self.tmp_file],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
self.index = index
def run(out_filename, data):
WORKERS[self.index] = Null # qua-mutex??
self.tmp_file.write(data)
if (os.fork() == 0): # does the child have access to self.p??
out, err = self.p.communicate()
o = open(out_filename, "w")
o.write(out)
exit()
self.p.close()
self.o.close()
self.tmp_file.close()
WORKERS[self.index] = Worker(index) # replace this one
return out_file
@classmethod
def get_worker() # get the next worker
# ... static, incrementing index
Где-то должна быть какая-то инициализация workers, например, так:
def init_workers(): # create WORKERS_COUNT workers
for i in xrange(0, WORKERS_COUNT):
tmp_file = tempfile.NamedTemporaryFile()
WORKERS.push(Worker(i))
Теперь то, что у меня есть выше, становится чем-то вроде:
def _run_cmd(data):
Worker.get_worker() # this needs to be atomic & lock worker at Worker.index
fifo = open(tempfile.NamedTemporaryFile("r")) # this stores output of cmd
Worker.run(fifo.name, data)
# please ignore the fact that everything will be
# appended to out.txt ... these will be tmp files, too, but named elsewhere.
out = fifo.read()
# read 'out' from a tmp file
return out
def call_process(request):
response = HttpResponse()
out = _run_cmd(request.GET['data'])
response.write(out) # would be text/plain...
return response
Теперь, вопросы:
Сработает ли это?(Я только что набрал это в StackOverflow, так что я уверен, что есть проблемы, но концептуально это сработает)
На какие проблемы следует обратить внимание?
Есть ли лучшие альтернативы этому?например ,Могут ли потоки работать так же хорошо (это Debian Lenny Linux)?Существуют ли какие-либо библиотеки, которые обрабатывают рабочие пулы параллельных процессов, подобные этому?
Существуют ли взаимодействия с Django, о которых я должен осознавать?
Спасибо за чтение!Я надеюсь, что вы находите эту проблему такой же интересной, как и я.
Брайан
Решение
Может показаться, что я отказываюсь от этого продукта, поскольку это уже второй раз, когда я отвечаю с подобной рекомендацией.
Но, похоже, вам нужна служба очереди сообщений, в частности распределенная очередь сообщений.
вот как это будет работать:
- Ваше приложение Django запрашивает CMD
- CMD добавляется в очередь
- CMD получает доступ к нескольким работам
- Он выполняется, и результаты возвращаются вверх по течению
Большая часть этого кода уже существует, и вам не нужно заниматься созданием своей собственной системы.
Взгляните на Celery, который изначально был создан с помощью Django.
http://www.celeryq.org/ http://robertpogorzelski.com/blog/2009/09/10/rabbitmq-celery-and-django/
Другие советы
Исси уже упоминал Сельдерей, но поскольку комментарии плохо работают с примерами кода я отвечу вместо этого.
Вам следует попробовать использовать Celery синхронно с хранилищем результатов AMQP.Вы могли бы распространить фактическое выполнение на другой процесс или даже другую машину.Выполнять синхронное выполнение в сельдерее легко, например:
>>> from celery.task import Task
>>> from celery.registry import tasks
>>> class MyTask(Task):
...
... def run(self, x, y):
... return x * y
>>> tasks.register(MyTask)
>>> async_result = MyTask.delay(2, 2)
>>> retval = async_result.get() # Now synchronous
>>> retval 4
Хранилище результатов AMQP позволяет очень быстро отправлять результат обратно, но оно доступно только в текущей версии разработки (в code-freeze, чтобы стать 0.8.0)
Как насчет "демонизации" вызова подпроцесса с помощью python-демон или его преемник, поседевший.