由于epoll主要是用来解决网络IO的并发问题,因此Tornado的异步编程也主要体如今网络IO的异步上,即异步Web请求。php
Tornado提供了一个异步Web请求客户端tornado.httpclient.AsyncHTTPClient用来进行异步Web请求。前端
用于执行一个web请求request,并异步返回一个tornado.httpclient.HTTPResponse响应。python
request能够是一个url,也能够是一个tornado.httpclient.HTTPRequest对象。若是是url,fetch会本身构造一个HTTPRequest对象。web
HTTP请求类,HTTPRequest的构造函数能够接收众多构造参数,最经常使用的以下:数据库
HTTP响应类,其经常使用属性以下:编程
新浪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":""}
#!/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等方法返回时自动完成结束请求处理(只返回状态码,来不及返回任何数据)。
在上一节中咱们本身封装的装饰器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("  国家:%s <br/>\   省份: %s <br/>\   城市: %s <br/>\   单位: %s <br/>\   运营商: %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)
网站基本都会有数据库操做,而Tornado是单线程的,这意味着若是数据库查询返回过慢,整个服务器响应会被堵塞。
数据库查询,实质上也是远程的网络调用;理想状况下,是将这些操做也封装成为异步的;但Tornado对此并无提供任何支持。
这是Tornado的设计,而不是缺陷。
一个系统,要知足高流量;是必须解决数据库查询速度问题的!
数据库若存在查询性能问题,整个系统不管如何优化,数据库都会是瓶颈,拖慢整个系统!
异步并不能从本质上提到系统的性能;它仅仅是避免多余的网络响应等待,以及切换线程的CPU耗费。
若是数据库查询响应太慢,须要解决的是数据库的性能问题;而不是调用数据库的前端Web应用。
对于实时返回的数据查询,理想状况下须要确保全部数据都在内存中,数据库硬盘IO应该为0;这样的查询才能足够快;而若是数据库查询足够快,那么前端web应用也就无将数据查询封装为异步的必要。
就算是使用协程,异步程序对于同步程序始终仍是会提升复杂性;须要衡量的是处理这些额外复杂性是否值得。
若是后端有查询实在是太慢,没法绕过,Tornaod的建议是将这些查询在后端封装独立封装成为HTTP接口,而后使用Tornado内置的异步HTTP客户端进行调用。