Django长期运行线程/处理的异步任务

时间:2011-11-09 17:23:48

标签: django asynchronous mod-wsgi

免责声明:我知道在SO上有几个类似的问题。我想我读的大多数,如果不是全部,但没有找到我真实问题的答案(见后文)。 我也知道使用celery或其他异步队列系统是实现长时间运行任务的最佳方式 - 或者至少使用cron管理的脚本。还有mod_wsgi doc about processes and threads,但我不确定我是否正确。

问题是:

使用此处列出的解决方案涉及哪些确切的风险/问题?它们中的任何一个都适用于长时间运行的任务(好吧,即使芹菜更适合)? 我的问题更多是关于理解wsgi和python / django的内部结构,而不是找到最好的整体解决方案。阻塞线程,对变量的不安全访问,僵尸处理等问题

让我们说:

  1. 我的“long_process”正在做一些非常安全的事情。即使它失败了我也不在乎。
  2. python> = 2.6
  3. 我在守护进程模式下使用带有apache的mod_wsgi(用uwsgi或gunicorn会有什么变化吗?)
  4. mod_wsgi conf:

    WSGIDaemonProcess NAME user=www-data group=www-data threads=25
    WSGIScriptAlias / /path/to/wsgi.py
    WSGIProcessGroup %{ENV:VHOST}
    

    我认为这些是可用于启动单独的进程(广义上意味着)的选项,可以在快速返回对用户的响应的同时执行长时间运行的任务:

    os.fork

    import os
    
    if os.fork()==0:
        long_process()
    else:
        return HttpResponse()
    

    import subprocess
    
    p = subprocess.Popen([sys.executable, '/path/to/script.py'], 
                                        stdout=subprocess.PIPE, 
                                        stderr=subprocess.STDOUT)
    

    (脚本可能是manage.py命令)

    线程

    import threading
    
    t = threading.Thread(target=long_process,
                                 args=args,
                                 kwargs=kwargs)
    t.setDaemon(True)
    t.start()
    return HttpResponse()
    

    NB。

      

    由于Global Interpreter Lock,在CPython中只有一个线程可以同时执行Python代码(即使某些面向性能的库可能会克服此限制)。如果您希望应用程序更好地使用多核计算机的计算资源,建议您使用多处理。但是,如果要同时运行多个I / O绑定任务,则线程仍然是一个合适的模型。

    主线程将快速返回(httpresponse)。产生的长线程是否会阻止wsgi为其他请求做其他事情?!

    from multiprocessing import Process
    
    p = Process(target=_bulk_action,args=(action,objs))
    p.start()
    return HttpResponse()
    

    这应该解决线程并发问题,不应该吗?


    所以这些是我能想到的选择。什么会起作用,什么不行,为什么?

3 个答案:

答案 0 :(得分:26)

<强> os.fork

fork将克隆父进程,在本例中,它是您的Django堆栈。由于你只是想运行一个单独的python脚本,这似乎是不必要的臃肿。

<强>子

预计使用subprocess是互动的。换句话说,虽然你可以使用它来有效地生成一个进程,但是预计在某些时候你会在完成后终止它。如果你让一个人继续运行,Python可能会为你清理,但我猜这会导致内存泄漏。

<强>线程

线程是定义的逻辑单元。它们在调用run()方法时启动,并在run()方法执行结束时终止。这使得它们非常适合创建将在当前范围之外运行的逻辑分支。但是,如您所述,它们受Global Interpreter Lock的约束。

<强>多

这基本上是关于类固醇的线程。它具有线程的优点,但不受全局解释器锁的约束,并且可以利用多核架构。但是,结果使它们变得更加复杂。

因此,您的选择实际上归结为线程或多处理。如果你可以使用一个线程,它对你的应用程序有意义,那就去一个线程吧。否则,请使用多处理。

答案 1 :(得分:11)

我发现如果你需要在后台运行一些长任务,使用uWSGI Decorators 相当比使用Celery更简单。 认为Celery是严重繁重项目的最佳解决方案,而且这是做简单事情的开销。

要开始使用uWSGI Decorators,您只需要使用

更新uWSGI配置
<spooler-processes>1</spooler-processes>
<spooler>/here/the/path/to/dir</spooler>

编写如下代码:

@spoolraw
def long_task(arguments):
    try:
        doing something with arguments['myarg'])
    except Exception as e:
        ...something...
    return uwsgi.SPOOL_OK

def myView(request)
    long_task.spool({'myarg': str(someVar)})
    return render_to_response('done.html')

在uWSGI日志中启动视图时出现:

[spooler] written 208 bytes to file /here/the/path/to/dir/uwsgi_spoolfile_on_hostname_31139_2_0_1359694428_441414

任务结束时:

[spooler /here/the/path/to/dir pid: 31138] done with task uwsgi_spoolfile_on_hostname_31139_2_0_1359694428_441414 after 78 seconds

有一些奇怪的(对我来说)限制:

    - spool can receive as argument only dictionary of strings, look like because it's serialize in file as strings.
    - spool should be created on start up so "spooled" code it should be contained in separate file which should be defined in uWSGI config as <import>pyFileWithSpooledCode</import>

答案 2 :(得分:3)

问题:

  

产生的长线程是否会阻止wsgi执行其他操作   另一个请求?!

答案是否定的。

您仍然需要小心从请求中创建后台线程,以防您只是创建大量的后台线程并阻塞整个过程。即使你正在处理过程中,你真的需要一个任务排队系统。

关于从Web进程执行fork或exec,特别是来自Apache,这通常不是一个好主意,因为Apache可能会对创建的子进程的环境施加奇怪的条件,这可能在技术上干扰其操作。

使用像Celery这样的系统仍然是最好的解决方案。