Tornado makes your asynchronous requests non-blocking

Source: Internet
Author: User
Tags curl mongodb sleep sleep function wrapper

Some may be confused: Isn't tornado advertised that asynchronous non-blocking solves the 10 k problem? But I found that not torando is bad, but you used it wrong. for example, a website is slow to open pages, and the cpu/memory of the server is normal. the network status is also good. later, I found that there were a lot of requests to access the back-end database, and there was a rest service for the mongodb database service api. however, its tornado is wrong, and the problem is studied step by step:

Description

In the following example, there are 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 I am not familiar with the technology, in principle, he hopes that his access requests will not be affected and will not be affected by other users' requests.

The code is as follows: Copy code
#! /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 5 s ")

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


Assume that you access http: // localhost: 8000/sleep first by using a page request or tools such as httpie and curl, and then access http: // localhost: 8000/justnow. you will find that the/jsunow request that can be returned immediately will be blocked until the/sleep request is complete.

Why? Why is my request blocked by/sleep requests? If our web requests are fast enough at ordinary times, we may not be aware of this problem, but in fact there are often some time-consuming processes, which means that the application is effectively locked until the processing ends.

Does this remind you of @ tornado. web. asynchronous? However, the premise of using this decorator is that you need to execute asynchronous execution for time-consuming execution, such as the preceding time. sleep, you just add the modifier does not work, and it should be noted that Tornado closes the client connection when function processing returns by default, but when you use @ tornado. web. when asynchonous decorator is used, Tornado will never close the connection by itself. close finish ()

Most of our functions are congested. For example, the above time. sleep actually tornado has an asynchronous implementation:

The code is as follows: Copy code
#! /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 5 s ")


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

Here is a new tornado. gen. coroutine, which is a new coroutine after 3.0. The previous method is to use callback. Let's 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 5 s ")
Self. finish ()


Callback is used, but the new decorator allows us to achieve the same effect through yield: You Click/justnow after opening/sleep, and the requests of justnow are immediately returned and will not be affected. however, with the asynchronous decorator, your time-consuming functions also need to be asynchronous.

The above are all meaningless examples. The following is a bit useful: Read the mongodb database data, and then write the data in the front end by row.

The code is as follows: Copy code
#! /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 for mongodb that supports asynchronous databases
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 ):
# It takes time for this row to be blocked. My tt set has some data and has no index.
Cursor = db. tt. find (). sort ([('A',-1)])
# The asynchronous non-blocking execution of this branch does not affect other page requests
While (yield cursor. fetch_next ):
Message = cursor. next_object ()
Self. write ('<li> % s </li>' % message ['A'])
Self. write ('</ul> ')
Self. finish ()

Def _ on_response (self, message, error ):
If error:
Raise tornado. web. HTTPError (500, error)
Elif message:
For I in message:
Self. write ('<li> % s </li>' % I ['A'])
Else:
Self. write ('</ul> ')
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 ()



A colleague suggested why this time-consuming item cannot be asynchronously thrown to a tool for execution without blocking my requests? Okay, I also thought of celery. github has this thing: tornado-celery.

To execute the following program, you must first install rabbitmq and celery:

The code is as follows: Copy code
#! /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 ):
# The tornado. gen. Task parameter is the function to be executed. The parameter
Yield tornado. gen. Task (tasks. sleep. apply_async, args = [5])
Self. write ("when I sleep 5 s ")
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 is a celery task definition file, which contains the time. sleep 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 celelry worker (or how do you execute your task? Must be removed by a consumer ):

The code is as follows: Copy code

Celery-A tasks worker -- loglevel = info



However, the problem here may also be very serious: our asynchronous non-blocking depends on celery, or the length of this queue. If there are many tasks, we need to wait and the efficiency is very low. is there a way to convert my synchronous blocking function to asynchronous (or be understood and recognized by tornado decorators?

The code is as follows: Copy code
#! /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 concurrent Library is included in python3 and needs to install sudo pip install futures in python2.
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)
@ Tornado. web. asynchronous
@ Tornado. gen. coroutine
Def get (self ):
# If The asynchronous method you run will return the value to be called again, you can (just for demonstration). Otherwise, you can directly use yield.
Res = yield self. sleep ()
Self. write ("when I sleep ")
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 ()



But some friends left me a message and said, why do I still block responses when I open multiple url requests in the browser?

In this case, the browser may implement caching. This problem occurs when the requested resources are the same. You can use multiple browsers (multiple users) or the curl login in the command line won't have this problem, and there is a more evil solution:

Add some useless parameters to your request, such as http: // localhost: 8000/sleep /? A = 1 can also be a timestamp

The code is as follows: Copy code
From concurrent. futures import ThreadPoolExecutor
From functools import partial, wraps
Import time

Import tornado. ioloop
Import tornado. web

From tornado. options import define, options
Define ("port", default = 8000, help = "run on the given port", type = int)

EXECUTOR = ThreadPoolExecutor (max_workers = 4)


Def unblock (f ):

@ Tornado. web. asynchronous
@ Wraps (f)
Def wrapper (* args, ** kwargs ):
Self = args [0]

Def callback (future ):
Self. write (future. result ())
Self. finish ()

EXECUTOR. submit (
Partial (f, * args, ** kwargs)
). Add_done_callback (
Lambda future: tornado. ioloop. IOLoop. instance (). add_callback (
Partial (callback, future )))

Return wrapper


Class JustNowHandler (tornado. web. RequestHandler ):

Def get (self ):
Self. write ("I hope just now see you ")


Class SleepHandler (tornado. web. RequestHandler ):

@ Unblock
Def get (self, n ):
Time. sleep (float (n ))
Return "Awake! % S "% time. time ()


Class SleepAsyncHandler (tornado. web. RequestHandler ):

@ Tornado. web. asynchronous
Def get (self, n ):

Def callback (future ):
Self. write (future. result ())
Self. finish ()

EXECUTOR. submit (
Partial (self. get _, n)
). Add_done_callback (
Lambda future: tornado. ioloop. IOLoop. instance (). add_callback (
Partial (callback, future )))

Def get _ (self, n ):
Time. sleep (float (n ))
Return "Awake! % S "% time. time ()


Application = tornado. web. Application ([
(R "/justnow", JustNowHandler ),
(R "/sleep/(d +)", SleepHandler ),
(R "/sleep_async/(d +)", SleepAsyncHandler ),
])


If _ name _ = "_ main __":
Application. listen (options. port)
Tornado. ioloop. IOLoop. instance (). start ()

 

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.