Tornado
简而言之,Tornado的异步包括两个方面,异步服务端和异步客户端。无论服务端和客户端,具体的异步模型又可以分为回调(callback)和协程(coroutine)。具体应用场景,也没有很明确的界限。往往一个请求服务里还包含对别的服务的客户端异步请求。
服务端异步方式
服务端异步,可以理解为一个tornado请求之内,需要做一个耗时的任务。直接写在业务逻辑里可能会block整个服务。因此可以把这个任务放到异步处理,实现异步的方式就有两种,一种是yield挂起函数,另外一种就是使用类线程池的方式。
具体实现测试code示例:
import os
import tornado
import tornado.ioloop
import tornado.web
import requests
from concurrent.futures import ThreadPoolExecutor
class Executor(ThreadPoolExecutor):
""" 创建多线程的线程池,线程池的大小为10
创建多线程时使用了单例模式,如果Executor的_instance实例已经被创建,
则不再创建,单例模式的好处在此不做讲解
"""
_instance = None
def __new__(cls, *args, **kwargs):
if not getattr(cls, '_instance', None):
cls._instance = ThreadPoolExecutor(max_workers=10)
return cls._instance
# 全部协程+异步线程池实现,yield在此的作用相当于回调函数
# 经过压力测试发现,此种方式的性能在并发量比较大的情况下,要远远优于纯协程实现方案
class Haha1Handler(tornado.web.RequestHandler):
""" 获取域名所关联的IP信息 """
# executor为RequestHandler中的一个属性,在使用run_on_executor时,必须要用,不然会报错
# executor在此设计中为设计模式中的享元模式,所有的对象共享executor的值
executor = Executor()
@tornado.web.asynchronous # 异步处理
@tornado.gen.coroutine # 使用协程调度
def get(self):
""" get 接口封装 """
# 可以同时获取POST和GET请求参数
value = self.get_argument("value", default=None)
result = yield self._process(value)
self.write(result)
@tornado.concurrent.run_on_executor # 增加并发量
def _process(self, url):
# 此处执行具体的任务
try:
resp = requests.get(url)
except IOError as e:
print(e)
return 'failed'
return 'success'
# 全部协程实现
class Haha2Handler(tornado.web.RequestHandler):
""" 获取域名所关联的IP信息 """
@tornado.web.asynchronous
@tornado.gen.coroutine
def get(self):
""" get 接口封装 """
# 可以同时获取POST和GET请求参数
value = self.get_argument("value", default=None)
result = yield tornado.gen.Task(self._process, value)
self.write(result)
@tornado.gen.coroutine # 使用协程调度
def _process(self, url):
# 此处执行具体的任务
try:
resp = requests.get(url)
except IOError as e:
print(e)
return 'failed'
return 'success'
class WebServerApplication(object):
def __init__(self, port):
self.port = port
self.settings = {'debug': False}
def make_app(self):
""" 构建Handler
(): 一个括号内为一个Handler
"""
return tornado.web.Application([
(r"/gethaha1?", Haha1Handler),
(r"/gethaha2?", Haha2Handler),
], ** self.settings)
def process(self):
""" 构建app, 监听post, 启动服务 """
app = self.make_app()
app.listen(self.port)
tornado.ioloop.IOLoop.current().start()
if __name__ == "__main__":
# 定义服务端口
server_port = "10001"
server = WebServerApplication(server_port)
server.process()
回调函数写在代码里面看着比较混乱,现在python3.6出了最新的替代方案就是使用yield代替,实现阻塞挂起的功能,使代码看上起更像是同步的代码,同时此方案在协程中被采纳;回调函数实现的异步再次没有实现,想要实现的可以查阅相关资料实现一下。
协程的出现主要是为了解决IO阻塞问题,提升CPU的利用率,增加单位时间的并发量;tornado实质是一个单线程模型,所以这个并发量还是很受全局解释锁GIL的限制的,为了改变这一现状,tornado框架引入协程模块@tornado.gen.coroutine,只要被该装饰器修饰的方法, 执行就会以协程的方式执行;一些提升性能的模块大部分被封装在@tornado.gen中,此模块值得好好研究
如果想测试自己搭建的API性能,可以采用ApacheBench带有的免费测试工具来测试,此工具使用很简易,想要了解的请查找的我的别的博客,有详细讲解
使用方式列举:
# ab -n500 -c100 http://127.0.0.1:10001/gethaha1?value=https://www.tornadoweb.org/en/stable/ioloop.html