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

时间:2017-10-23 20:13:48

标签: 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(request):
    my_model = MyModel()
    my_model.foo()

然而,在我使用Tornado服务器包装它之后,它不再能够处理并发请求。当我点击我的网站向这个call_foo()函数发送async get请求时,似乎我的应用程序无法处理其他请求。例如,如果我打开主页url,它会一直等待并且不会显示,直到foo()中的上述子进程调用结束。

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

以下是启动龙卷风服务器的代码。有什么我做错了吗?

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()

1 个答案:

答案 0 :(得分:1)

您的设置没有任何问题。这是设计的。

因此, WSGI 协议(以及 Django )使用同步模型。这意味着当您的应用程序开始处理请求时,它会获得控制权并仅在请求完成时将其返回。这就是它可以立即处理单个请求的原因。要允许同时发出请求,通常会在多线程或多进程模式下启动 wsgi 应用程序。

另一方的 Tornado 服务器使用异步模型。这里的想法是拥有自己的调度程序而不是与线程和进程一起使用的OS调度程序。因此,您的代码运行一些逻辑,然后启动一些长任务(数据库调用,URL获取),设置任务完成时运行的内容并将控制权交还给调度程序。

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

This answer解释了sync / async详细信息。它专注于客户,但我认为你可以看到这个想法。

您的代码有什么问题:Popen无法控制IOLoop。在你的子进程完成之前,Python什么都不做,因此无法处理其他请求,甚至不能处理Django的请求。 runserver“在这里工作”,因为它是多线程的。因此,在完全锁定线程时,其他线程仍然可以处理请求。

出于这个原因,通常不推荐 async 服务器下运行 WSGI 应用程序,如龙卷风。该文档声称它将可伸缩性,但您可以在自己的代码上看到问题。因此,如果您需要两个服务器(例如Tornado for socket和Django for main site),我建议在 nginx 后面运行,并使用 uwsgi gunicorn 运行 Django 。或者查看django-channels应用,而不是龙卷风

此外,虽然它适用于测试环境,但我认为这并不是你想要实现的目标。很难建议解决方案,因为我不知道你用Popen调用了什么,但它接缝是长时间运行的。也许你应该看看Celery项目。这是一个运行长期后台工作的软件包。

然而,回到运行子流程。在 Tornado 中,您可以使用tornado.process.Subprocess。它是Popen的包装器,允许它与IOLoop一起使用。不幸的是,我不知道你是否可以在 tornado 下的 wsgi 部分使用它。我记得有一些项目,比如django futures,但似乎已经放弃了。

作为另一个快速而又脏的修复 - 您可以使用多个进程运行 Tornado 。检查this example如何分叉服务器。但是无论如何我都不会在生产中重新使用它(fork是正常的,运行wsgi后备不是)。

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

  1. 在一些后台队列中运行Popen调用,如Celery
  2. 使用Tornado处理此类视图,并使用tornado.processes模块运行子进程。
  3. 总的来说,我会寻求另一个部署基础架构,并且不会在龙卷风下运行Durango。