https://piaosanlang.gitbooks.io/faq/tornado/tornadoyi-bu-bi-ji-ff08-yi-ff09-yi-bu-ren-wu.html
https://piaosanlang.gitbooks.io/faq/tornado/tornadoyuan-ma-fen-xi-ff08-er-ff09-yi-bu-shang-xia-wen-guan-li-ff08-stackcontext.html
https://piaosanlang.gitbooks.io/faq/tornado/tornadoyuan-ma-yue-du-ff08-yiff09-ioloop-zhi-chuang-jian-ioloop.html
http://www.jianshu.com/p/31fae7dd05ba
http://tornado-zh.readthedocs.io/zh/latest/ioloop.html
http://www.cnblogs.com/shijingjing07/p/6555439.html
同步请求:
class IndexHandler(tornado.web.RequestHandler): def get(self): client=tornado.httpclient.HTTPClient() response=client.fetch("http://test.com/list") self.write("success")
异步请求:
class IndexAsyncHandler(tornado.web.RequestHandler): @tornado.web.asynchronous def get(self): client=tornado.httpclient.AsyncHTTPClient() client.fetch("http://test.com/list",callback=self.on_response) def on_response(self,response): self.write("success") self.finish()
同步请求在请求完毕后,自动关闭连接。
异步请求保持连接开启,需要手动关闭连接。
tornado中使用@tornado.web.asynchronous装饰器作用是保持连接一直开启,
回调函数执行完毕后,调用finish方法来主动关闭连接。
http://tornado-zh.readthedocs.io/zh/latest/guide/coroutines.html?highlight=ThreadPoolExecutor
包含了 yield
关键字的函数是一个 生成器(generator). 所有的生成器都是异步的; 当调用它们的时候,会返回一个生成器对象,而不是一个执行完的结果.
5.异步生成器
上例中,是使用回调函数来做业务处理及关闭连接的。
回调函数的缺点是,可能引起回调深渊,系统将难以维护。如回调中调用回调。
def get(self): client = AsyncHTTPClient() client.fetch("http://example.com", callback=on_response) def on_response(self, response): client = AsyncHTTPClient() client.fetch("http://another.example.com/", callback=on_response2) def on_response2(self, response): client = AsyncHTTPClient() client.fetch("http://still.another.example.com/", callback=on_response3) def on_response3(self, response): [etc., etc.]
tornado2.1引入了tornado.gen模块,可以更整洁地执行异步请求。
@gen.coroutine
装饰器通过 yield
表达式和生成器进行交流, 而且通过返回一个 Future
与协程的调用方进行交互.
class IndexGenHandler(tornado.web.RequestHandler): @tornado.web.asynchronous @tornado.gen.engine def get(self): client=tornado.httpclient.AsyncHTTPClient() response=yield tornado.gen.Task(client.fetch,"http://test.com/list") self.write("success") self.finish()
tornado.gen是一个生成器(不是因为@gen.coroutine, 而是因为yield关键字,带有yield的函数就是生成器)(关于生成器参见“python生成器,函数,数组” ),
yield关键字的作用是返回控制,异步任务执行完毕后,程序在yield的地方恢复。
可以看到使用生成器,异步后业务处理不是在回调函数中完成的,看起来像同步处理一样,代码逻辑更清晰。
使用生成器和回调函数异步请求是一样的。
6.异步请求的适用场景
请求处理逻辑复杂耗时,或长时间请求数据库的时候,异步请求可以大幅提升并发请求效率。
同时综合考虑缓存,业务逻辑放在客户端等手段,来缓解服务器压力。
参考资料:http://docs.pythontab.com/tornado/introduction-to-tornado/ch5.html
=======================================================================================
异步装饰器, 还有AsyncHTTPClient都是用在tornado的web应用中的, 例如def MainHandler(tornado.web.RequestHandler), 必须启动一个io_loop, 否则不会运行
例如
from concurrent.futures import ThreadPoolExecutor import tornado.ioloop import Queue import pdb def download_finish(resp): print resp print "finish" def download(url): http_client = AsyncHTTPClient() http_client.fetch(url, callback = download_finish) url = "http://10.11.78.64/" download(url) #loop = tornado.ioloop.IOLoop.current() #loop.start()
上面并不会启动异步的客户端进行下载,你需要取消掉注释,启动loop
http://blog.chinaunix.net/uid-26000296-id-4699999.html
前言
刚开始使用用tornado时都很迷惑:tornado不是标榜异步非阻塞解决10K问题的嘛?
但为什么我在handler中有一个步骤是耗时时,整体的响应就慢下了呢?是不是torando根本就不好用。其实是你用错了,因为你没有将你的耗时任务使用tornado的异步功能实现。
下面就各种torndo响应请求,进行耗时任务处理时的各种异步场景一一总结
一、异步HTTP请求的实现
tornado是一个异步web framework,说是异步,是因为tornado server与client的网络交互是异步的,底层基于io event loop。
所有的异步思想都是一致的,python是基于linux c实现的,所以python的loop也就对外提供了定时器,添加异步任务等之类的接口。
但是如果client请求server处理的handler里面有一个阻塞的耗时操作,那么整体的server性能就会下降。
比如: 访问一个耗时的网站请求 www.douban.com/search, 这个结果要在5秒后才返回值。
当我访问的话,肯定是要等5秒钟,这时候,要是有别的客户要连接的别的页面,不堵塞的页面,你猜他能马上显示吗?不能的。。。 他也是要等当前这个5秒延迟过后,才能访问的。
幸运的是,tornado提供了一套异步机制,方便我们实现自己的异步操作。
当handler处理需要进行其余的网络操作的时候,tornado提供了一个AsyncHTTPClient来支持异步。
示例代码如下:
import json
import tornado.web
import tornado.gen
def MainHandler(tornado.web.RequestHandler):
def initialize(self):
...
@tornado.web.asynchronous
@tornado.gen.coroutine
def get(self):
url = "www.douban.com/search/"
data = {}
data['title'] = 'fury'
...
client = tornado.httpclient.AsyncHTTPClient()
response = yield client.fetch(url, method=POST, body=json.dumps(data))
self.on_response(response)
def on_response(self, resp):
body = json.loads(resp.body)
if body == None:
self.write('error')
else:
self.write(body)
return
注意:
1. 只在每个响应处理的函数(如get, post, put,delete)前加修饰器@tornado.web.asynchronous 和 @tornado.gen.coroutine, 不要在初始函数 initialize(self)前加修饰器,不然会造成各种程序错误
2. 在 on_response(self, resp)回调函数中,如果使用了 self.wirte()或self.render()函数,就不要使用 self.finish()函数, 不然,会报异常:
raise RuntimeError("finish() called twice. May be caused "
RuntimeError: finish() called twice. May be caused by using async operations without the @asynchronous decorator.
如果没有用self.wirte()或self.render()函数,就要使用self.finish()函数
上面的例子,主要有几个变化:
. 使用asynchronous decorator,它主要设置_auto_finish为false, 这样handler的get函数返回的时候tornado就不会关闭与client的连接。
. 使用AsyncHttpClient,fetch的时候提供callback函数, 这样当fetch http请求完成的时候才会去调用on_response,而不会阻塞。
. on_response调用完成之后通过finish结束与client的连接。
====================================================
高性能服务器Tornado
Python的web框架名目繁多,各有千秋。正如光荣属于希腊,伟大属于罗马。Python的优雅结合WSGI的设计,让web框架接口实现千秋一统。WSGI 把应用(Application)和服务器(Server)结合起来。Django 和 Flask 都可以结合 gunicon 搭建部署应用。
与 django 和 flask 不一样,tornado 既可以是 wsgi 应用,也可以是 wsgi 服务。当然,选择tornado更多的考量源于其单进程单线程异步IO的网络模式。高性能往往吸引人,可是有不少朋友使用之后会提出疑问,tornado号称高性能,实际使用的时候却怎么感受不到呢?
实际上,高性能源于Tornado基于Epoll(unix为kqueue)的异步网络IO。因为tornado的单线程机制,一不小心就容易写出阻塞服务(block)的代码。不但没有性能提高,反而会让性能急剧下降。因此,探索tornado的异步使用方式很有必要。
Tornado 异步使用方式
简而言之,Tornado的异步包括两个方面,
异步服务端和异步客户端。无论服务端和客户端,具体的异步模型又可以分为回调(callback,一层一层的回调)和协程(coroutine, 书写上看似同步)。
具体应用场景,也没有很明确的界限。往往一个请求服务里还包含对别的服务的客户端异步请求。
服务端异步方式
服务端异步,可以理解为一个tornado请求之内,需要做一个耗时的任务。直接写在业务逻辑里可能会block整个服务。
因此可以把这个任务放到异步处理,实现异步的方式就有两种,一种是yield挂起函数,另外一种就是使用类线程池的方式。
请看一个同步例子:
class SyncHandler(tornado.web.RequestHandler):
def get(self, *args, **kwargs):
# 耗时的代码
os.system("ping -c 2 www.google.com")
self.finish('It works')
使用ab测试一下:
ab -c 5 -n 5 http://127.0.0.1:5000/sync
#其中-n代表请求数,-c代表并发数
Server Software: TornadoServer/4.3
Server Hostname: 127.0.0.1
Server Port: 5000
Document Path: /sync
Document Length: 5 bytes
Concurrency Level: 5
Time taken for tests: 5.076 seconds
Complete requests: 5
Failed requests: 0
Total transferred: 985 bytes
HTML transferred: 25 bytes
Requests per second: 0.99 [#/sec] (mean)
Time per request: 5076.015 [ms] (mean)
Time per request: 1015.203 [ms] (mean, across all concurrent requests)
Transfer rate: 0.19 [Kbytes/sec] received
qps 仅有可怜的 0.99,姑且当成每秒处理一个请求吧。
下面祭出异步大法:
class AsyncHandler(tornado.web.RequestHandler):
@tornado.web.asynchronous
@tornado.gen.coroutine
def get(self, *args, **kwargs):
tornado.ioloop.IOLoop.instance().add_timeout(1, callback=functools.partial(self.ping, 'www.google.com')) #函数立即返回
# do something others
self.finish('It works') //结束与客户端的连接
@tornado.gen.coroutine
def ping(self, url):
os.system("ping -c 2 {}".format(url))
return 'after'
尽管在执行异步任务的时候选择了timeout 1秒,主线程的返回还是很快的。ab压测如下:
Document Path: /async
Document Length: 5 bytes
Concurrency Level: 5
Time taken for tests: 0.009 seconds
Complete requests: 5
Failed requests: 0
Total transferred: 985 bytes
HTML transferred: 25 bytes
Requests per second: 556.92 [#/sec] (mean)
Time per request: 8.978 [ms] (mean)
Time per request: 1.796 [ms] (mean, across all concurrent requests)
Transfer rate: 107.14 [Kbytes/sec] received
上述的使用方式,通过tornado的IO循环,把可以把耗时的任务放到后台异步计算,请求可以接着做别的计算。
偏函数:functools.partial
的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。
可是,经常有一些耗时的任务完成之后,我们需要返回其计算的结果。此时这种方式就不行了。车道山前必有路,只需要切换一异步方式即可。下面使用协程来改写:
class AsyncTaskHandler(tornado.web.RequestHandler):
@tornado.web.asynchronous
@tornado.gen.coroutine
def get(self, *args, **kwargs):
# yield 结果
response = yield tornado.gen.Task(self.ping, ' www.google.com')
print 'response', response
self.finish('hello')
@tornado.gen.coroutine
def ping(self, url):
os.system("ping -c 2 {}".format(url))
return 'after'
可以看到异步在处理,而结果值也被返回了。
Server Software: TornadoServer/4.3
Server Hostname: 127.0.0.1
Server Port: 5000
Document Path: /async/task
Document Length: 5 bytes
Concurrency Level: 5
Time taken for tests: 0.049 seconds
Complete requests: 5
Failed requests: 0
Total transferred: 985 bytes
HTML transferred: 25 bytes
Requests per second: 101.39 [#/sec] (mean)
Time per request: 49.314 [ms] (mean)
Time per request: 9.863 [ms] (mean, across all concurrent requests)
Transfer rate: 19.51 [Kbytes/sec] received
qps提升还是很明显的。有时候这种协程处理,未必就比同步快。在并发量很小的情况下,IO本身拉开的差距并不大。甚至协程和同步性能差不多。例如你跟博尔特跑100米肯定输给他,可是如果跟他跑2米,鹿死谁手还未定呢。
yield挂起函数协程,尽管没有block主线程。因为需要处理返回值,挂起到返回响应还是有段时间的。
另外一种使用异步和协程的方式就是在主线程之外,使用线程池,线程池依赖于futures。Python2需要额外安装。
下面使用线程池的方式修改为异步处理:
from concurrent.futures import ThreadPoolExecutor #线程池
class FutureHandler(tornado.web.RequestHandler):
executor = ThreadPoolExecutor(10)
@tornado.web.asynchronous
@tornado.gen.coroutine
def get(self, *args, **kwargs):
url = 'www.google.com'
tornado.ioloop.IOLoop.instance().add_callback(functools.partial(self.ping, url))
self.finish('It works')
@tornado.concurrent.run_on_executor
def ping(self, url):
os.system("ping -c 2 {}".format(url))
再运行ab测试:
Document Path: /future
Document Length: 5 bytes
Concurrency Level: 5
Time taken for tests: 0.003 seconds
Complete requests: 5
Failed requests: 0
Total transferred: 995 bytes
HTML transferred: 25 bytes
Requests per second: 1912.78 [#/sec] (mean)
Time per request: 2.614 [ms] (mean)
Time per request: 0.523 [ms] (mean, across all concurrent requests)
Transfer rate: 371.72 [Kbytes/sec] received
qps瞬间达到了1912.78。同时,可以看到服务器的log还在不停的输出ping的结果。
线程池想要返回值也很容易。再切换一下使用方式接口。使用tornado的gen模块下的with_timeout功能(这个功能必须在tornado>3.2的版本)。
class Executor(ThreadPoolExecutor):
_instance = None
def __new__(cls, *args, **kwargs):
if not getattr(cls, '_instance', None):
cls._instance = ThreadPoolExecutor(max_workers=10)
return cls._instance
class FutureResponseHandler(tornado.web.RequestHandler):
executor = Executor()
@tornado.web.asynchronous
@tornado.gen.coroutine
def get(self, *args, **kwargs):
future = Executor().submit(self.ping, 'www.google.com')
response = yield tornado.gen.with_timeout(datetime.timedelta(10), future,
quiet_exceptions=tornado.gen.TimeoutError)
if response:
print 'response', response.result()
@tornado.concurrent.run_on_executor
def ping(self, url):
os.system("ping -c 1 {}".format(url))
return 'after'
线程池的方式也可以通过使用tornado的yield把函数挂起,实现了协程处理。可以得出耗时任务的result,同时不会block住主线程。
Concurrency Level: 5
Time taken for tests: 0.043 seconds
Complete requests: 5
Failed requests: 0
Total transferred: 960 bytes
HTML transferred: 0 bytes
Requests per second: 116.38 [#/sec] (mean)
Time per request: 42.961 [ms] (mean)
Time per request: 8.592 [ms] (mean, across all concurrent requests)
Transfer rate: 21.82 [Kbytes/sec] received
qps为116,使用yield协程的方式,仅为非reponse的十分之一左右。看起来性能损失了很多,主要原因这个协程返回结果需要等执行完毕任务。
好比打鱼,前一种方式是撒网,然后就完事,不闻不问,时间当然快,后一种方式则撒网之后,还得收网,等待收网也是一段时间。当然,相比同步的方式还是快了千百倍,毕竟撒网还是比一只只钓比较快。
具体使用何种方式,更多的依赖业务,不需要返回值的往往需要处理callback,回调太多容易晕菜,当然如果需要很多回调嵌套,首先优化的应该是业务或产品逻辑。yield的方式很优雅,写法可以异步逻辑同步写,爽是爽了,当然也会损失一定的性能。
异步多样化
Tornado异步服务的处理大抵如此。现在异步处理的框架和库也很多,借助redis或者celery等,也可以把tonrado中一些业务异步化,放到后台执行。
此外,Tornado还有客户端异步功能。该特性主要是在于 AsyncHTTPClient的使用。此时的应用场景往往是tornado服务内,需要针对另外的IO进行请求和处理。顺便提及,上述的例子中,调用ping其实也算是一种服务内的IO处理。接下来,将会探索一下AsyncHTTPClient的使用,尤其是使用AsyncHTTPClient上传文件与转发请求。
========================
http://blog.willdx.me/2017/05/25/Tornado-%E5%8D%8F%E7%A8%8B-%E5%BC%82%E6%AD%A5-%E9%9D%9E%E9%98%BB%E5%A1%9EIO/#
在 Tornado 中两个装饰器:
Tornado 的协程异步函数有两个特点:
import time import logging import tornado.ioloop import tornado.web import tornado.options from tornado import gen tornado.options.parse_command_line() class MainHandler(tornado.web.RequestHandler): @tornado.web.asynchronous def get(self): self.write("Hello, world") self.finish() class NoBlockingHnadler(tornado.web.RequestHandler): @gen.coroutine def get(self): yield gen.sleep(10) self.write('Blocking Request') class BlockingHnadler(tornado.web.RequestHandler): def get(self): time.sleep(10) self.write('Blocking Request') def make_app(): return tornado.web.Application([ (r"/", MainHandler), (r"/block", BlockingHnadler), (r"/noblock", NoBlockingHnadler), ], autoreload=True) if __name__ == "__main__": app = make_app() app.listen(8000) tornado.ioloop.IOLoop.current().start()
当我们使用 yield gen.sleep(10) 这个异步的 sleep 时候其他请求是不阻塞的:
当使用 time.sleep(10) 时候会阻塞其他的请求, 因为time.sleep方法并不支持Tornado的异步方式; 所以这种实现异步的方式有一定的局限性;
注释:
这里的异步非阻塞是针对另一请求来说的,本次的请求该是阻塞的仍然是阻塞的
gen.coroutine 在 Tornado 3.1 后会自动调用 self.finish() 结束请求,可以不使用asynchronous 装饰器