想象一下龙卷风应用:
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/
龙卷风阻止,直到WSGI请求完成(此后一秒睡眠)。这件事我知道。但是如果你一个接一个地发出许多请求,那么IOLoop可能会永远阻止。
我尝试了这样的基准:
$ ab -n 20 -c 2 localhost:8888
在第二个终端,我试图获得另一个网址:
$ curl http://localhost:8888/tornado
在所有其他并发WSGI请求完成之前,我得到了非WSGI请求的响应。这仅在删除yield gen.moment
时有效。
有人可以解释这里发生了什么,以及如何阻止Tornado阻止我的所有请求,而不仅仅是阻止其中一个请求?
答案 0 :(得分:1)
Tornado' WSGIContainer
并非专为高流量使用而设计。请参阅its docs中的警告:
WSGI是一个同步接口,而Tornado的并发模型基于单线程异步执行。这意味着使用Tornado的WSGIContainer运行WSGI应用程序的可伸缩性低于在多线程WSGI服务器(如gunicorn或uwsgi)中运行相同的应用程序。仅当在同一进程中将Tornado和WSGI组合在一起的好处超过降低的可伸缩性时,才使用WSGIContainer。
通常,最好在单独的进程中运行WSGI和Tornado应用程序,以便WSGI部分可以拥有专为WSGI设计的服务器。 WSGIContainer
只应在有特定原因将它们组合到同一进程中时使用,然后应谨慎使用以避免阻塞事件循环太长时间。在Tornado原生的RequestHandler中尽可能做到最好,这样就可以使用协程而不是阻塞。