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,你可以这样

UWSGI_EMBED_PLUGINS=tornado,greenlet pip install tornado greenlet uwsgi

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

UWSGI_EMBED_PLUGINS=tornado,greenlet make

运行之

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

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事件可用:

from tornado.httpclient import AsyncHTTPClient
import greenlet
import functools

# this gives us access to the main IOLoop (the same used by uWSGI)
from tornado.ioloop import IOLoop
io_loop = IOLoop.instance()

# this is called at the end of the external HTTP request
def handle_request(me, response):
    if response.error:
        print("Error:", response.error)
    else:
        me.result = response.body
    # back to the WSGI callable
    me.switch()

 def application(e, sr):
     me = greenlet.getcurrent()
     http_client = AsyncHTTPClient()
     http_client.fetch("http://localhost:9191/services", functools.partial(handle_request, me))
     # suspend the execution until an IOLoop event is available
     me.parent.switch()
     sr('200 OK', [('Content-Type','text/plain')])
     return me.result

欢迎来到回调地狱

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

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

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

from tornado.httpclient import AsyncHTTPClient
import greenlet
import functools

# this gives us access to the main IOLoop (the same used by uWSGI)
from tornado.ioloop import IOLoop
io_loop = IOLoop.instance()

def sleeper(me):
    #TIMED OUT
    # finally come back to WSGI callable
    me.switch()

# this is called at the end of the external HTTP request
def handle_request(me, response):
    if response.error:
        print("Error:", response.error)
    else:
        me.result = response.body
    # add another callback in the chain
    me.timeout = io_loop.add_timeout(time.time() + 10, functools.partial(sleeper, me))

 def application(e, sr):
     me = greenlet.getcurrent()
     http_client = AsyncHTTPClient()
     http_client.fetch("http://localhost:9191/services", functools.partial(handle_request, me))
     # suspend the execution until an IOLoop event is available
     me.parent.switch()
     # unregister the timer
     io_loop.remove_timeout(me.timeout)
     sr('200 OK', [('Content-Type','text/plain')])
     return me.result

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

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

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

以下面的WSGI应用为例:

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

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

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

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

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

io_loop.add_callback(me.switch)

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

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

绑定和监听Tornado

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

from uwsgidecorators import *
import tornado.web

# this is our Tornado-managed app
class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")

t_application = tornado.web.Application([
    (r"/", MainHandler),
])

# here happens the magic, we bind after every fork()
@postfork
def start_the_tornado_servers():
    application.listen(8000 + uwsgi.worker_id())

# this is our WSGI callable managed by uWSGI
def application(e, sr):
    ...

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