为什么 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 中做尽可能多的事情,这样可以使用协程而不是阻塞。
想象一下以下龙卷风应用程序:
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 中做尽可能多的事情,这样可以使用协程而不是阻塞。