tornado原理介绍及异步非阻塞实现方式
tornado原理介绍及异步非阻塞实现方式
以下内容根据自己实操和理解进行的整理,欢迎交流~
在tornado的开发中,我们一般会见到以下四个组成部分。
- ioloop:
同一个ioloop实例运行在一个单线程环境下。
tornado.ioloop.IOLoop.current().start()
- app
可以有多个app,一般使用一个。会挂接一个或多个服务端套接字端口对外提供服务。
app = tornado.web.Application([
                                (r"/api/predict", PredictHandler),
                                (....)
                                ], debug=False
                                )
- 路由表
将服务url与handler对应起来,形成一个路由映射表。当请求到来时,根据请求的访问url查询路由映射表来找到相应的业务handler。
[
    (r"/api/predict", PredictHandler),
    (....)
]
- handler
开发时编写的业务逻辑。可以有多个handler,为了可以通过不同的url访问handler,增加一个handler就需要增加一个路由寻址。
class Handler_1(RequestHandler):
    def get(self, *args, **kwargs):
                time.sleep(5)
                self.write("done")
class Handler_2(RequestHandler):
    def get(self, *args, **kwargs):
                time.sleep(5)
                self.write("done")
app = tornado.web.Application([
                            (r"/api/predict_1", Handler_1),
                            (r"/api/predict_2", Handler_2)
                            ], debug=False)
tornado的请求处理流程
- 当一个请求到来时,ioloop读取这个请求,解包成一个http请求对象;
- tornado找到该请求对象中对应app的路由表,通过路由表查询挂接的handler;
- 执行handler。handler方法执行后一般会返回一个对象;
- ioloop负责将对象包装成http响应对象序列化发送给客户端;
 
异步
- 什么是异步 - tornado的异步非阻塞是针对另一请求来说的,本次的请求如果没有执行完,那依然需要继续执行。 - python代码的异步和tornado的异步差异? - Python里面的异步是针对代码段来讲,异步的代码段相当于放进后台执行,CPU接着执行异步代码段下一行的代码,这样可以充分利用CPU,毕竟IO的速度可比不上CPU的运行速度,一直等着IO结束多浪费时间。 
- 为什么需要异步? - 提高CPU利用和服务并发 - 在传统的同步web服务器中,网络为了实现多并发的功能,就需要和每一个用户保持长连接,这就需要向每一个用户分配一个线程,这是非常昂贵的资源支出。而在进行IO操作时,CPU是处于闲置的状态。 - 为了解决上述问题,tornado采用了单线程事件循环,减少并发连接的成本。一次只有一个线程在工作。要想用单线程实现并发,这就要求应用程序是异步非阻塞的。 - 需要注意的是tornado的高性能源于Tornado基于Epoll的异步网络IO。但是因为tornado的单线程机制,很容易写出阻塞服务(block)的代码。不但没有性能提高,反而会让性能急剧下降。 
- tornado实现异步的方式 - 在一个tornado请求之内,需要做一个I/O耗时的任务。直接写在业务逻辑里可能会block整个服务(也就是其他请求无法访问此服务)。因此可以把这个任务放到异步处理,实现异步的方式就有两种,一种是yield挂起函数,另外一种就是使用类线程池的方式。 - 一般网上都会说‘yield生成器方式’,‘使用协程方法的异步非阻塞’,但是都没有提及到特别重要的一点,那就是yield挂起的函数必须是非阻塞函数。如果写了使用了异步方法,但是写了阻塞函数,那么处理请求的方式仍然是同步阻塞的。 
同步阻塞:
并发请求多路由地址:
- 若handler里面没有耗时IO操作,则会立马返回,多个并行访问感觉上是并行的,实际上由于tornado单线程事件循环机制,实际上是串行处理请求。
- 若handler里面有耗时IO操作,不会立马返回,会阻塞在耗时IO操作里面;这时并发请求会阻塞住(因为在排队),迟迟返不回结果。
产生上述的原因是由于taonado的单线程事件循环,每次只有一个线程执行操作。如果线程正在处理阻塞函数,就不能重新获得一个连接,处理并发的请求。
所以tornado要求里面的业务逻辑是异步非阻塞。
异步非阻塞——协程
协程/生成器
tornado推荐使用协程实现异步的方法。python 关键字 yield来实现异步。
Tornado的异步条件:要使用到异步,就必须把IO操作变成非阻塞的IO。这一点非常重要,否则就达不到异步的效果。通过异步,可以释放线程,线程从连接队列获取一个新的连接请求,从而可以处理其他请求。
当采用协程+非阻塞函数进行异步处理时,不管这个IO操作是否有返回结果,当前路由不会跳过耗时函数执行下一行代码。前面说过了,tornado的异步与python的异步不是一回事,或者说针对的对象不一样。但是线程不会一直干等着,在等待的时候可以干别的事情,于是就去重新连接了一个请求,与新的请求打的火热。原来的IO操作执行完毕了,会通知线程返回继续执行下一行代码,
此种方式的严重缺点:
使用 coroutine 方式严重依赖第三方库(需要支持异步)的实现,如果库本身不支持 Tornado 的异步操作,再怎么使用协程也依然会是阻塞的;或者可以参考内置异步客户端,借助tornado.ioloop.IOLoop封装一个自己的异步客户端,但开发成本太高。
基于协程的编程
class SleepHandler(BaseHandler):
    """
    异步的延时10秒
    """
    @gen.coroutine
    def get(self):
        yield gen.sleep(10) # 这里必须是异步函数,I/O操作,否则仍然会阻塞
        self.write("when i sleep 5s")
对于不支持异步的耗时操作,如何使服务不阻塞,可以继续处理其他请求呢?
那就是:基于线程的异步编程
异步非阻塞——线程池异步
由于python解释器使用GIL,多线程只能提高IO的并发能力,不能提高计算的并发能力。因此可以考虑通过子进程的方式,适当增加提供服务的进程数,提高整个系统服务能力的上限
基于线程池的方式,能让tornado的阻塞过程变成非阻塞,其原理是在tornado本身这个线程之外启动一个线程执行阻塞程序,从而变成非阻塞。
线程池为RequestHandler持有,请求处理逻辑中的耗时/阻塞任务可以提交给线程池处理,主循环逻辑可以继续处理其他请求,线程池内的任务处理完毕后,会通过回调注册callback到ioloop,ioloop可以通过执行callback恢复挂起的请求处理逻辑。
需要添加的代码:
- 创建线程池:executor = ThreadPoolExecutor(10)
- @tornado.gen.coroutine # 使用协程调度 + yield
- @tornado.concurrent.run_on_executor
优点:
异步非阻塞服务
小负载的工作,可以起到很好的效果
缺点:
如果大量使用线程化的异步函数做一些高负载的活动,会导致Tornado进程性能低下响应缓慢;
from concurrent.futures import ThreadPoolExecutor
class Executor(ThreadPoolExecutor):
    """ 单例模式
    """
    _instance = None
    def __new__(cls, *args, **kwargs):
        if not getattr(cls, '_instance', None):
            thred_num = 10 # 线程池数量
            cls._instance = ThreadPoolExecutor(max_workers=thred_num)
        return cls._instance
class PredictHandler(RequestHandler):
        # 1.使用单例模式
    executor = Executor()
        # 2.直接创建
        # executor = ThreadPoolExecutor(10)
        @tornado.gen.coroutine  # 使用协程调度
    def get(self, *args, **kwargs):
            result = yield self.main_process(url)
                self.write(....)
        @tornado.concurrent.run_on_executor
    def main_process(self,url):
                #  do something
                # 会让tornado阻塞的行为
        return sa_result
def create_app():
    return tornado.web.Application([
        (r"/api/predict", PredictHandler),
    ], debug=False) # 开启多进程后,一定要将 debug 设置为 False
app = create_app()
app.listen(8501)
tornado.ioloop.IOLoop.current().start()
如果函数做的是高负载该怎么办?
使用:Tornado 结合 Celery
Tornado 结合 Celery tornado-celery
Celery 是一个简单、灵活且可靠的,处理大量消息的分布式系统,它是一个专注于实时处理的任务队列, 同时也支持任务调度。
它是一个分布式的实时处理消息队列调度系统,tornado接到请求后,可以把所有的复杂业务逻辑处理、数据库操作以及IO等各种耗时的同步任务交给celery,由这个任务队列异步处理完后,再返回给tornado。这样只要保证tornado和celery的交互是异步的,那么整个服务是完全异步的。
参考:
https://segmentfault.com/a/1190000015619549
https://www.jianshu.com/p/de7f04e65618
https://juejin.cn/post/6844904179564183565
https://blog.csdn.net/permike/article/details/51783528
https://blog.csdn.net/qq_16912257/article/details/78705587
https://segmentfault.com/a/1190000016610210
https://blog.csdn.net/iin729/article/details/109908963
tornado原理介绍及异步非阻塞实现方式的更多相关文章
- 在nginx启动后,如果我们要操作nginx,要怎么做呢  别增加无谓的上下文切换 异步非阻塞的方式来处理请求  worker的个数为cpu的核数  红黑树
		nginx平台初探(100%) — Nginx开发从入门到精通 http://ten 众所周知,nginx性能高,而nginx的高性能与其架构是分不开的.那么nginx究竟是怎么样的呢?这一节我们先来 ... 
- 利用tornado使请求实现异步非阻塞
		基本IO模型 网上搜了很多关于同步异步,阻塞非阻塞的说法,理解还是不能很透彻,有必要买书看下. 参考:使用异步 I/O 大大提高应用程序的性能 怎样理解阻塞非阻塞与同步异步的区别? 同步和异步:主要关 ... 
- Python web框架 Tornado(二)异步非阻塞
		异步非阻塞 阻塞式:(适用于所有框架,Django,Flask,Tornado,Bottle) 一个请求到来未处理完成,后续一直等待 解决方案:多线程,多进程 异步非阻塞(存在IO请求): Torna ... 
- 使用tornado让你的请求异步非阻塞
		http://www.dongwm.com/archives/shi-yong-tornadorang-ni-de-qing-qiu-yi-bu-fei-zu-sai/?utm_source=tuic ... 
- Python的异步编程[0] -> 协程[1] -> 使用协程建立自己的异步非阻塞模型
		使用协程建立自己的异步非阻塞模型 接下来例子中,将使用纯粹的Python编码搭建一个异步模型,相当于自己构建的一个asyncio模块,这也许能对asyncio模块底层实现的理解有更大的帮助.主要参考为 ... 
- nginx学习(二)——基础概念之异步非阻塞
		上面讲了很多关于nginx的进程模型,接下来,我们来看看nginx是如何处理事件的. 有人可能要问了,nginx采用多worker的方式来处理请求,每个worker里面只有一个主线程,那能够处理的并发 ... 
- Tornado异步非阻塞的使用以及原理
		Tornado 和现在的主流 Web 服务器框架(包括大多数 Python 的框架)有着明显的区别:它是非阻塞式服务器,而且速度相当快.得利于其 非阻塞的方式和对 epoll 的运用,Tornado ... 
- 03: 自定义异步非阻塞tornado框架
		目录:Tornado其他篇 01: tornado基础篇 02: tornado进阶篇 03: 自定义异步非阻塞tornado框架 04: 打开tornado源码剖析处理过程 目录: 1.1 源码 1 ... 
- 异步非阻塞IO的Python Web框架--Tornado
		Tornado的全称是Torado Web Server,从名字上就可知它可用作Web服务器,但同时它也是一个Python Web的开发框架.最初是在FriendFeed公司的网站上使用,FaceBo ... 
- Tornado的异步非阻塞
		阻塞和非阻塞Web框架 只有Tornado和Node.js是异步非阻塞的,其他所有的web框架都是阻塞式的. Tornado阻塞和非阻塞两种模式都支持. 阻塞式: 代表:Django.Flask.To ... 
随机推荐
- 齐博X1-栏目的终极方法get_sort
			本节说明栏目的最终方法get_sort 我们之前讲的一系列fun函数调用栏目的方法都是基于get_sort这个公共方法而来 我们来看下这个函数的具体参数这个方法有四个参数: id:也就是栏目id,经常 ... 
- llinux下mysql建库、新建用户、用户授权、修改用户密码
			1.创建新的数据库 1.1.root用户登录mysql mysql -u root -p 1.2.查看现有数据库 show databases; 1.3.新建数据库,此命名为cjc create ... 
- Springboot 一行代码实现文件上传 20个平台!少写代码到极致
			大家好,我是小富~ 技术交流,公众号:程序员小富 又是做好人好事的一天,有个小可爱私下问我有没有好用的springboot文件上传工具,这不巧了嘛,正好我私藏了一个好东西,顺便给小伙伴们也分享一下,d ... 
- 【JavaWeb】学习笔记——Servlet、Filter、Listenter
			Servlet Servlet 简介 Servlet 是 Java提供的一门动态web资源开发技术 Servlet 是JavaEE 规范之一,其实就是一个接口,将来我们需要定义Servlet类实现Se ... 
- C#-多线程的使用Tread
			首先是概念,什么是线程? 线程是操作系统分配CPU时间的基本单元,在一个进程中可以有多个线程同时执行代码. 谈一谈什么是进程? 简单的说,一个正在运行的应用程序可以视为一个进程,进程间相互独立,资源不 ... 
- 手把手教你从安装CentOS7.4镜像开始,搭建IoT视频监控系统
			摘要:在CentOS7.4服务器版本的环境下安装nginx服务器.配置文件服务器.流媒体服务器. 本文分享自华为云社区<华为云ECS服务器安装CentOS7.4镜像,部署GINX服务器.搭建物联 ... 
- 四、Django中使用celery
			项目跟目录创建celery包,目录结构如下: mycelery/ ├── config.py ├── __init__.py ├── main.py └── sms/ ├── __init__.py ... 
- 类视图函数 VIEW
			常用的视图函数: ListView.DetailView.UpdateView 1 ListView object_list:此属性表示对象的列表 常用场景: 1.展示数据库中信息: 2.在展示信息时 ... 
- SpringBoot 03: 常用web组件 - - - 拦截器 + Servlet + 过滤器
			常用web组件 拦截器 Servlet 过滤器 使用思想 创建自定义类 实现或者继承框架里的接口或类 将自定义类注册到框架中 使用自定义类 拦截器 说明 拦截器是SpringMVC中的一种对象,能拦截 ... 
- 【题解】[ARC113C] String Invasion
			题面传送门 解决思路 题目大意是给你一个字符串 \(s\) ,定义一次操作为对于长度为 \(3\) 的一个子段,满足 \(s_i=s_{i+1}\ne s_{i+2}\),则可以将 \(s_{i+2} ... 
