Python Web框架Tornado的异步处理代码演示样例
1. What is Tornado
Tornado是一個輕量級但高性能的Python web框架,與還有一個流行的Python web框架Django相比。tornado不提供操作數(shù)據(jù)庫的ORM接口及嚴(yán)格的MVC開發(fā)模式,但能夠提供主要的web server功能。故它是輕量級的;它借助non-blocking and event-driven的I/O模型(epoll或kqueue)實(shí)現(xiàn)了一套異步網(wǎng)絡(luò)庫,故它是高性能的。
Tornado的輕量級+高性能特性使得它特別適用于提供web api的場合,使用合理的話,其非堵塞+異步能力能夠應(yīng)對C10K問題。
須要特別注意的是,因為Python的GIL導(dǎo)致多線程總是單核運(yùn)行的”特點(diǎn)”,tornado處理http請求時,若某個請求的后端響應(yīng)有堵塞現(xiàn)象(如從DB或磁盤讀數(shù)據(jù)導(dǎo)致處理時間非常長),則會導(dǎo)致其他http請求也被block,這會嚴(yán)重拖累tornado在高并發(fā)場景下的性能。
幸運(yùn)的是。tornado提供了異步處理請求的能力,在異步模式下,我們能夠通過傳入回調(diào)函數(shù)或借助tornado提供的tornado.gen.coroutine裝飾器,使得tornado內(nèi)部的io loop在等待當(dāng)前請求響應(yīng)結(jié)果的同一時候,仍然能夠接受其他的http請求,這樣就避免了某個耗時操作影響tornado的處理能力。
2. 怎樣在tornado框架下編寫異步處理代碼
Tornado官網(wǎng)文檔給出了幾個簡單的異步代碼演示樣例,只是說實(shí)話,代碼太過簡單(都是在某個uri的handler類的get或post函數(shù)中展現(xiàn)了主要的異步語法),沒有多大的實(shí)戰(zhàn)意義。
在實(shí)際項目中。復(fù)雜的處理邏輯不可能都堆在get或post函數(shù)中,而是會封裝在其他class中供handler類的get或post函數(shù)調(diào)用。
所以,本文給出一個稍復(fù)雜的實(shí)例,旨在說明怎樣在其他class的函數(shù)中實(shí)現(xiàn)異步處理邏輯,以實(shí)現(xiàn)http請求異步化處理的目的。
如果如今的需求是用tornado實(shí)現(xiàn)一個web server,支持名為cityhotel的uri方法,當(dāng)client通過http GET請求訪問該uri時,web server依據(jù)query參數(shù)指定的城市,去請求存放hotel具體數(shù)據(jù)的還有一個后端api。進(jìn)行業(yè)務(wù)處理后返回某個連鎖hotel在該城市的全部門店給client。
如果client GET請求的url格式為:http://host/api/hotel/cityhotel?city=xxx
再如果存放hotel具體數(shù)據(jù)的后端api接口為:http://hotel_backend/getCityHotels?
city=xxx
依據(jù)上面的場景,因為我們用tornado實(shí)現(xiàn)的web server接到client的請求后,還要去還有一個API接口請求基礎(chǔ)數(shù)據(jù),而后者在返回前,tornado會block,所以,這樣的場景下,tornado最好以異步方式請求那個提供基礎(chǔ)數(shù)據(jù)的API。避免不可控的后端拖累tornado的響應(yīng)性能。
依據(jù)上面描寫敘述的業(yè)務(wù)需求。以下的代碼示范了怎樣通過異步方式處理業(yè)務(wù)處理。
模塊入口文件(main.py):
#!/bin/env pythonimport tornado.ioloop import tornado.web import tornado.gen import hotelcoreclass CityHotelHandler(tornado.web.RequestHandler):@tornado.gen.coroutinedef get(self):## parse query paramsparams = {}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()處理業(yè)務(wù)邏輯的module封裝在hotelcore.py文件里,代碼例如以下:
#!/bin/env python #-*- encoding: utf-8 -*-import jsonfrom tornado import gen from tornado import httpclientclass HotelApiHandler(object):_cfg_dict = {'api_host' : 'api.hotelbackend.com',}@classmethod@gen.coroutinedef get_city_hotel(cls, city):ret = yield cls._parallel_fetch_city_hotel(city)raise gen.Return((200, ret))@classmethod@gen.coroutinedef _parallel_fetch_city_hotel(cls, city):base_url = 'http://%s/v1/getCityHotel' % (cls._cfg_dict['api_host'])## hote type: 1=normal room; 2=deluxe roomhotel_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 requesthttp_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'] = 0ret['errmsg'] = 'SUCCESS'ret['data'] = city_hotel_infoelse:ret['errno'] = 1ret['errmsg'] = 'Service Not Found at This City'ret['data'] = ''raise gen.Return(ret)@classmethoddef _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_detailbreakreturn city_hotel_info對以上代碼的幾點(diǎn)補(bǔ)充說明:
- 編寫tornado異步處理代碼須要對Python的decorator語法和generator/yield語法比較熟悉
- tornado提供的裝飾器@gen.coroutine表明被裝飾函數(shù)是個異步處理函數(shù),該函數(shù)的調(diào)用不會block tornado主線程
- 被@gen.coroutine裝飾的函數(shù)中,須要異步運(yùn)行的耗時函數(shù)用yield來調(diào)用,yield本身返回的是個generator,結(jié)合@gen.coroutine后。它返回一個tornado定義的Future類型的對象
- yield調(diào)用的函數(shù)在運(yùn)行過程中。進(jìn)程控制權(quán)會返給主線程,故即使該函數(shù)須要較長運(yùn)行時間,tornado的主線程也能夠繼續(xù)處理其他請求
- 在Python 2.x版本號的語法中。generator中不同意用return返回函數(shù)的返回值。必須用tornado提供的raise gen.Return(ret)達(dá)到返回的目的。這是個比較tricky的方法
- yield返回的Future對象能夠通過調(diào)用body屬性來獲取通過yield調(diào)用的函數(shù)的返回值
- 僅僅要結(jié)合上述幾點(diǎn)理解了@gen.coroutine和yield在tornado異步編程中的語法意義,那么,寫出復(fù)雜的異步調(diào)用代碼與編寫實(shí)現(xiàn)同樣功能但tornado總體性能無法保證的同步調(diào)用代碼相比。實(shí)現(xiàn)難度就差點(diǎn)兒不存在了。
上面的代碼非常多語法細(xì)節(jié)沒有展開,希望實(shí)現(xiàn)思路能幫助到有緣人。^_^
參考資料
轉(zhuǎn)載于:https://www.cnblogs.com/llguanli/p/8406251.html
總結(jié)
以上是生活随笔為你收集整理的Python Web框架Tornado的异步处理代码演示样例的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 怀孕梦到鱼预示着什么
- 下一篇: ansible---基础