我现在需要解决一个类似的问题。这不是一个公共网站,而是一个内部流量较低的服务器。
技术限制:
- 所有输入到长时间运行的进程的数据都可以在其启动时提供
- 长时间运行的进程不需要用户交互(除了开始进程的初始输入)
- 计算时间足够长,以至于结果无法在立即的HTTP响应中返回给客户端
- 需要某种反馈(类似进度条)来自长时间运行的进程。
因此,我们至少需要两个web“视图”:一个用于启动长时间运行的进程,另一个用于监视其状态/收集结果。
我们还需要某种进程间通信:将用户数据从发起者(http请求上的web服务器)发送到长时间运行的进程,然后将其结果发送到接收者(再次通过http请求驱动的web服务器)。前者很容易,后者则不太明显。与正常的unix编程不同,接收者最初未知。接收者可能是与初始化程序不同的进程,并且它可能在长时间运行的任务仍在进行或已经完成时启动。因此,管道不起作用,我们需要某种长时间运行进程结果的永久性。
我看到了两种可能的解决方案:
- 将长时间运行的进程发起请求传递给长时间运行的作业管理器(这可能就是上述django-queue-service所做的);
- 将结果永久保存在文件或DB中。
我更喜欢使用临时文件并记住它们在会话数据中的位置。我认为这不能变得更简单。
一个作业脚本(这是长时间运行的进程),myjob.py
:
import sys
from time import sleep
i = 0
while i < 1000:
print 'myjob:', i
i=i+1
sleep(0.1)
sys.stdout.flush()
Django中的urls.py
映射:
urlpatterns = patterns('',
(r'^startjob/$', 'mysite.myapp.views.startjob'),
(r'^showjob/$', 'mysite.myapp.views.showjob'),
(r'^rmjob/$', 'mysite.myapp.views.rmjob'),
)
Django视图:
from tempfile import mkstemp
from os import fdopen,unlink,kill
from subprocess import Popen
import signal
def startjob(request):
"""Start a new long running process unless already started."""
if not request.session.has_key('job'):
outfd,outname=mkstemp()
request.session['jobfile']=outname
outfile=fdopen(outfd,'a+')
proc=Popen("python myjob.py",shell=True,stdout=outfile)
request.session['job']=proc.pid
return HttpResponse('A <a href="/showjob/">new job</a> has started.')
def showjob(request):
"""Show the last result of the running job."""
if not request.session.has_key('job'):
return HttpResponse('Not running a job.'+\
'<a href="/startjob/">Start a new one?</a>')
else:
filename=request.session['jobfile']
results=open(filename)
lines=results.readlines()
try:
return HttpResponse(lines[-1]+\
'<p><a href="/rmjob/">Terminate?</a>')
except:
return HttpResponse('No results yet.'+\
'<p><a href="/rmjob/">Terminate?</a>')
return response
def rmjob(request):
"""Terminate the runining job."""
if request.session.has_key('job'):
job=request.session['job']
filename=request.session['jobfile']
try:
kill(job,signal.SIGKILL)
unlink(filename)
except OSError, e:
pass
del request.session['job']
del request.session['jobfile']
return HttpResponseRedirect('/startjob/')