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 ")
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#!/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. 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 timefrom celery import Celerycelery = 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()