为什么 Tornado 的 WSGI 支持阻塞多个请求?

Why does Tornado's WSGI support block for more than one request?

想象一下以下龙卷风应用程序:

import logging
import time

from tornado import gen, httpserver, ioloop, web, wsgi


def simple_app(environ, start_response):
    time.sleep(1)
    status = "200 OK"
    response_headers = [("Content-type", "text/plain")]
    start_response(status, response_headers)
    return [b"Hello, WSGI world!\n"]

class HelloHandler(web.RequestHandler):
    @gen.coroutine
    def get(self):
        yield gen.moment
        self.write('Hello from tornado\n')
        self.finish()

def main():
    wsgi_app = wsgi.WSGIContainer(simple_app)
    tornado_app = web.Application(
        [
            ('/tornado', HelloHandler),
            ('.*', web.FallbackHandler, dict(fallback=wsgi_app)),
        ],
        debug=True,
    )
    http_server = httpserver.HTTPServer(tornado_app)
    http_server.listen(8888)
    current_loop = ioloop.IOLoop.current()
    current_loop.start()


if __name__ == '__main__':
    main()

现在,如果你 运行 这个并尝试获取 http://localhost:8888/ tornado 块,直到 WSGI 请求完成(这里是在一秒钟睡眠之后)。这件事我知道。但是,如果您一个接一个地发送许多请求,那么 IOLoop 可能会永远阻塞。

我试过这样的基准测试:

$ ab -n 20 -c 2 localhost:8888

在第二个终端我试图得到另一个 url:

$ curl http://localhost:8888/tornado

直到所有其他并发 WSGI 请求完成后,我才收到非 WSGI 请求的响应。这仅在删除 yield gen.moment 时有效。

任何人都可以解释这里发生了什么,以及如何防止 Tornado 阻止我的所有请求,而不仅仅是其中一个?

Tornado 的 WSGIContainer 不是为高流量使用而设计的。请参阅 its docs 中的警告:

WSGI is a synchronous interface, while Tornado’s concurrency model is based on single-threaded asynchronous execution. This means that running a WSGI app with Tornado’s WSGIContainer is less scalable than running the same app in a multi-threaded WSGI server like gunicorn or uwsgi. Use WSGIContainer only when there are benefits to combining Tornado and WSGI in the same process that outweigh the reduced scalability.

一般来说,最好运行 WSGI 和 Tornado 应用程序在不同的进程中,这样 WSGI 部分就可以有一个专为 WSGI 设计的服务器。 WSGIContainer 只应在有特定原因将它们组合在同一进程中时才使用,然后应谨慎使用,以免阻塞事件循环时间过长。最好在 Tornado 原生 RequestHandlers 中做尽可能多的事情,这样可以使用协程而不是阻塞。