8

我继承了一个 django+fastcgi 应用程序,需要对其进行修改以执行冗长的计算(最多半小时或更长时间)。我想要做的是在后台运行计算并返回“你的工作已经开始”类型的响应。在进程运行时,对 url 的进一步点击应返回“您的作业仍在运行”,直到作业完成,此时应返回作业的结果。对 url 的任何后续命中都应返回缓存的结果。

我是 django 的新手,十年来没有做过任何重要的网络工作,所以我不知道是否有内置的方法来做我想做的事。我已经尝试通过 subprocess.Popen() 启动进程,除了它在进程表中留下一个失效条目之外,它工作正常。我需要一个干净的解决方案,可以在完成后删除临时文件和进程的任何痕迹。

我还尝试过 fork() 和线程,但尚未提出可行的解决方案。对于我看来是一个非常常见的用例,是否有规范的解决方案?FWIW 这只会在流量非常低的内部服务器上使用。

4

2 回答 2

4

我现在必须解决类似的问题。它不会是一个公共站点,而是类似地,一个低流量的内部服务器。

技术限制:

  • 长期运行过程的所有输入数据都可以在其开始时提供
  • 长时间运行的进程不需要用户交互(启动进程的初始输入除外)
  • 计算时间足够长,以至于结果无法通过即时 HTTP 响应提供给客户端
  • 需要来自长时间运行过程的某种反馈(某种进度条)。

因此,我们至少需要两个网络“视图”:一个用于启动长期运行的进程,另一个用于监控其状态/收集结果。

我们还需要某种进程间通信:将用户数据从发起方(基于 http 请求的 Web 服务器)发送到长时间运行的进程,然后将其结果发送到接收方(同样是 Web 服务器,由 http 请求驱动)。前者容易,后者不那么明显。与普通的 Unix 编程不同,接收者最初是未知的。接收者可能是与发起者不同的进程,它可能会在长时间运行的作业仍在进行中或已经完成时开始。所以管道不起作用,我们需要一些长期运行过程的结果。

我看到两种可能的解决方案:

  • 将长时间运行的进程的启动分派给长时间运行的作业管理器(这可能是上述 django-queue-service 的内容);
  • 将结果永久保存在文件或数据库中。

我更喜欢使用临时文件并记住它们在会话数据中的位置。我不认为它可以变得更简单。

作业脚本(这是长期运行的过程)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()

djangourls.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'):
          # create a temporary file to save the resuls
          outfd,outname=mkstemp()
          request.session['jobfile']=outname
          outfile=fdopen(outfd,'a+')
          proc=Popen("python myjob.py",shell=True,stdout=outfile)
          # remember pid to terminate the job later
          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) # unix only
               unlink(filename)
          except OSError, e:
               pass # probably the job has finished already
          del request.session['job']
          del request.session['jobfile']
     return HttpResponseRedirect('/startjob/') # start a new one
于 2008-12-29T15:51:23.920 回答
3

也许你可以反过来看问题。

也许你可以试试DjangoQueueService,让一个“守护进程”监听队列,看看是否有新的东西并处理它。

于 2008-10-20T18:13:38.067 回答