Home >Backend Development >Python Tutorial >tornado asynchronous request non-blocking

tornado asynchronous request non-blocking

高洛峰
高洛峰Original
2016-10-17 13:58:441086browse

Foreword

Maybe some students are confused: Doesn’t tornado claim to be asynchronous and non-blocking to solve the 10K problem? But I found that it is not that torando is bad, but that you are using it wrong. For example, I recently discovered something: a website is very slow to open the page , the server CPU/memory is normal. The network status is also good. Later, I found that when opening the page, there will be many requests for back-end database access. There is a rest service of mongodb database business api. But its tornado is used incorrectly, step by step Let’s study the problem:


Explanation

The following examples have two URLs, one is a time-consuming request, and the other is a request that can or needs to be returned immediately. I think even if one is not familiar with the technology, from Logically speaking, the user hopes that his access request will not affect or be affected by other people's requests


#!/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()

If you use a page request or which httpie, curl and other tools to access http://localhost:8000 first /sleep, and then visit http://localhost:8000/justnow. You will find that the request for /jsutnow that could have been returned immediately will be blocked until the /sleep request is completed before returning.


Why is this? Why My request is blocked by /sleep request? If our web requests are usually fast enough we may not realize this problem, but in fact there are often some time-consuming processes, which means that the application is effectively locked until the end of processing.


This is the time for you Have you ever thought of the @tornado.web.asynchronous decorator? But the prerequisite for using this decorator is that you need to perform asynchronous execution for time-consuming execution, such as time.sleep above. Just adding the decorator will have no effect, and it should be noted that Tornado closes the client by default when the function returns. end connection, but when you use the @tornado.web.asynchonous decorator, Tornado will never close the connection by itself, and needs to be closed explicitly by self.finish()


Most of our functions are blocking, For example, the time.sleep above actually has an asynchronous implementation of 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): Or yield tornado.gen.task (tornado.ioloop.ioloop.instance (). Add_timeout, time.time () + 5)

Self.write ("When I Sleep 5s")

Class JustNowHandler (Tor nado.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=[

                                                                                                                                                                                                ) options.port)

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

There is a new tornado.gen.coroutine decorator. Coroutine is a new decorator after 3.0. The previous method was to use callbacks , or look at my example:

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 is used, but the new decorator allows us to achieve the same effect through yield: if you open /sleep and then click /justnow, the justnow request will return immediately without being affected. But with the asynchronous decorator, your Time-consuming functions also need to be executed asynchronously


The examples I just mentioned are all meaningless examples. Here is a useful one: read the mongodb database data, and then write it out line by line on the front end


#! /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

# A python driver produced by mongodb that supports asynchronous database

import motor

from tornado.options import define, options

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

# db is actually the cursor of the test database

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

class SleepHandler(BaseHandler):

@tornado.web .asynchronous

@tornado.gen.coroutine

def get(self):

# Execution of this line still takes time to block. My tt collection has some data and no index

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

# This part of this part will be asynchronous and non -blocking. :

                                                                                                                                                                                                                                                             can raise tornado.web.HTTPError(500, error) .write('

%s

' % i['a'])

                                                                                                                                                                                                                                                                     . 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()

    A colleague suggested why Can't this time-consuming thing be asynchronously thrown to a tool for execution without blocking my request? Well, I also thought of: celery, and github just has this thing: tornado-celery

  • Execute the following program First you need to install rabbitmq and 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. The parameters of gen.Task are: the function to be executed, parameters

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

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

                   .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 is the task definition file of celery, including the time.sleep we are talking about Function

    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()

    Then start the celelry worker (otherwise, how will your task be executed? It is definitely needed A consumer takes it away):


    celery -A tasks worker --loglevel=info

    But the problem here may also be serious: our asynchronous non-blocking depends on celery, or the length of this queue, if the task If there are many, then you need to wait, which is very inefficient. Is there a way to change my synchronous blocking function to asynchronous (or be understood and recognized by tornado's decorator)?


    #!/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

    # This concurrency library In python3, you need to install 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 is a local variable not global

    @tornado.web.asynchronous

    @tornado.gen.coroutine

    def get(self):

    # If the asynchronous execution you perform will return a value and continue to be called, you can do this (just for demonstration), otherwise just yield directly

    res = yield self.sleep() I Self.write ("WHEN I SLEEP % 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()

  • Statement:
    The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn