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地址庫(kù)
接口說明
1.請(qǐng)求接口(GET):
[http://int.dpool.sina.com.cn/iplookup/iplookup.php?format=json&ip=ip地址字串]
2.響應(yīng)信息:
(json格式的)國(guó)家 疤孕、噬毯酢(自治區(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"國(guó)家:%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)楫惒讲裙伲皇歉嬖V框架被裝飾的方法是異步的却桶,當(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"國(guó)家:%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"國(guó)家:%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"國(guó)家:%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ù)庫(kù)的異步說明
網(wǎng)站基本都會(huì)有數(shù)據(jù)庫(kù)操作商虐,而Tornado是單線程的,這意味著如果數(shù)據(jù)庫(kù)查詢返回過慢崖疤,整個(gè)服務(wù)器響應(yīng)會(huì)被堵塞秘车。
數(shù)據(jù)庫(kù)查詢,實(shí)質(zhì)上也是遠(yuǎn)程的網(wǎng)絡(luò)調(diào)用劫哼;理想情況下叮趴,是將這些操作也封裝成為異步的;但Tornado對(duì)此并沒有提供任何支持权烧。
這是Tornado的設(shè)計(jì)眯亦,而不是缺陷。
一個(gè)系統(tǒng)豪嚎,要滿足高流量搔驼;是必須解決數(shù)據(jù)庫(kù)查詢速度問題的!
數(shù)據(jù)庫(kù)若存在查詢性能問題侈询,整個(gè)系統(tǒng)無論如何優(yōu)化舌涨,數(shù)據(jù)庫(kù)都會(huì)是瓶頸,拖慢整個(gè)系統(tǒng)扔字!
異步并不能從本質(zhì)上提到系統(tǒng)的性能囊嘉;它僅僅是避免多余的網(wǎng)絡(luò)響應(yīng)等待,以及切換線程的CPU耗費(fèi)革为。
如果數(shù)據(jù)庫(kù)查詢響應(yīng)太慢扭粱,需要解決的是數(shù)據(jù)庫(kù)的性能問題;而不是調(diào)用數(shù)據(jù)庫(kù)的前端Web應(yīng)用震檩。
對(duì)于實(shí)時(shí)返回的數(shù)據(jù)查詢琢蛤,理想情況下需要確保所有數(shù)據(jù)都在內(nèi)存中,數(shù)據(jù)庫(kù)硬盤IO應(yīng)該為0抛虏;這樣的查詢才能足夠快博其;而如果數(shù)據(jù)庫(kù)查詢足夠快,那么前端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)用携兵。