Python Web架構Tornado的非同步處理代碼示範範例

來源:互聯網
上載者:User

標籤:inf   and   幸運   tail   gui   log   import   hand   client   

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介面為:

city=xxx">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總體效能無法保證的同步調用代碼相比。實現難度就差點兒不存在了。

上面的代碼非常多文法細節沒有展開,希望實現思路能協助到有緣人。^_^

參考資料
  1. Tornado Doc: User’s guide
  2. Book: Introduction to tornado chapter 5. asynchronous web services

Python Web架構Tornado的非同步處理代碼示範範例

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.