首页 后端开发 Python教程 tornado异步请求非阻塞

tornado异步请求非阻塞

Oct 17, 2016 pm 01:58 PM

前言

也许有同学很迷惑:tornado不是标榜异步非阻塞解决10K问题的嘛?但是我却发现不是torando不好,而是你用错了.比如最近发现一个事情:某网站打开页面很慢,服务器cpu/内存都正常.网络状态也良好. 后来发现,打开页面会有很多请求后端数据库的访问,有一个mongodb的数据库业务api的rest服务.但是它的tornado却用错了,一步步的来研究问题:


说明

以下的例子都有2个url,一个是耗时的请求,一个是可以或者说需要立刻返回的请求,我想就算一个对技术不熟,从道理上来说的用户, 他希望的是他访问的请求不会影响也不会被其他人的请求影响


#!/bin/env python

import tornado.httpserver

import tornado.ioloop

import tornado.options

import tornado.web

import tornado.httpclient

import time

from tornado.options import define, options

define("port", default=8000, help="run on the given port", type=int)

class SleepHandler(tornado.web.RequestHandler):

    def get(self):

        time.sleep(5)

        self.write("when i sleep 5s")

class JustNowHandler(tornado.web.RequestHandler):

    def get(self):

        self.write("i hope just now see you")

if __name__ == "__main__":

    tornado.options.parse_command_line()

    app = tornado.web.Application(handlers=[

            (r"/sleep", SleepHandler), (r"/justnow", JustNowHandler)])

    http_server = tornado.httpserver.HTTPServer(app)

    http_server.listen(options.port)

    tornado.ioloop.IOLoop.instance().start()

假如你使用页面请求或者使用哪个httpie,curl等工具先访问http://localhost:8000/sleep,再访问http://localhost:8000/justnow.你会发现本来可以立刻返回的/jsutnow的请求会一直阻塞到/sleep请求完才返回.


这是为啥?为啥我的请求被/sleep请求阻塞了?如果平时我们的web请求足够快我们可能不会意识到这个问题,但是事实上经常会有一些耗时的进程,意味着应用程序被有效的锁定直至处理结束.


这是时候你有没有想起@tornado.web.asynchronous这个装饰器?但是使用这个装饰器有个前提就是你要耗时的执行需要执行异步,比如上面的time.sleep,你只是加装饰器是没有作用的,而且需要注意的是 Tornado默认在函数处理返回时关闭客户端的连接,但是当你使用@tornado.web.asynchonous装饰器时,Tornado永远不会自己关闭连接,需要显式的self.finish()关闭


我们大部分的函数都是阻塞的, 比如上面的time.sleep其实tornado有个异步的实现:


#!/bin/env python

import tornado.httpserver

import tornado.ioloop

import tornado.options

import tornado.web

import tornado.gen

import tornado.httpclient

import tornado.concurrent

import tornado.ioloop

import time

from tornado.options import define, options

define("port", default=8000, help="run on the given port", type=int)

class SleepHandler(tornado.web.RequestHandler):

    @tornado.web.asynchronous

    @tornado.gen.coroutine

    def get(self):

        yield tornado.gen.Task(tornado.ioloop.IOLoop.instance().add_timeout, time.time() + 5)

        self.write("when i sleep 5s")

class JustNowHandler(tornado.web.RequestHandler):

    def get(self):

        self.write("i hope just now see you")

if __name__ == "__main__":

    tornado.options.parse_command_line()

    app = tornado.web.Application(handlers=[

            (r"/sleep", SleepHandler), (r"/justnow", JustNowHandler)])

    http_server = tornado.httpserver.HTTPServer(app)

    http_server.listen(options.port)

    tornado.ioloop.IOLoop.instance().start()

这里有个新的tornado.gen.coroutine装饰器, coroutine是3.0之后新增的装饰器.以前的办法是用回调,还是看我这个例子:


class SleepHandler(tornado.web.RequestHandler):

    @tornado.web.asynchronous

    def get(self):

        tornado.ioloop.IOLoop.instance().add_timeout(time.time() + 5, callback=self.on_response)

    def on_response(self):

        self.write("when i sleep 5s")

        self.finish()

使用了callback, 但是新的装饰器让我们通过yield实现同样的效果:你在打开/sleep之后再点击/justnow, justnow的请求都是立刻返回不受影响.但是用了asynchronous的装饰器你的耗时的函数也需要执行异步


刚才说的都是没有意义的例子,下面写个有点用的:读取mongodb数据库数据,然后再前端按行write出来


#!/bin/env python

import tornado.httpserver

import tornado.ioloop

import tornado.options

import tornado.web

import tornado.gen

import tornado.httpclient

import tornado.concurrent

import tornado.ioloop

import time

# 一个mongodb出品的支持异步的数据库的python驱动

import motor

from tornado.options import define, options

define("port", default=8000, help="run on the given port", type=int)

# db其实就是test数据库的游标

db = motor.MotorClient().open_sync().test

class SleepHandler(BaseHandler):

    @tornado.web.asynchronous

    @tornado.gen.coroutine

    def get(self):

        # 这一行执行还是阻塞需要时间的,我的tt集合有一些数据并且没有索引

        cursor = db.tt.find().sort([('a', -1)])

        # 这部分会异步非阻塞的执行二不影响其他页面请求

        while (yield cursor.fetch_next):

            message = cursor.next_object()

            self.write('

  • %s
  • ' % message['a'])

            self.write('')

            self.finish()

        def _on_response(self, message, error):

            if error:

                raise tornado.web.HTTPError(500, error)

            elif message:

                for i in message:

                    self.write('

  • %s
  • ' % i['a'])

            else:

                self.write('')

                self.finish()

    class JustNowHandler(BaseHandler):

        def get(self):

            self.write("i hope just now see you")

    if __name__ == "__main__":

        tornado.options.parse_command_line()

        app = tornado.web.Application(handlers=[

                (r"/sleep", SleepHandler), (r"/justnow", JustNowHandler)])

        http_server = tornado.httpserver.HTTPServer(app)

        http_server.listen(options.port)

        tornado.ioloop.IOLoop.instance().start()

    一个同事提示为什么这个耗时的东西不能异步的丢给某工具去执行而不阻塞我的请求呢?好吧,我也想到了:celery,正好github有这个东西:tornado-celery


    执行下面的程序首先你要安装rabbitmq和celery:


    #!/bin/env python

    import tornado.httpserver

    import tornado.ioloop

    import tornado.options

    import tornado.web

    import tornado.gen

    import tornado.httpclient

    import tcelery, tasks

    import time

    from tornado.options import define, options

    define("port", default=8000, help="run on the given port", type=int)

    tcelery.setup_nonblocking_producer()

    class SleepHandler(tornado.web.RequestHandler):

        @tornado.web.asynchronous

        @tornado.gen.coroutine

        def get(self):

            # tornado.gen.Task的参数是:要执行的函数, 参数

            yield tornado.gen.Task(tasks.sleep.apply_async, args=[5])

            self.write("when i sleep 5s")

            self.finish()

    class JustNowHandler(tornado.web.RequestHandler):

        def get(self):

            self.write("i hope just now see you")

    if __name__ == "__main__":

        tornado.options.parse_command_line()

        app = tornado.web.Application(handlers=[

                (r"/sleep", SleepHandler), (r"/justnow", JustNowHandler)])

        http_server = tornado.httpserver.HTTPServer(app)

        http_server.listen(options.port)

        tornado.ioloop.IOLoop.instance().start()

    task是celery的任务定义的文件,包含我们说的time.sleep的函数


    import time

    from celery import Celery

    celery = Celery("tasks", broker="amqp://guest:guest@localhost:5672")

    celery.conf.CELERY_RESULT_BACKEND = "amqp"

    @celery.task

    def sleep(seconds):

        time.sleep(float(seconds))

        return seconds

    if __name__ == "__main__":

        celery.start()

    然后启动celelry worker(要不然你的任务怎么执行呢?肯定需要一个消费者取走):


    celery -A tasks worker --loglevel=info

    但是这里的问题也可能很严重:我们的异步非阻塞依赖于celery,还是这个队列的长度,假如任务很多那么就需要等待,效率很低.有没有一种办法把我的同步阻塞函数变为异步(或者说被tornado的装饰器理解和识别)呢?


    #!/bin/env python

    import tornado.httpserver

    import tornado.ioloop

    import tornado.options

    import tornado.web

    import tornado.httpclient

    import tornado.gen

    from tornado.concurrent import run_on_executor

    # 这个并发库在python3自带在python2需要安装sudo pip install futures

    from concurrent.futures import ThreadPoolExecutor

    import time

    from tornado.options import define, options

    define("port", default=8000, help="run on the given port", type=int)

    class SleepHandler(tornado.web.RequestHandler):

        executor = ThreadPoolExecutor(2)

      #executor 是局部变量  不是全局的

        @tornado.web.asynchronous

        @tornado.gen.coroutine

        def get(self):

            # 假如你执行的异步会返回值被继续调用可以这样(只是为了演示),否则直接yield就行

            res = yield self.sleep()

            self.write("when i sleep %s s" % res)

            self.finish()

        @run_on_executor

        def sleep(self):

            time.sleep(5)

            return 5

    class JustNowHandler(tornado.web.RequestHandler):

        def get(self):

            self.write("i hope just now see you")

    if __name__ == "__main__":

        tornado.options.parse_command_line()

        app = tornado.web.Application(handlers=[

                (r"/sleep", SleepHandler), (r"/justnow", JustNowHandler)])

        http_server = tornado.httpserver.HTTPServer(app)

        http_server.listen(options.port)

        tornado.ioloop.IOLoop.instance().start()


    本站声明
    本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

    热AI工具

    Undresser.AI Undress

    Undresser.AI Undress

    人工智能驱动的应用程序,用于创建逼真的裸体照片

    AI Clothes Remover

    AI Clothes Remover

    用于从照片中去除衣服的在线人工智能工具。

    Undress AI Tool

    Undress AI Tool

    免费脱衣服图片

    Clothoff.io

    Clothoff.io

    AI脱衣机

    Video Face Swap

    Video Face Swap

    使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

    热工具

    记事本++7.3.1

    记事本++7.3.1

    好用且免费的代码编辑器

    SublimeText3汉化版

    SublimeText3汉化版

    中文版,非常好用

    禅工作室 13.0.1

    禅工作室 13.0.1

    功能强大的PHP集成开发环境

    Dreamweaver CS6

    Dreamweaver CS6

    视觉化网页开发工具

    SublimeText3 Mac版

    SublimeText3 Mac版

    神级代码编辑软件(SublimeText3)

    如何解决Linux终端中查看Python版本时遇到的权限问题? 如何解决Linux终端中查看Python版本时遇到的权限问题? Apr 01, 2025 pm 05:09 PM

    Linux终端中查看Python版本时遇到权限问题的解决方法当你在Linux终端中尝试查看Python的版本时,输入python...

    如何在10小时内通过项目和问题驱动的方式教计算机小白编程基础? 如何在10小时内通过项目和问题驱动的方式教计算机小白编程基础? Apr 02, 2025 am 07:18 AM

    如何在10小时内教计算机小白编程基础?如果你只有10个小时来教计算机小白一些编程知识,你会选择教些什么�...

    如何在使用 Fiddler Everywhere 进行中间人读取时避免被浏览器检测到? 如何在使用 Fiddler Everywhere 进行中间人读取时避免被浏览器检测到? Apr 02, 2025 am 07:15 AM

    使用FiddlerEverywhere进行中间人读取时如何避免被检测到当你使用FiddlerEverywhere...

    在Python中如何高效地将一个DataFrame的整列复制到另一个结构不同的DataFrame中? 在Python中如何高效地将一个DataFrame的整列复制到另一个结构不同的DataFrame中? Apr 01, 2025 pm 11:15 PM

    在使用Python的pandas库时,如何在两个结构不同的DataFrame之间进行整列复制是一个常见的问题。假设我们有两个Dat...

    Uvicorn是如何在没有serve_forever()的情况下持续监听HTTP请求的? Uvicorn是如何在没有serve_forever()的情况下持续监听HTTP请求的? Apr 01, 2025 pm 10:51 PM

    Uvicorn是如何持续监听HTTP请求的?Uvicorn是一个基于ASGI的轻量级Web服务器,其核心功能之一便是监听HTTP请求并进�...

    Python中如何通过字符串动态创建对象并调用其方法? Python中如何通过字符串动态创建对象并调用其方法? Apr 01, 2025 pm 11:18 PM

    在Python中,如何通过字符串动态创建对象并调用其方法?这是一个常见的编程需求,尤其在需要根据配置或运行...

    在Linux终端中使用python --version命令时如何解决权限问题? 在Linux终端中使用python --version命令时如何解决权限问题? Apr 02, 2025 am 06:36 AM

    Linux终端中使用python...

    如何绕过Investing.com的反爬虫机制获取新闻数据? 如何绕过Investing.com的反爬虫机制获取新闻数据? Apr 02, 2025 am 07:03 AM

    攻克Investing.com的反爬虫策略许多人尝试爬取Investing.com(https://cn.investing.com/news/latest-news)的新闻数据时,常常�...

    See all articles