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