The asynchronous task of Python's Tornado framework and Asynchttpclient

Source: Internet
Author: User
Tags nets
High-Performance Server tornado
Python's web framework is a very, very numerous. Just as Glory belongs to Greece, greatness belongs to Rome. Python's elegance combined with WSGI's design allows the web framework interface to achieve unified. WSGI combines applications (application) and servers (server). Both Django and Flask can deploy applications in conjunction with Gunicon.

Unlike Django and flask, Tornado can be either a WSGI application or a WSGI service. Of course, the choice of tornado more considerations stems from its single-process one-thread asynchronous IO network mode. High performance is often attractive, but a lot of friends will ask questions after use, Tornado claims high performance, the actual use of the time but how can not feel it?

In fact, high performance originates from Tornado asynchronous network IO based on Epoll (Unix-Kqueue). Because of the tornado single-threaded mechanism, it is easy to write down the blocking service (block) code accidentally. Not only does not improve performance, but it will cause a sharp decline in performance. Therefore, it is necessary to explore how tornado is used asynchronously.

Tornado How to use Async
In short, Tornado's async consists of two aspects, asynchronous server side and asynchronous client. The specific asynchronous model can be divided into callbacks (callback) and co-processes (Coroutine), regardless of the server and client. Specific application scenarios, there is no clear boundaries. Often a request service also contains client asynchronous requests for other services.

Server-side Async mode
Service-side asynchrony, which can be understood as a tornado request, requires a time-consuming task. Writing directly in the business logic may block the entire service. So you can put this task into asynchronous processing, there are two ways to implement Async, one is the yield hang function, the other is the way to use the class thread pool. Take a look at a synchronization example:

Class SyncHandler (Tornado.web.RequestHandler):  def get (self, *args, **kwargs):    # Time-Consuming code    os.system ("Ping- C 2 www.google.com ")    self.finish (' It works ')

Test with AB:

Ab-c 5-n 5 Http://127.0.0.1:5000/sync

Server software:    tornadoserver/4.3server Hostname:    127.0.0.1Server Port:      5000Document Path:     / Syncdocument Length:    5 bytesconcurrency level:   5Time taken for tests:  5.076 Secondscomplete requests:   5Failed requests:    0Total transferred:   985 byteshtml Transferred:    bytesrequests per second:  0.99 [#/sec] (mean) time per request:    5076.015 [MS] (mean) time per request:    1015.203 [MS] (mean, across all Concurrent requests) Transfer rate:     0.19 [Kbytes/sec] Received

The QPS only has a pathetic 0.99, so consider processing a request every second.

The following is an offering of asynchronous Dafa:

Class Asynchandler (Tornado.web.RequestHandler):  @tornado. web.asynchronous  @tornado. Gen.coroutine  def get (self, *args, **kwargs):    tornado.ioloop.IOLoop.instance (). Add_timeout (1, callback=functools.partial ( self.ping, ' www.google.com ')    # do something others    self.finish (' It works ')  @tornado. Gen.coroutine  def ping (self, URL):    os.system ("Ping-c 2 {}". Format (URL))    return ' after '

Although a timeout of 1 seconds is chosen when performing an asynchronous task, the return of the main thread is very fast. AB pressure measurement is as follows:

Document Path:     /asyncdocument Length:    5 bytesconcurrency level:   5Time taken for tests:  0.009 Secondscomplete requests:   5Failed requests:    0Total transferred:   985 byteshtml Transferred:    25 Bytesrequests per Second:  556.92 [#/sec] (mean) time per request:    8.978 [MS] (mean) time per request:    1.796 [MS] (mean, across all concurrent requests) Transfer Rate:     107.14 [Kbytes/sec] Received

The use of the above, through the Tornado IO loop, can put time-consuming tasks into the background of asynchronous computation, the request can be followed by other calculations. However, there are often time-consuming tasks to complete, and we need the results of their calculations. It's not going to happen this way. There must be a way in front of the driveway, just switch to an asynchronous way. The following uses a thread to rewrite:

Class Asynctaskhandler (Tornado.web.RequestHandler):  @tornado. web.asynchronous  @tornado. gen.coroutine  def get (self, *args, **kwargs):    # yield result    response = yield Tornado.gen.Task (self.ping, ' www.google.com ') C5/>print ' response ', Response    self.finish (' hello ')  @tornado. Gen.coroutine  def ping (self, url):    Os.system ("Ping-c 2 {}". Format (URL))    return ' after '

You can see that the asynchronous is processing, and the resulting value is returned.

Server software:    tornadoserver/4.3server Hostname:    127.0.0.1Server Port:      5000Document Path:     /async /taskdocument Length:    5 bytesconcurrency level:   5Time taken for tests:  0.049 secondscomplete requests:   5Failed requests:    0Total transferred:   985 byteshtml Transferred:    bytesrequests per second  : 101.39 [#/sec] (mean) time per request:    49.314 [MS] (mean) time per request:    9.863 [MS] (mean, across all Concurre NT requests) Transfer rate:     19.51 [Kbytes/sec] Received

The QPS ascension is still obvious. Sometimes this kind of process is not necessarily faster than synchronization. In the case of small concurrency, the IO itself is not a big gap. Even the co-process and synchronization performance are similar. For example, you and bolt run 100 meters must lose to him, but if run with him 2 meters, winner still undecided.

The yield suspend function coprocessor, although there is no block main thread, because the return value needs to be processed, suspend to respond to execution or have time to wait, relative to a single request. Another way to use asynchronous and co-processes is to use a thread pool outside the main thread, and the thread pool relies on futures. Python2 requires additional installation.

The following method is modified for asynchronous processing using the thread pool:

From concurrent.futures import Threadpoolexecutorclass Futurehandler (tornado.web.RequestHandler):  executor = Threadpoolexecutor  @tornado. web.asynchronous  @tornado. Gen.coroutine  def get (self, *args, **kwargs ):    url = ' www.google.com '    tornado.ioloop.IOLoop.instance (). Add_callback (functools.partial (self.ping, URL) )    self.finish (' It works ')  @tornado. Concurrent.run_on_executor  def ping (self, URL):    os.system (" Ping-c 2 {} ". Format (URL))

Run the AB test again:

Document Path:     /futuredocument Length:    5 bytesconcurrency level:   5Time taken for tests:  0.003 Secondscomplete requests:   5Failed requests:    0Total transferred:   995 byteshtml transferred:    25 Bytesrequests per Second:  1912.78 [#/sec] (mean) time per request:    2.614 [MS] (mean) time per request:    0.523 [MS] (mean, across all concurrent requests) Transfer rate:     371.72 [Kbytes/sec] Received

The QPS instantly reached 1912.78. At the same time, you can see that the log of the server is also continuously output ping results.
It is also easy to return a value. Then switch the usage interface. Use the With_timeout function under the Gen module of tornado (this feature must be in the tornado>3.2 version).

Class Executor (Threadpoolexecutor):  _instance = None  def __new__ (CLS, *args, **kwargs):    If not getattr (CLS , ' _instance ', None):      cls._instance = Threadpoolexecutor (max_workers=10)    return Cls._instanceclass Futureresponsehandler (Tornado.web.RequestHandler):  executor = executor ()  @tornado. web.asynchronous  @ Tornado.gen.coroutine  def get (self, *args, **kwargs):    The future = Executor (). Submit (Self.ping, ' www.google.com ' )    response = yield tornado.gen.with_timeout (Datetime.timedelta), future,                         quiet_exceptions= TORNADO.GEN.TIMEOUTERROR)    If response:      print ' response ', Response.result ()  @tornado. Concurrent.run _on_executor  def ping (self, URL):    os.system ("Ping-c 1 {}". Format (URL))    return ' after '

The thread pool can also be suspended by using the yield of tornado, which enables the processing of the threads. The result of a time-consuming task can be obtained without block the main thread.

Concurrency level:   5Time taken for tests:  0.043 secondscomplete requests:   5Failed requests:    0Total Transferred:   960 byteshtml transferred:    0 bytesrequests per second:  116.38 [#/sec] (mean) time per request:< c7/>42.961 [MS] (mean) time per request:    8.592 [MS] (mean, across all concurrent requests) Transfer rate:     21.82 [Kby TES/SEC] Received

The QPS is 116, and the yield association is used only about one-tenth of the non-reponse. It seems that the performance is lost a lot, the main reason is that this process returns the result needs to wait for completion of the task.

Like fishing, the first way is to cast a net, and then finished, indifferent, time is of course fast, after a way to cast nets, but also to receive nets, waiting for the net is a period of time. Of course, compared to the way of synchronization is fast appearances, after all, the net is more than a fishing faster.

Specific use of the way, more dependent on the business, do not need to return the value of the often need to deal with callback, callback too much prone to faint dish, of course, if a lot of callback nesting, the first optimization should be business or product logic. Yield way is elegant, writing can be asynchronous logic synchronous write, Cool is cool, of course, will also lose a certain performance.

Asynchronous diversification
This is probably the case with Tornado asynchronous services. There are also many frameworks and libraries for asynchronous processing, and with redis or celery, some of the business in Tonrado can be asynchronously executed in the background.

In addition, Tornado also has client async functionality. The main feature is the use of asynchttpclient. The scenario at this time is often within the Tornado service and requires additional IO for request and processing. By the way, in the above example, calling ping is actually an IO processing within a service. Next, you'll explore the use of asynchttpclient, especially using asynchttpclient to upload files and forward requests.

Asynchronous client
The common practice of understanding tornado asynchronous tasks is described earlier as asynchronous services. Typically within our services, we also need to request third-party services asynchronously. Python's library Requests is the best library for HTTP requests, not one of them. The official website declares: HTTP for Human. However, the direct use of requests in tornado will be a nightmare. Requests requests block the entire service process.

When God closes the door, he often opens a window. Tornado provides an asynchronous HTTP client based on the framework itself (and, of course, a synchronous client)---asynchttpclient.

Asynchttpclient Basic Usage
Asynchttpclient is an asynchronous HTTP client provided by Tornado.httpclinet. Use is also relatively simple. As with the service process, asynchttpclient can also be used in the callback and yield two ways. The former does not return a result, and the latter returns response.

If requesting a third-party service is synchronous, it also kills performance.

Class SyncHandler (Tornado.web.RequestHandler):  def get (self, *args, **kwargs):    url = ' https://api.github.com /'    resp = requests.get (URL)    print resp.status_code    self.finish (' It works ')

Using the AB test is probably as follows:

Document Path:     /syncdocument Length:    5 bytesconcurrency level:   5Time taken for tests:  10.255 Secondscomplete requests:   5Failed requests:    0Total transferred:   985 byteshtml Transferred:    25 Bytesrequests per Second:  0.49 [#/sec] (mean) time per request:    10255.051 [MS] (mean) time per request:    2051.010 [MS] (mean, across all concurrent requests) Transfer rate:     0.09 [Kbytes/sec] Received

The performance is quite slow, replaced by Asynchttpclient re-test:

Class Asynchandler (Tornado.web.RequestHandler):  @tornado. web.asynchronous  def get (self, *args, **kwargs) :    url = ' https://api.github.com/'    http_client = tornado.httpclient.AsyncHTTPClient ()    HTTP_ Client.fetch (URL, self.on_response)    self.finish (' It works ')  @tornado. Gen.coroutine  def on_response (Self, Response):    print Response.code

QPS has improved a lot

Document Path:     /asyncdocument Length:    5 bytesconcurrency level:   5Time taken for tests:  0.162 Secondscomplete requests:   5Failed requests:    0Total transferred:   985 byteshtml Transferred:    25 Bytesrequests per Second:  30.92 [#/sec] (mean) time per request:    161.714 [MS] (mean) time per request:    32.343 [MS] (mean, across all concurrent requests) Transfer Rate:     5.95 [Kbytes/sec] Received

Similarly, in order to obtain the results of response, only the yield function is required.

Class Asyncresponsehandler (Tornado.web.RequestHandler):  @tornado. web.asynchronous  @ Tornado.gen.coroutine  def get (self, *args, **kwargs):    url = ' https://api.github.com/'    http_client = Tornado.httpclient.AsyncHTTPClient ()    response = Yield tornado.gen.Task (http_client.fetch, url)    print Response.code    Print Response.body

Asynchttpclient forwarding
The use of tornado often requires some forwarding services, requiring the help of asynchttpclient. Since it is forward, it is impossible to get only the method, Post,put,delete and other methods will also have. This involves some headers and body, and even the waring of HTTPS.

Here is an example of post, yield results, usually, when using yield, handler is required to tornado.gen.coroutine.

headers = Self.request.headersbody = Json.dumps ({' name ': ' rsj217 '}) Http_client = Tornado.httpclient.AsyncHTTPClient () RESP = yield Tornado.gen.Task (  self.http_client.fetch,   URL,  method= "POST",   headers=headers,  Body=body,   Validate_cert=false)

Asynchttpclient Construct request
If the business process is not written in handlers, but elsewhere, when you cannot use Tornado.gen.coroutine directly, you can construct the request, using the callback method.

BODY = Urllib.urlencode (params) req = Tornado.httpclient.HTTPRequest (url=url,  method= ' POST ',  body=body,  validate_cert=false) Http_client.fetch (req, self.handler_response) def handler_response (self, Response):  Print Response.code

Usage is also relatively simple, asynchttpclient in the Fetch method, the first parameter is actually a HttpRequest instance object, so for some and HTTP request related parameters, such as method and body, You can use HttpRequest to construct a request before throwing it to the fetch method. Usually in the forwarding service, if the Validate_cert is opened, it is possible to return 599timeout, this is a warning, the official think it is reasonable.

Asynchttpclient Uploading Images
Asynchttpclient more advanced usage is to upload images. For example, a service has a function of requesting a picture OCR service for a third-party service. We need to transfer the images uploaded by users to third party services.

@router. Route ('/api/v2/account/upload ') class Apiaccountuploadhandler (helper. Basehandler): @tornado. Gen.coroutine @helper. Token_require def post (self, *args, **kwargs): Upload_type = self.get_a Rgument (' type ', None) files_body = self.request.files[' file '] new_file = ' upload/new_pic.jpg ' new_file_name = ' ne W_pic.jpg ' # writes the file with open (New_file, ' W ') as W:w.write (file_[' body ')) Logging.info (' user {} upload {} '. Mat (user_id, New_file_name)) # Asynchronous request upload picture with open (New_file, ' RB ') as F:Files = [(' Image ', New_file_name, f.re AD ())] fields = ((' Api_key ', key), (' Api_secret ', secret)) Content_Type, BODY = encode_multipart_formdata (fields, fi Les) headers = {"Content-type": Content_Type, ' content-length ': str (len)} request = Tornado.httpclient.HTTPReq Uest (config. Ocr_host, method= "POST", Headers=headers, Body=body, validate_cert=false) response = yield torn Ado.httpclient.AsyncHTTPClient (). Fetch (Request) def Encode_Multipart_formdata (fields, files): "" "The fields are a sequence of (name, value) elements for regular form fields.  Files are a sequence of (name, filename, value) elements for data to be uploaded as files. Return (Content_Type, body) ready for httplib. HTTP instance "" "Boundary = '----------this_is_the_boundary_$ ' CRLF = ' \ r \ n ' l = [] for (key, value) in Fields:l   . Append ('---' + boundary) l.append (' Content-disposition:form-data; name= '%s "'% key) l.append (') l.append (value)        for (key, filename, value) in files:filename = Filename.encode ("UTF8") l.append ('---' + boundary) L.append ( ' Content-disposition:form-data; Name= "%s"; Filename= "%s" '% (key, filename) ') l.append (' Content-type:%s '% get_content_type (filename)) L. Append (') l.append (value) l.append ('---' + boundary + '--') l.append (') BODY = Crlf.join (l) content_type = ' Multip Art/form-data; boundary=%s '% boundary return Content_Type, bodydef get_content_type (fileName): Import mimetypes return mimetypes.guess_type (filename) [0] or ' application/octet-stream ' 

In contrast to the above usage, uploading a picture is just one more image of the encoding. The binary data of the picture is encoded in multipart manner. Encoding, you also need to handle the relevant fields of delivery. In contrast, the way to use requests is very simple:

Files = {}f = open ('/users/ghost/desktop/id.jpg ') files[' image ' = Fdata = Dict (api_key= ' key ', api_secret= ' secret ') resp = Requests.post (URL, data=data, files=files) f.close () print Resp.status_code

Summarize
By using asynchttpclient, you can easily implement handler requests for third-party services. Combine the previous usage of tornado async. It's still two keys. Whether you need to return results to determine the way to use callback or yield. Of course, if different functions are yield,yield, they can be passed all the time. This feature, Tornado in the Tornado.auth inside of the OAuth authentication.

This is roughly the way it is used.

  • Related Article

    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.