Tornado框架09-異步02

因爲epoll主要是用來解決網絡IO的併發問題,所以Tornado的異步編程也主要體現在網絡IO的異步上,即異步Web請求。

01-tornado.httpclient.AsyncHTTPClient

Tornado提供了一個異步Web請求客戶端tornado.httpclient.AsyncHTTPClient用來進行異步Web請求。

fetch(request, callback=None)
用於執行一個web請求request,並異步返回一個tornado.httpclient.HTTPResponse響應。

request可以是一個url,也可以是一個tornado.httpclient.HTTPRequest對象。如果是url,fetch會自己構造一個HTTPRequest對象。

HTTPRequest

HTTP請求類,HTTPRequest的構造函數可以接收衆多構造參數,最常用的如下:

  • url (string) – 要訪問的url,此參數必傳,除此之外均爲可選參數
  • method (string) – HTTP訪問方式,如“GET”或“POST”,默認爲GET方式
  • headers (HTTPHeaders or dict) – 附加的HTTP協議頭
  • body – HTTP請求的請求體

HTTPResponse

HTTP響應類,其常用屬性如下:

  • code: HTTP狀態碼,如 200 或 404
  • reason: 狀態碼描述信息
  • body: 響應體字符串
  • error: 異常(可有可無)

02-測試接口

新浪IP地址庫

接口說明

1.請求接口(GET):

http://int.dpool.sina.com.cn/iplookup/iplookup.php?format=json&ip=[ip地址字串]

2.響應信息:

(json格式的)國家 、省(自治區或直轄市)、市(縣)、運營商

3.返回數據格式:

{"ret":1,"start":-1,"end":-1,"country":"\u4e2d\u56fd","province":"\u5317\u4eac","city":"\u5317\u4eac","district":"","isp":"","type":"","desc":""}

03-回調異步

class IndexHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous  # 不關閉連接,也不發送響應
    def get(self):
        http = tornado.httpclient.AsyncHTTPClient()
        http.fetch("http://int.dpool.sina.com.cn/iplookup/iplookup.php?format=json&ip=14.130.112.24",
                   callback=self.on_response)

    def on_response(self, response):
        if response.error:
            self.send_error(500)
        else:
            data = json.loads(response.body)
            if 1 == data["ret"]:
                self.write(u"國家:%s 省份: %s 城市: %s" % (data["country"], data["province"], data["city"]))
            else:
                self.write("查詢IP信息錯誤")
        self.finish() # 發送響應信息,結束請求處理

tornado.web.asynchronous

此裝飾器用於回調形式的異步方法,並且應該僅用於HTTP的方法上(如get、post等)。

此裝飾器不會讓被裝飾的方法變爲異步,而只是告訴框架被裝飾的方法是異步的,當方法返回時響應尚未完成。只有在request handler調用了finish方法後,纔會結束本次請求處理,發送響應。

不帶此裝飾器的請求在get、post等方法返回時自動完成結束請求處理。

04-協程異步

在上一節中我們自己封裝的裝飾器get_coroutine在Tornado中對應的是tornado.gen.coroutine。

class IndexHandler(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def get(self):
        http = tornado.httpclient.AsyncHTTPClient()
        response = yield http.fetch("http://int.dpool.sina.com.cn/iplookup/iplookup.php?format=json&ip=14.130.112.24")
        if response.error:
            self.send_error(500)
        else:
            data = json.loads(response.body)
            if 1 == data["ret"]:
                self.write(u"國家:%s 省份: %s 城市: %s" % (data["country"], data["province"], data["city"]))
            else:
                self.write("查詢IP信息錯誤")

也可以將異步Web請求單獨出來:

class IndexHandler(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def get(self):
        rep = yield self.get_ip_info("14.130.112.24")
        if 1 == rep["ret"]:
            self.write(u"國家:%s 省份: %s 城市: %s" % (rep["country"], rep["province"], rep["city"]))
        else:
            self.write("查詢IP信息錯誤")

    @tornado.gen.coroutine
    def get_ip_info(self, ip):
        http = tornado.httpclient.AsyncHTTPClient()
        response = yield http.fetch("http://int.dpool.sina.com.cn/iplookup/iplookup.php?format=json&ip=" + ip)
        if response.error:
            rep = {"ret:0"}
        else:
            rep = json.loads(response.body)
        raise tornado.gen.Return(rep)  # 此處需要注意

代碼中我們需要注意的地方是get_ip_info返回值的方式,在python 2中,使用了yield的生成器可以使用不返回任何值的return,但不能return value,因此Tornado爲我們封裝了用於在生成器中返回值的特殊異常tornado.gen.Return,並用raise來返回此返回值。

並行協程

Tornado可以同時執行多個異步,併發的異步可以使用列表或字典,如下:

class IndexHandler(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def get(self):
        ips = ["14.130.112.24",
            "15.130.112.24",
            "16.130.112.24",
            "17.130.112.24"]
        rep1, rep2 = yield [self.get_ip_info(ips[0]), self.get_ip_info(ips[1])]
        rep34_dict = yield dict(rep3=self.get_ip_info(ips[2]), rep4=self.get_ip_info(ips[3]))
        self.write_response(ips[0], rep1) 
        self.write_response(ips[1], rep2) 
        self.write_response(ips[2], rep34_dict['rep3']) 
        self.write_response(ips[3], rep34_dict['rep4']) 

    def write_response(self, ip, response):
        self.write(ip) 
        self.write(":<br/>") 
        if 1 == response["ret"]:
            self.write(u"國家:%s 省份: %s 城市: %s<br/>" % (response["country"], response["province"], response["city"]))
        else:
            self.write("查詢IP信息錯誤<br/>")

    @tornado.gen.coroutine
    def get_ip_info(self, ip):
        http = tornado.httpclient.AsyncHTTPClient()
        response = yield http.fetch("http://int.dpool.sina.com.cn/iplookup/iplookup.php?format=json&ip=" + ip)
        if response.error:
            rep = {"ret:1"}
        else:
            rep = json.loads(response.body)
        raise tornado.gen.Return(rep)

05-關於數據庫的異步說明

網站基本都會有數據庫操作,而Tornado是單線程的,這意味着如果數據庫查詢返回過慢,整個服務器響應會被堵塞。

數據庫查詢,實質上也是遠程的網絡調用;理想情況下,是將這些操作也封裝成爲異步的;但Tornado對此並沒有提供任何支持。

這是Tornado的設計,而不是缺陷。

一個系統,要滿足高流量;是必須解決數據庫查詢速度問題的!

數據庫若存在查詢性能問題,整個系統無論如何優化,數據庫都會是瓶頸,拖慢整個系統!

異步並不能從本質上提到系統的性能;它僅僅是避免多餘的網絡響應等待,以及切換線程的CPU耗費。

如果數據庫查詢響應太慢,需要解決的是數據庫的性能問題;而不是調用數據庫的前端Web應用。

對於實時返回的數據查詢,理想情況下需要確保所有數據都在內存中,數據庫硬盤IO應該爲0;這樣的查詢才能足夠快;而如果數據庫查詢足夠快,那麼前端web應用也就無將數據查詢封裝爲異步的必要。

就算是使用協程,異步程序對於同步程序始終還是會提高複雜性;需要衡量的是處理這些額外複雜性是否值得。

如果後端有查詢實在是太慢,無法繞過,Tornaod的建議是將這些查詢在後端封裝獨立封裝成爲HTTP接口,然後使用Tornado內置的異步HTTP客戶端進行調用。

發佈了48 篇原創文章 · 獲贊 4 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章