Python Web框架Tornado的异步处理代码示例
Tornado是一个轻量级但高性能的Python web框架,与另一个流行的Python web框架Django相比,tornado不提供操作数据库的ORM接口及严格的MVC开发模式,但可以提供基本的web server功能,故它是轻量级的;它借助non-blocking and event-driven的I/O模型(epoll或kqueue)实现了一套异步网络库,故它是高性能的。
Tornado的轻量级+高性能特性使得它特别适用于提供web api的场合,使用合理的话,其非阻塞+异步能力可以应对C10K问题。
需要特别注意的是,由于Python的GIL导致多线程总是单核执行的”特点”,tornado处理http请求时,若某个请求的后端响应有阻塞现象(如从DB或磁盘读数据导致处理时间很长),则会导致其他http请求也被block,这会严重拖累tornado在高并发场景下的性能。
幸运的是,tornado提供了异步处理请求的能力,在异步模式下,我们可以通过传入回调函数或借助tornado提供的tornado.gen.coroutine装饰器,使得tornado内部的io loop在等待当前请求响应结果的同时,仍然可以接受其它的http请求,这样就避免了某个耗时操作影响tornado的处理能力。
Tornado官网文档给出了几个简单的异步代码示例,不过说实话,代码太过简单(都是在某个uri的handler类的get或post函数中展现了基本的异步语法),没有多大的实战意义。
在实际项目中,复杂的处理逻辑不可能都堆在get或post函数中,而是会封装在其它class中供handler类的get或post函数调用。所以,本文给出一个稍复杂的实例,旨在说明如何在其它class的函数中实现异步处理逻辑,以实现http请求异步化处理的目的。
假设现在的需求是用tornado实现一个web server,支持名为cityhotel的uri方法,当client通过http GET请求访问该uri时,web server根据query参数指定的城市,去请求存放hotel详细数据的另一个后端api,进行业务处理后返回某个连锁hotel在该城市的所有门店给client。
假设client GET请求的url格式为:http://host/api/hotel/cityhotel?city=xxx
再假设存放hotel详细数据的后端api接口为:http://hotel_backend/getCityHotels?city=xxx
根据上面的场景,由于我们用tornado实现的web server接到client的请求后,还要去另一个API接口请求基础数据,而后者在返回前,tornado会block,所以,这种场景下,tornado最好以异步方式请求那个提供基础数据的API,避免不可控的后端拖累tornado的响应性能。
根据上面描述的业务需求,下面的代码示范了如何通过异步方式处理业务处理。
模块入口文件(main.py):
#!/bin/env python import tornado.ioloop import tornado.web import tornado.gen import hotelcore class CityHotelHandler(tornado.web.RequestHandler): @tornado.gen.coroutine def get(self): ## parse query params params = {} keys = ['city'] for key in keys: value = self.get_query_argument(key) params[key] = value (status, rsp) = yield hotelcore.HotelApiHandler.get_city_hotel(params['city']) if 200 == status: self.set_header('content-type', 'application/json') self.finish(rsp) else: self.set_status(404) self.finish() def main(): app_inst = tornado.web.Application([ (r'/api/hotel/cityhotel', CityHotelHandler), ], compress_response = True) app_inst.listen(8218) tornado.ioloop.IOLoop.current().start() if '__main__' == __name__: main()
处理业务逻辑的module封装在hotelcore.py文件中,代码如下:
#!/bin/env python #-*- encoding: utf-8 -*- import json from tornado import gen from tornado import httpclient class HotelApiHandler(object): _cfg_dict = { 'api_host' : 'api.hotelbackend.com', } @classmethod @gen.coroutine def get_city_hotel(cls, city): ret = yield cls._parallel_fetch_city_hotel(city) raise gen.Return((200, ret)) @classmethod @gen.coroutine def _parallel_fetch_city_hotel(cls, city): base_url = 'http://%s/v1/getCityHotel' % (cls._cfg_dict['api_host']) ## hote type: 1=normal room; 2=deluxe room hotel_type = {'normal': 1, 'deluxe': 2} urls = [] for v in hotel_type.values(): api_url = '%s?city=%s&level=%s' % (base_url, city, v) urls.append(api_url) ## issue async http request http_clt = httpclient.AsyncHTTPClient() rsps_dict = yield dict(normal_room = http_clt.fetch(urls[0]), deluxe_room = http_clt.fetch(urls[1])) city_hotel_info = cls._parse_city_hotel(rsps_dict, city) ret = { } if len(city_hotel_info): ret['errno'] = 0 ret['errmsg'] = 'SUCCESS' ret['data'] = city_hotel_info else: ret['errno'] = 1 ret['errmsg'] = 'Service Not Found at This City' ret['data'] = '' raise gen.Return(ret) @classmethod def _parse_city_hotel(cls, rsp_dict, city): city_hotel_info = {} for hotel_level, rsp in rsp_dict.items(): rsp_json = json.loads(rsp.body) datas = rsp_json['data'] for city_id, city_detail in datas.items(): name = city_detail['name'] if city in name: city_hotel_info[hotel_level] = city_detail break return city_hotel_info
说明: 编写tornado异步处理代码需要对Python的decorator语法和generator/yield语法比较熟悉 tornado提供的装饰器@gen.coroutine表明被装饰函数是个异步处理函数, 该函数的调用不会block tornado主线程被@gen.coroutine装饰的函数中, 需要异步执行的耗时函数用yield来调用,yield本身返回的是个generator, 结合@gen.coroutine后,它返回一个tornado定义的Future类型的对象 yield调用的函数在执行过程中,进程控制权会返给主线程, 故即使该函数需要较长运行时间,tornado的主线程也可以继续处理其它请求 在Python 2.x版本的语法中,generator中不允许用return返回函数的返回值, 必须用tornado提供的raise gen.Return(ret)达到返回的目的, 这是个比较tricky的方法yield返回的Future对象可以通过调用body属性来获取 通过yield调用的函数的返回值 只要结合上述几点理解了 @gen.coroutine和yield在tornado异步编程中的语法意义, 那么,写出复杂的异步调用代码与编写实现相同功能 但tornado整体性能无法保证的同步调用代码相比,实现难度就几乎不存在了。
Tornado Doc: User’s guide
Book: Introduction to tornado chapter 5. asynchronous web services