(2018-05-30.Python從Zero到One)8、(Tornado)異步與WebSockets__1.7.2 Tornado異步

7.2 Tornado異步

因?yàn)閑poll主要是用來解決網(wǎng)絡(luò)IO的并發(fā)問題,所以Tornado的異步編程也主要體現(xiàn)在網(wǎng)絡(luò)IO的異步上,即異步Web請(qǐng)求。

1. tornado.httpclient.AsyncHTTPClient

Tornado提供了一個(gè)異步Web請(qǐng)求客戶端tornado.httpclient.AsyncHTTPClient用來進(jìn)行異步Web請(qǐng)求。

fetch(request, callback=None)

用于執(zhí)行一個(gè)web請(qǐng)求request,并異步返回一個(gè)tornado.httpclient.HTTPResponse響應(yīng)。

request可以是一個(gè)url,也可以是一個(gè)tornado.httpclient.HTTPRequest對(duì)象。如果是url,fetch會(huì)自己構(gòu)造一個(gè)HTTPRequest對(duì)象。

HTTPRequest

HTTP請(qǐng)求類,HTTPRequest的構(gòu)造函數(shù)可以接收眾多構(gòu)造參數(shù),最常用的如下:

  • url (string) – 要訪問的url,此參數(shù)必傳,除此之外均為可選參數(shù)
  • method (string) – HTTP訪問方式,如“GET”或“POST”,默認(rèn)為GET方式
  • headers (HTTPHeaders or dict) – 附加的HTTP協(xié)議頭
  • body – HTTP請(qǐng)求的請(qǐng)求體

HTTPResponse

HTTP響應(yīng)類,其常用屬性如下:

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

2. 測(cè)試接口

新浪IP地址庫

接口說明

1.請(qǐng)求接口(GET):

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

2.響應(yīng)信息:

(json格式的)國家 、?。ㄗ灾螀^(qū)或直轄市)、市(縣)、運(yùn)營(yíng)商

3.返回?cái)?shù)據(jù)格式:

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

3. 回調(diào)異步

class IndexHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous  # 不關(guān)閉連接,也不發(fā)送響應(yīng)
    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信息錯(cuò)誤")
        self.finish() # 發(fā)送響應(yīng)信息,結(jié)束請(qǐng)求處理

tornado.web.asynchronous

此裝飾器用于回調(diào)形式的異步方法,并且應(yīng)該僅用于HTTP的方法上(如get、post等)。

此裝飾器不會(huì)讓被裝飾的方法變?yōu)楫惒?,而只是告訴框架被裝飾的方法是異步的,當(dāng)方法返回時(shí)響應(yīng)尚未完成。只有在request handler調(diào)用了finish方法后,才會(huì)結(jié)束本次請(qǐng)求處理,發(fā)送響應(yīng)。

不帶此裝飾器的請(qǐng)求在get、post等方法返回時(shí)自動(dòng)完成結(jié)束請(qǐng)求處理。

4. 協(xié)程異步

在上一節(jié)中我們自己封裝的裝飾器get_coroutine在Tornado中對(duì)應(yīng)的是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信息錯(cuò)誤")

也可以將異步Web請(qǐng)求單獨(dú)出來:

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信息錯(cuò)誤")

    @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來返回此返回值。

并行協(xié)程

Tornado可以同時(shí)執(zhí)行多個(gè)異步,并發(fā)的異步可以使用列表或字典,如下:

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信息錯(cuò)誤<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)

5. 關(guān)于數(shù)據(jù)庫的異步說明

網(wǎng)站基本都會(huì)有數(shù)據(jù)庫操作,而Tornado是單線程的,這意味著如果數(shù)據(jù)庫查詢返回過慢,整個(gè)服務(wù)器響應(yīng)會(huì)被堵塞。

數(shù)據(jù)庫查詢,實(shí)質(zhì)上也是遠(yuǎn)程的網(wǎng)絡(luò)調(diào)用;理想情況下,是將這些操作也封裝成為異步的;但Tornado對(duì)此并沒有提供任何支持。

這是Tornado的設(shè)計(jì),而不是缺陷。

一個(gè)系統(tǒng),要滿足高流量;是必須解決數(shù)據(jù)庫查詢速度問題的!

數(shù)據(jù)庫若存在查詢性能問題,整個(gè)系統(tǒng)無論如何優(yōu)化,數(shù)據(jù)庫都會(huì)是瓶頸,拖慢整個(gè)系統(tǒng)!

異步并不能從本質(zhì)上提到系統(tǒng)的性能;它僅僅是避免多余的網(wǎng)絡(luò)響應(yīng)等待,以及切換線程的CPU耗費(fèi)。

如果數(shù)據(jù)庫查詢響應(yīng)太慢,需要解決的是數(shù)據(jù)庫的性能問題;而不是調(diào)用數(shù)據(jù)庫的前端Web應(yīng)用。

對(duì)于實(shí)時(shí)返回的數(shù)據(jù)查詢,理想情況下需要確保所有數(shù)據(jù)都在內(nèi)存中,數(shù)據(jù)庫硬盤IO應(yīng)該為0;這樣的查詢才能足夠快;而如果數(shù)據(jù)庫查詢足夠快,那么前端web應(yīng)用也就無將數(shù)據(jù)查詢封裝為異步的必要。

就算是使用協(xié)程,異步程序?qū)τ谕匠绦蚴冀K還是會(huì)提高復(fù)雜性;需要衡量的是處理這些額外復(fù)雜性是否值得。

如果后端有查詢實(shí)在是太慢,無法繞過,Tornaod的建議是將這些查詢?cè)诤蠖朔庋b獨(dú)立封裝成為HTTP接口,然后使用Tornado內(nèi)置的異步HTTP客戶端進(jìn)行調(diào)用。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容