使用 Tornado

本来想着使用蚂蚁搬家的方式,将 Tornado 整体框架分割为各个章节,各个方面分别学习整理。但发现这样的效率不高,经常碰到需要深入或者扩展的知识点。所以决定先使用,在用的过程中摸清楚整个框架活的主线,通过主线逐渐再渗透的细节。那么,这一篇文章就记录和整理一下使用 Tornado 框架开发应用的心得。

本篇文章,我们尝试将 OpenStack 的 nova-api 服务跑在 Tornado 上,正好可以与 eventlet 做一下并发处理性能比较。

初步实现 Web 服务

首先看下,我们通过官网上的第一个代码示例认识下 tornado 框架的用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world!")
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
])
if __name__ == '__main__':
app = make_app()
app.listen(8879)
tornado.ioloop.IOLoop().current().start()

以上代码将启动一个 Web 服务,当访问本机的8879端口,即 127.0.0.1:8879 时,服务端返回 Hello, world!

但是,OpenStack 的 nova-api 服务是一个 wsgi 服务。我们看下 Tornado 框架如何支持 wsgi 。

wsgi 服务的支持

参考官网:http://www.tornadoweb.org/en/stable/wsgi.html

在 Tornado servers 上运行 WSGI 应用

tornado.wsgi.WsgiContainer 模块可以让其他 wsgi 应用或者框架在 Tornado HTTP 服务上运行。For example, with this class you can mix Django and Tornado handlers in a single server.

还是先看下官网的例子:

1
2
3
4
5
6
7
8
9
10
def simple_app(environ, start_response):
status = "200 OK"
response_headers = [("Content-type", "text/plain")]
start_response(status, response_headers)
return ["Hello world!\n"]
container = tornado.wsgi.WSGIContainer(simple_app)
http_server = tornado.httpserver.HTTPServer(container)
http_server.listen(8888)
tornado.ioloop.IOLoop.current().start()
在 Tornado servers 上运行 nova-api

参考上面的例子程序,在 Tornado 上运行 nova-api 服务的思路为,找到 nova-api 的 wsgi Application,使用 WSGIContainer 包装为 http_server,启动 tornado 的 ioloop 即可。OpenStack 的 wsgi Application 是由 Python 模块 paste.deploy 根据配置文件生成的,查看 /etc/nova/api-paste.ini 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[composite:osapi_compute]
use = call:nova.api.openstack.urlmap:urlmap_factory
/: oscomputeversions
/v1.1: openstack_compute_api_v2
/v2: openstack_compute_api_v2
[composite:openstack_compute_api_v2]
use = call:nova.api.auth:pipeline_factory
noauth = faultwrap sizelimit noauth ratelimit osapi_compute_app_v2
keystone = faultwrap sizelimit authtoken keystonecontext ratelimit osapi_compute_app_v2
keystone_nolimit = faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v2
[app:osapi_compute_app_v2]
paste.app_factory = nova.api.openstack.compute:APIRouter.factory

可以看到,最终的 wsgi Application 为 nova.api.openstack.compute:APIRouter.factory 。我们开始尝试将其放入 Tornado 运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 本代码对应的 Openstack 版本为官方F版本 :)
import sys
import tornado.wsgi
import tornado.httpserver
from nova import wsgi as nova_wsgi
from nova import flags
from nova.openstack.common import log as logging
from nova.api.openstack.wsgi import Resource
from nova.api.openstack.compute import APIRouter
if __name__ == '__main__':
flags.parse_args(sys.argv)
logging.setup("nova")
"""通过paste.deploy从/etc/nova/api-paste.ini"""
loader = nova_wsgi.Loader()
wsgi_app = loader.load_app('osapi_compute')
container = tornado.wsgi.WSGIContainer(wsgi_app)
http_server = tornado.httpserver.HTTPServer(container)
http_server.listen(8879)
tornado.ioloop.IOLoop.current().start()

请求服务 curl -i -X GET -H "Content-Type: application/json" -H "X-Auth-Token:xxxxxx:xxxxxx" http://127.0.0.1:8879/v2/xxxxxx/servers/yyyyyy/os-security-groups

返回结果 {"security_groups": [{"name": "default", "rules": [], "tenant_id": "xxxxxx", "created_at": "2016-04-29T13:31:05.000000", "id": 96, "description": "default"}]}

当当当当!成功了!

当然,Tornado 官方文档中有警告提示,这次尝试只是当作深入 学习Tornado的一次尝试:

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 的同步模型基于单线程的同步执行。这意味着,在 Tornado 中使用 WSGIContainer 运行 WSGI 应用比在使用多线程的 WSGI 服务(比如gunicorn或者uwsgi)上运行欠缺了“可伸缩性”。建议只在这种情况下使用 WSGIContainer:相比损失掉“可伸缩性”,绑定使用 Tornado 和 WSGI 应用可以获得更多其他益处。

这个“可伸缩性”具体指什么,后续深入学习 Tornado 的过程中再具体探索一下。