標籤:python tornado web-server
1. What is 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的處理能力。
2. 如何在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 pythonimport tornado.ioloopimport tornado.webimport tornado.genimport hotelcoreclass 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 jsonfrom tornado import genfrom tornado import httpclientclass 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
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
Python Web架構Tornado的非同步處理程式碼範例