Tornado循环引擎

可用版本自 uWSGI 1.9.19-dev

支持挂起引擎: greenlet

支持CPython版本: 所有支持tornado的版本

tornado循环引擎允许你将你的uWSGI栈与Tornado IOLoop类集成。

基本上,服务器的每一个I/O操作会被映射到一个tornado IOLoop回调。进行RPC、远程缓存或者简单的写入响应则由Tornado引擎管理。

由于uWSGI不是用一个基于回调的编程方法写的,因此与那种类型的库集成需要某些类型“挂起”引擎 (绿色线程/协程)

目前唯一支持的挂起引擎是”greenlet”。Stackless python也能用 (需要测试)。

当前并不支持PyPy (尽管因为continulet,技术上是可行的)。如果你感兴趣的话,给Unbit员工发邮件吧。

为什么?

Tornado项目自己包含了一个简单的WSGI服务器。在于Gevent插件相同的思想下,Loop引擎的目的是允许外部项目使用(尽情使用)uWSGI api,从而获得更好的性能、多功能性和 (也许是最重要的) 资源使用。

在你的tornado应用中,可以使用所有的uWSGI子系统 (从缓存,到websockets,到度量),而WSGI引擎则是其中一个久经考验的uWSGI。

安装

当前默认不内置tornado插件。要在单个二进制文件中同时拥有tornado和greenlet,你可以这样

  1. UWSGI_EMBED_PLUGINS=tornado,greenlet pip install tornado greenlet uwsgi

或者 (来自uWSGI源代码,如果你已经安装了tornado和greenlet的话)

  1. UWSGI_EMBED_PLUGINS=tornado,greenlet make

运行之

—tornado 选项是由tornado插件公开的,允许你设置最佳参数:

  1. uwsgi --http-socket :9090 --wsgi-file myapp.py --tornado 100 --greenlet

这将会在http端口9090上运行一个uWSGI实例,使用tornado作为I/O(和时间)管理,greenlet作为挂起引擎

会分配100个异步核心,允许你管理多达100个并发请求

集成WSGI和tornado api

出于WSGI的工作方式,处理基于回调的编程是相当难的 (如果有可能的话)。

有了greenlet,我们可以挂起我们的WSGI可调用的执行,直到一个tornado IOLoop事件可用:

  1. from tornado.httpclient import AsyncHTTPClient
  2. import greenlet
  3. import functools
  4.  
  5. # this gives us access to the main IOLoop (the same used by uWSGI)
  6. from tornado.ioloop import IOLoop
  7. io_loop = IOLoop.instance()
  8.  
  9. # this is called at the end of the external HTTP request
  10. def handle_request(me, response):
  11. if response.error:
  12. print("Error:", response.error)
  13. else:
  14. me.result = response.body
  15. # back to the WSGI callable
  16. me.switch()
  17.  
  18. def application(e, sr):
  19. me = greenlet.getcurrent()
  20. http_client = AsyncHTTPClient()
  21. http_client.fetch("http://localhost:9191/services", functools.partial(handle_request, me))
  22. # suspend the execution until an IOLoop event is available
  23. me.parent.switch()
  24. sr('200 OK', [('Content-Type','text/plain')])
  25. return me.result

欢迎来到回调地狱

一如既往,判断编程方法并非uWSGI的工作。它是为系统管理员提供的工具,而系统管理员应该宽容开发者的选择。

使用这个方法,你将很快体验到的事情之一是回调地狱。

让我们扩展前面的例子,在发送响应回客户端之前等待10秒

  1. from tornado.httpclient import AsyncHTTPClient
  2. import greenlet
  3. import functools
  4.  
  5. # this gives us access to the main IOLoop (the same used by uWSGI)
  6. from tornado.ioloop import IOLoop
  7. io_loop = IOLoop.instance()
  8.  
  9. def sleeper(me):
  10. #TIMED OUT
  11. # finally come back to WSGI callable
  12. me.switch()
  13.  
  14. # this is called at the end of the external HTTP request
  15. def handle_request(me, response):
  16. if response.error:
  17. print("Error:", response.error)
  18. else:
  19. me.result = response.body
  20. # add another callback in the chain
  21. me.timeout = io_loop.add_timeout(time.time() + 10, functools.partial(sleeper, me))
  22.  
  23. def application(e, sr):
  24. me = greenlet.getcurrent()
  25. http_client = AsyncHTTPClient()
  26. http_client.fetch("http://localhost:9191/services", functools.partial(handle_request, me))
  27. # suspend the execution until an IOLoop event is available
  28. me.parent.switch()
  29. # unregister the timer
  30. io_loop.remove_timeout(me.timeout)
  31. sr('200 OK', [('Content-Type','text/plain')])
  32. return me.result

这里,我们链接了两个回调,最后一个负责将控制权交还WSGI可调用

代码可能看起来丑或者过于复杂 (与其他诸如gevent的方法相比),但是,这基本上是提高并发性最有效的方法 (同时在内存使用和性能方面)。诸如node.js这样的技术由于它们允许完成的结果,它们正变得流行起来。

WSGI生成器 (aka yield all over the place)

以下面的WSGI应用为例:

  1. def application(e, sr):
  2. sr('200 OK', [('Content-Type','text/html')])
  3. yield "one"
  4. yield "two"
  5. yield "three"

如果你已经使用uWSGI异步模式,那么你就会知道每次yield内部调用使用的挂起引擎 (在我们的例子中,是greenlet.switch())。

那意味着,我们在调用”application()”后会立即进入tornado IOLoop引擎。如果我们不在等待事件,那么能如何将控制权交还给我们的可回调对象?

已扩展uWSGI异步API来支持”schedule_fix”钩子。它允许你在调用挂起引擎后立即调用一个钩子。

在tornado这种情况下,这个钩子会被映射到某些像这样的东东:

  1. io_loop.add_callback(me.switch)

通过这种方式,在每次yield之后,一个me.switch()函数就会被调用,从而让可回调对象恢复。

有了这个钩子,你可以透明地托管标准的WSGI应用,而无需更改它们。

绑定和监听Tornado

在每一个worker中,在fork()之后会执行Tornado IOLoop。如果你想把Tornado绑定到网络地址上,那么记得为每个worker使用不同的端口:

  1. from uwsgidecorators import *
  2. import tornado.web
  3.  
  4. # this is our Tornado-managed app
  5. class MainHandler(tornado.web.RequestHandler):
  6. def get(self):
  7. self.write("Hello, world")
  8.  
  9. t_application = tornado.web.Application([
  10. (r"/", MainHandler),
  11. ])
  12.  
  13. # here happens the magic, we bind after every fork()
  14. @postfork
  15. def start_the_tornado_servers():
  16. application.listen(8000 + uwsgi.worker_id())
  17.  
  18. # this is our WSGI callable managed by uWSGI
  19. def application(e, sr):
  20. ...

记住:不要启动IOLoop类。一旦安装完成,uWSGI将会自己启动它。