7.2 Tornado異步

7.2 Tornado異步

由於epoll主要是用來解決網絡IO的併發問題,因此Tornado的異步編程也主要體如今網絡IO的異步上,即異步Web請求。php

1. tornado.httpclient.AsyncHTTPClient

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

fetch(request, callback=None)

用於執行一個web請求request,並異步返回一個tornado.httpclient.HTTPResponse響應。python

request能夠是一個url,也能夠是一個tornado.httpclient.HTTPRequest對象。若是是url,fetch會本身構造一個HTTPRequest對象。web

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: 異常(無關緊要)

2. 測試接口

新浪IP地址庫json

接口說明後端

1.請求接口(GET):api

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":""} 

3. 回調異步

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
#  @Time: 2020/3/15 19:38
#  @Author:zhangmingda
#  @File: asyncHTTPClient_on_server.py
#  @Software: PyCharm
#  Description: 學習異步客戶端httpclient.AsyncHTTPClient


from tornado.options import options
from tornado.options import parse_command_line
from tornado.web import RequestHandler, asynchronous,Application
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from tornado.httpclient import AsyncHTTPClient
import tornado
import base64,uuid,os,json

class AsyncHttpclientIndexHandler(RequestHandler):
    '''學習異步請求客戶端'''
     #不關閉鏈接,也不發送響應
    @asynchronous
    def get(self, *args, **kwargs):
        http = AsyncHTTPClient()
        req_url = "http://myip.ksyun.com"
        print('發起請求:',req_url)
        http.fetch(request=req_url,
                   callback=self.on_response
                   )
    def on_response(self,response):
        if response.error:
            print('send_error:500')
            self.send_error(500)
        else:
            self.xsrf_token
            data = response.body
            print('獲取返回值response.body : ',data)
            self.write(data.decode('utf-8'))
        # 發送響應信息,結束請求處理
        self.finish()
if __name__ == '__main__':
    parse_command_line()
    options.define(name='port',default=80,type=int,help="服務監聽端口")
    url = [
        (r'/',AsyncHttpclientIndexHandler)
    ]
    BASEDIR = os.path.dirname(__file__)
    static_path = os.path.join(BASEDIR,'statics')
    template_path = os.path.join(BASEDIR,'templates')
    app = Application(url,
                      static_path=static_path,
                      template_path=template_path,
                      cookie_secret=base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes),
                      xsrf_cookies=True,
                      debug=True
                      )
    http_server = HTTPServer(app)
    http_server.listen(options.port)
    IOLoop.current().start()

 

 

@tornado.web.asynchronous

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

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

不帶此裝飾器的請求在get、post等方法返回時自動完成結束請求處理(只返回狀態碼,來不及返回任何數據)

 

 

4. 協程異步

在上一節中咱們本身封裝的裝飾器get_coroutine在Tornado中對應的是@tornado.gen.coroutine 裝飾器。

class GenAsyncIndexHandler(RequestHandler):
    '''使用yield相似協程的方式'''
    @coroutine
    def get(self, *args, **kwargs):
        req_url = "http://myip.ksyun.com"
        http = AsyncHTTPClient()
        response = yield http.fetch(req_url)
        if response.error:
            self.send_error(500)
        else:
            self.write(response.body)

 

也能夠將異步Web請求單獨出來:

class Gen2AsyncIndexHandler(RequestHandler):
    '''使用yield相似協程的方式分紅兩個函數'''
    @coroutine
    def get(self, *args, **kwargs):
        req_url = "http://myip.ksyun.com"
        rep = yield self.get_client_ip(req_url)
        print(type(rep))
        if rep != 'error':
            self.write(rep)
        else:
            self.send_error(500)
    @coroutine
    def get_client_ip(self,url):
        http = AsyncHTTPClient()
        response = yield http.fetch(url)
        if response.error:
            rep = 'error'
        else:
            rep = response.body
        raise tornado.gen.Return(rep)

 

代碼中咱們須要注意的地方是get_ip_info返回值的方式,在python 2中,使用了yield的生成器可使用不返回任何值的return,但不能return value,

所以Tornado爲咱們封裝了用於在生成器中返回值的特殊異常tornado.gen.Return,並用raise來返回此返回值。

並行協程

Tornado能夠同時執行多個異步,併發的異步可使用列表以下:

class QueryIpInfoHandler(tornado.web.RequestHandler):
    '''測試多個請求併發協程客戶端'''
    @coroutine
    def get(self):
        ips = self.get_query_arguments('ip')
        if len(ips) > 0:
            for ip in ips:
                ret = yield self.get_ip_info(ip)
                self.write_response(ip,ret)
        else:
            client_ip = self.request.remote_ip
            print(client_ip)
            ret = yield self.get_ip_info(client_ip)
            print(ret)
            self.write_response(client_ip,ret)

    def write_response(self, ip, response):
        self.write(ip)
        self.write(":<br/>")
        if None !=  response:
            self.write("&nbsp&nbsp國家:%s <br/>\
            &nbsp&nbsp省份: %s <br/>\
            &nbsp&nbsp城市: %s  <br/>\
            &nbsp&nbsp單位: %s  <br/>\
            &nbsp&nbsp運營商: %s <br/>" % (response[0],
                                                                     response[1],
                                                                     response[2],
                                                                     response[3] if response[3] != '' else "未知",
                                                                     response[4],))
        else:
            self.write("查詢IP信息錯誤<br/>")

    @tornado.gen.coroutine
    def get_ip_info(self, ip):
        http = tornado.httpclient.AsyncHTTPClient()
        response = yield http.fetch("http://freeapi.ipip.net/" + ip)
        if response.error:
            rep = None
        else:
            rep = json.loads(response.body)
        raise tornado.gen.Return(rep)

 

 

5. 關於數據庫的異步說明

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

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

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

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

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

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

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

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

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

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

相關文章
相關標籤/搜索