Python Tornado服务器导致Django无法处理并发请求

Python Tornado服务器导致Django无法处理并发请求,python,django,web,concurrency,tornado,Python,Django,Web,Concurrency,Tornado,我编写了一个Django网站,只要运行python manage.py runserver,它就可以很好地处理并发数据库请求和子流程调用 这是我的模型 class MyModel: ... def foo(self): args = [......] pipe = subprocess.Popen(args, stdout=subproccess.PIPE, stderr=subprocess.PIPE) 我认为: def call_foo(re

我编写了一个Django网站,只要运行python manage.py runserver,它就可以很好地处理并发数据库请求和子流程调用

这是我的模型

class MyModel:
    ...
    def foo(self):
        args = [......]
        pipe = subprocess.Popen(args, stdout=subproccess.PIPE, stderr=subprocess.PIPE)
我认为:

def call_foo(request):
    my_model = MyModel()
    my_model.foo()
然而,在我使用Tornado服务器包装它之后,它就不再能够处理并发请求了。当我点击我的网站,它向这个call\u foo函数发送异步get请求时,我的应用程序似乎无法处理其他请求。例如,如果我打开主页url,它会一直等待,直到foo中的上述子流程调用完成后才会显示

如果我不使用龙卷风,一切正常

下面是我启动tornado服务器的代码。有什么是我做错的吗

MAX_WAIT_SECONDS_BEFORE_SHUTDOWN = 5

def sig_handler(sig, frame):
    logging.warning('Caught signal: %s', sig)
    tornado.ioloop.IOLoop.instance().add_callback(force_shutdown)

def force_shutdown():
    logging.info("Stopping tornado server")
    server.stop()
    logging.info('Will shutdown in %s seconds ...', MAX_WAIT_SECONDS_BEFORE_SHUTDOWN)
    io_loop = tornado.ioloop.IOLoop.instance()
    deadline = time.time() + MAX_WAIT_SECONDS_BEFORE_SHUTDOWN

    def stop_loop():
        now = time.time()
        if now < deadline and (io_loop._callbacks or io_loop._timeouts):
            io_loop.add_timeout(now + 1, stop_loop)
        else:
            io_loop.stop()
            logging.info('Force Shutdown')
    stop_loop()

def main():
    parse_command_line()
    logging.info("starting tornado web server")
    os.environ['DJANGO_SETTINGS_MODULE'] = 'mydjango.settings'
    django.setup()
    wsgi_app = tornado.wsgi.WSGIContainer(django.core.handlers.wsgi.WSGIHandler())
    tornado_app = tornado.web.Application([
        (r'/(favicon\.ico)', tornado.web.StaticFileHandler, {'path': "static"}),
        (r'/static/(.*)', tornado.web.StaticFileHandler, {'path': "static"}),
        ('.*', tornado.web.FallbackHandler, dict(fallback=wsgi_app)),
      ])
    global server
    server = tornado.httpserver.HTTPServer(tornado_app)
    server.listen(options.port)

    signal.signal(signal.SIGTERM, sig_handler)
    signal.signal(signal.SIGINT, sig_handler)

    tornado.ioloop.IOLoop.instance().start()

    logging.info("Exit...")

if __name__ == '__main__':
    main()

你的设置没有问题。这是故意的

所以,WSGI协议和Django使用同步模型。这意味着,当你的应用程序开始处理一个请求时,它会控制并在请求完成后返回。这就是为什么它可以一次处理单个请求。为了允许同时请求,通常以多线程或多处理模式启动wsgi应用程序

另一端的Tornado服务器使用异步模型。这里的想法是拥有自己的调度器,而不是与线程和进程一起工作的OS调度器。所以,您的代码运行一些逻辑,然后启动一些长任务DB调用,URL fetch,设置任务完成时要运行的内容,并将控制权交还给调度器

将控制权交还给调度器是关键的一部分,它允许异步服务器快速工作,因为它可以在上一个服务器等待数据时开始处理新请求

详细解释同步/异步。它关注的是客户,但我认为你可以看到这个想法

那么,您的代码有什么问题:Popen没有将控制权交给IOLoop。在子流程完成之前,Python什么都不做,因此无法处理其他请求,即使是Django的请求。runserver在这里工作,因为它是多线程的。因此,在完全锁定线程的同时,其他线程仍然可以处理请求

因此,通常不建议在异步服务器(如tornado)下运行WSGI应用程序。文档声称它的可伸缩性较差,但您可以在自己的代码中看到问题。因此,如果您需要两台服务器,例如Tornado用于sockets,Django用于主站点,我建议在nginx之后运行这两台服务器,并使用uwsgi或gunicorn来运行Django。或者看看django频道应用程序,而不是tornado

此外,虽然它可以在测试环境中工作,但我想这并不是一种推荐的实现您试图实现的目标的方法。很难提出解决方案,因为我不知道你怎么称呼Popen,但这似乎是一个长期运行的东西。也许你们应该看看这个项目。这是一个运行长期后台工作的包

但是,返回到运行子进程。在龙卷风中你可以使用。它是一个覆盖Popen的包装器,允许它与IOLoop一起工作。不幸的是,我不知道你们是否可以在tornado下的wsgi部分使用它。我记得有一些项目,比如,但似乎被放弃了

作为另一个快速而肮脏的解决方案,您可以通过几个进程运行Tornado。检查如何使用fork服务器。但我不建议在生产中使用它,反正fork还可以,运行wsgi fallback则不行

总之,我将重写您的代码以执行以下操作之一:

在后台队列中运行Popen调用,如芹菜 使用Tornado处理此类视图,并使用Tornado.Processs模块运行子流程。
总的来说,我会寻求另一种部署基础设施,不会在龙卷风下运行Durango。

谢谢@lgor!解释很清楚。您对我可以使用哪台服务器来与Django配合使用有什么建议吗?我需要popen的原因是我有一些遗留的东西,我必须调用子流程来运行。我将使用它来运行Django应用程序,可能是在Nginx后面。您可以运行一定数量的workers线程x进程,并希望这样的Popen块是正常的,它们是正常的,但它将处理较小的负载,较大的用户量可能会使服务器卡住。如果你需要更高的负荷-检查芹菜。您将运行一个任务,立即获取任务id,并且可以将id返回到客户端浏览器。浏览器将在任务准备就绪并可以加载任务结果时进行轮询。但您将需要更新django视图和模板以进行一些ajax轮询