tornado - 异步非阻塞
阻塞式IO框架 (Django、Flask)
大多数的web框架都是阻塞式的,如果一个请求到达服务端且为处理完该请求,后序请求会一致等待。
常用的解决方案:开启多线程/多进程,提高并发。 但是比较浪费系统资源
tornado多进程模式(仅linux):
import tornado.ioloop
import time
import tornado.web
from tornado.httpserver import HTTPServer
class IndexHadlar(tornado.web.RequestHandler):
def get(self):
print('请求开始')
time.sleep(10)
self.write('hello,world ')
print("请求结束")
application=tornado.web.Application([
(r'/index',IndexHadlar)
])
if __name__ == '__main__':
# 单线程模式
# application.listen(8888)
# tornado.ioloop.IOLoop.instance().start()
# 多线程模式
server=HTTPServer(application)
server.bind(8888)
server.start(3) #开启4个进程
tornado.ioloop.IOLoop.instance().start()
Tornado异步非阻塞
异步非阻塞就是在服务端结合IO多路复用select/poll/epoll模板,做到1个线程在遇到IO操作的情况下,还可以做一些其他的任务(协程化);Tornado默认是阻塞的同时也支持异步非阻塞功能。
处理流程:
1.客户端发送请求如果请求内容不涉及IO操作(连接数据、还得去其他网站获取内容)服务端直接响应客户端;
2.如果请求内容涉及IO操作,服务端把本次连接的socket信息添加到socket监听列表epoll中监听起来;
然后去连接其它socket(数据库、其它站点)由于是不阻塞的所以服务端把这次发送socket信息也监听起来;(一直循环监听epoll,直到socket监听列表中的socket发生变化)
3.把socket全部监听之后,就可以去继续接收其它请求了,如果检测到socket监听列表中的socket有变化(有数据返回),找到对应socket响应数据,并从socket监听列表中剔除;
小结:
Tornado的异步非阻塞,本质上是请求到达视图 1、先yield 一个Future对象 2、 IO多路复用模块把该socket添加到监听列表epoll循环监听起来;3、 循环监听过程中哪1个socket发生变化有response,执行 Future.set_result(response),请求至此返回结束,否则socket连接一直不断开,IO多路复用模块epoll一直循环监听socket是否发生变化。
非阻塞模式:
当发送GET请求时,由于方法被@gen.coroutine装饰且yield 一个 Future对象,那么Tornado会等待,等待用户向Future对象中放置数据或者发送信号,如果获取到数据或信号之后,就开始执行doing方法。
异步非阻塞体现在当在Tornaod等待用户向Future对象中放置数据时,还可以处理其他请求。
注意:在等待用户向Future对象中放置数据或信号时,此连接是不断开的。
import tornado.ioloop
import time
import tornado.web
from tornado import gen #导入
from tornado.concurrent import Future
class IndexHadlar(tornado.web.RequestHandler):
@gen.coroutine # coroutine(协程装饰器)
def get(self):
print('请求开始')
future=Future()
tornado.ioloop.IOLoop.current().add_timeout(time.time()+10,self.doing)
yield future #yield 1个future对象,IO之后自动切换到doing方法执行;
def doing(self):
self.write('请求完成')
self.finish() #关闭连接
application=tornado.web.Application([
(r'/index/',IndexHadlar)
])
if __name__ == '__main__':
# 单进程模式
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
Tornado httpclient:
如果服务端接受到客户端的请求,需要去其他API获取数据,再响应给客户端,这就涉及到了IO操作,Tornado提供了httpclient
类库用于发送Http请求,其配合Tornado的异步非阻塞使用。
import tornado.web
from tornado import gen
from tornado import httpclient
class AsyncHandler(RequestHandler):
@gen.coroutine # 协程
def get(self):
print('请求开始')
http = httpclient.AsyncHTTPClient()
yield http.fetch('https://baidu.com',self.done) # 获取到这个地址的数据或者信号后,就开始执行done方法
def done(self,respose,*args,**kwargs):
# print(respose) # 获取yield的结果
self.write(respose.body)
self.finish()
application = tornado.web.Application([
(r"/zhanggen/", AsyncHandler),
])
if __name__ == '__main__':
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
使用异步对象处理耗时操作,比如上面的AsyncHTTPClient。
调用yield关键字获取异步对象的处理结果,self.done是一个callback回调函数。
异步非阻塞体现在当在Tornaod等待请求的数据返回时,还可以处理其他请求。
Tornado-MySQL类库
如果服务端接收到客户端请求,需要连接数据库再把查询的结果响应客户端,这个过程中连接数据、发送查询SQL、接收数据库返回结果 都会遇到IO阻塞、耗时的问题,所以Tornado提供了Tornado-MySQL模块(对PyMySQL进行二次封装),让我们在使用数据库的时候也可以做到异步非阻塞。
方式一:
需要对每个IO操作分别yeild,操作起来比较繁琐。
# yield cur.execute("SELECT name,email FROM web_models_userprofile where name=%s", (user,))
方式二:可以通过task的方式把IO操作封装到函数中统一进行异步处理(无论什么方式本质都会yelid 1个Future对象);
"""
需要先安装支持异步操作Mysql的类库:
Tornado-MySQL: https://github.com/PyMySQL/Tornado-MySQL#installation
pip3 install Tornado-MySQL
"""
import tornado.web
from tornado import gen
import tornado_mysql
from tornado_mysql import pools
POOL = pools.Pool(
dict(host='127.0.0.1', port=3306, user='root', passwd='123', db='cmdb'),
max_idle_connections=1,
max_recycle_sec=3)
@gen.coroutine
def get_user_by_conn_pool(user):
cur = yield POOL.execute("SELECT SLEEP(%s)", (user,))
row = cur.fetchone()
raise gen.Return(row)
@gen.coroutine
def get_user(user):
conn = yield tornado_mysql.connect(host='127.0.0.1', port=3306, user='root', passwd='123', db='cmdb',charset='utf8')
cur = conn.cursor()
# yield cur.execute("SELECT name,email FROM web_models_userprofile where name=%s", (user,))
yield cur.execute("select sleep(10)")
row = cur.fetchone()
cur.close()
conn.close()
raise gen.Return(row)
class LoginHandler(tornado.web.RequestHandler):
def get(self, *args, **kwargs):
self.render('login.html')
@gen.coroutine
def post(self, *args, **kwargs):
user = self.get_argument('user')
data = yield gen.Task(get_user, user) #把函数添加任务
if data:
print(data)
self.redirect('http://www.xxx.com')
else:
self.render('login.html')
application = tornado.web.Application([
(r"/login", LoginHandler),
])
if __name__ == "__main__":
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()