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()
posted @ 2020-08-27 13:00  SensorError  阅读(414)  评论(0编辑  收藏  举报