美文网首页
tornado4 --异步

tornado4 --异步

作者: 遥远的她197 | 来源:发表于2019-03-16 17:27 被阅读0次

1. 前言

Tornado在处理严峻的网络流量时表现的非常强悍,是Python编写的强大、易扩展的Web服务,其利用非阻塞的方式和对epoll的运用,可以处理数以千计的连接,是理想的实时通信Web框架。 以下编写基于异步的Web服务。

2. 同步

大多数Web应用都是阻塞方式的,当一个耗时的请求被处理时,整个进程将会被阻塞,直到该请求响应。如何解决阻塞,可以通过启动多进程处理,也可以使用异步编程来处理。以下示例模拟同步调用bing的搜索接口,并响应时间。

同步Tornado Web服务

import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
import tornado.httpclient
from tornado.options import parse_command_line
import datetime

from tornado.options import define, options
define("port", default=8080, help="run on the given port", type=int)

class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        #获取URL中的q参数
        query = self.get_argument('q')
        #获取HTTPClient类对象,并调用fetch()方法获取源码
        client = tornado.httpclient.HTTPClient()
        response = client.fetch("https://cn.bing.com/search?q={}".format(query))
        body = response.body
        now = datetime.datetime.utcnow()
        self.write("""
            <div style="text-align: center">
                <div style="font-size: 72px">%s</div>
                <div style="font-size: 144px">%s</div>
            </div>""" % (query, now))

def make_app():
    return tornado.web.Application(handlers=[
        (r"/", IndexHandler),
    ])

if __name__ == "__main__":
    parse_command_line()
    app = make_app()
    http_server = tornado.httpserver.HTTPServer(app)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()

示例中定义了一个IndexHandler类用于处理访问根路径时的请求,在IndexHandler类的get()方法中通过self.get_argument('q')方法获取访问URL中的参数q。接下来通过实例化HTTPClicent类的对象,并调用对象的fetch()方法来获取响应HTTPResponse对象,其body属性包含整个页面的源码信息,最后返回给浏览器一个简单的HTML页面。

实例中虽然定义了一个简单的搜索功能,而且当访问根路径时,程序本身响应也是足够的快。但如果遇到网络延迟,程序3秒才响应一个请求,即程序每隔2秒才响应一个请求,这样的设计肯定不能解决C10K问题,也不是高性能、高扩展性应用。为了凸显性能问题,将使用apache的ab命令模拟多线程并发请求,测试服务器压力。

3. ab压力测试

启动Tornado项目,执行压力测试命令为: ab -c 10 -n 1000 http://127.0.0.1:8080/?q=python,其中-c参数表示:模型10个并发,相当于10个人同时访问统一地址,-n参数表示:发出1000个请求,测试结果如图11-5所示:

同步ab压力测试从图中可以分析几个ab命令中几个比较重要的性能指标:

吞吐量(Requests per second): 服务器并发处理能力的量化描述,即某个用法用户数下单位时间内能处理的最大请求数。

用户平均请求等待时间(Time per request): 计算公式为处理完所有请求所花费的时间/(总请求数/并发用户数)。

请求消耗时间Time per request (mean, across all concurrent requests): 并发的每个请求平均消耗时间。

并发用户数(Concurrentcy Level)

完成请求数(Complete requests)

失败请求数(Failed requests)

流量(Transfer rate): 平均每秒网络上的流量,用于排查是否有网络流量过大导致网络延迟的问题。

4. 异步

在Tornado的异步库中最常用的就是自带的AsyncHTTPClicen,可以执行异步非阻塞的HTTP请求。AsyncHTTPClicen类对象的fetch()方法不用立马返回调用的结果,而是可以指定一个回调callback参数。

异步Web服务:

import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
import tornado.httpclient
import datetime
from tornado.options import define, options

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

class IndexHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous # 在get方法的定义之前
    def get(self):
        query = self.get_argument('q')
        client = tornado.httpclient.AsyncHTTPClient()
        client.fetch("https://cn.bing.com/search?q={}".format(query), callback=self.on_response)

    def on_response(self, response):
        body = response.body
        now = datetime.datetime.utcnow()
        self.write("""
            <div style="text-align: center">
                <div style="font-size: 72px">%s</div>
                <div style="font-size: 144px">%s</div>
            </div>""" % (self.get_argument('q'), now))
        # 回调方法结尾处调用
        self.finish()

def make_app():
    return tornado.web.Application(handlers=[
        (r"/", IndexHandler),
    ])

if __name__ == "__main__":
    tornado.options.parse_command_line()
    app = make_app()
    http_server = tornado.httpserver.HTTPServer(app)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()

示例中使用AsyncHTTPClient类,并调用对象的fetch()方法,AsyncHTTPClicent对象的fetch()方法并不直接返回结果,而且指定一个callback参数,在callback指定的方法中将获取HTTPResponse响应,最后返回给浏览器一个简单的HTML页面。示例中代码需要注意以下几点:

1)装饰器@tornado.web.asynchronous: 在Tornado中会默认的在函数处理结束时关闭客户端的连接,但在异步操作时需要等待回调callback指定的方法执行结束才关闭客户端的连接,因此需要保持开启连接状态直到回调函数执行完毕。所以使用装饰器@tornado.web.asynchronous告诉Tornado保持连接开启。

2)self.finish():使用装饰器@tornado.web.asynchronous来告诉Tornado不要自己关闭连接,因此在回调callback方法执行完毕后,要显式的调用self.finish()方法告诉Tornado关闭连接。

5. 异步生成器

为了将同步的Web服务优化为异步Web服务,于是将代码切割为两个方法,并通过ab压力测试,发现Tornado的Web服务在性能上获得了极大程度上的扩展。但如果需要处理更多的异步请求,那程序的维护和编码将变得非常困难。比如在编写爬虫程序时以深度优先算法进行爬取数据,则将会出现一个回调函数调用回调函数的回调函数。如下示例所示:

5.1 异步链式回调

def get(self):
    client = AsyncHTTPClient()
    client.fetch("http://example1.com", callback=on_response)

def on_response(self, response):
    client = AsyncHTTPClient()
    client.fetch("http://example2.com/", callback=on_response2)

def on_response2(self, response):
    client = AsyncHTTPClient()
    client.fetch("http://example3.com/", callback=on_response3)

def on_response3(self, response):
    client = AsyncHTTPClient()
client.fetch("http:// example4.com/", callback=on_response4)

def on_response3(self, response):
    ……

在异步编程中应尽量避免嵌套的使用回调函数,否则将会给程序的维护和扩展带来非常大的麻烦。Tornado中可以使用装饰器tornado.web.gen.coroutine简化异步编程,避免写嵌套的回调函数,提高代码的灵活性、可读性、可扩展性。如下示例使用装饰器tornado.web.gen.coroutine进行优化代码。

5.2 非阻塞coroutine的使用

import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
import tornado.httpclient
import datetime
from tornado.options import define, options

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

class IndexHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous # 在get方法的定义之前
    @tornado.web.gen.coroutine
    def get(self):
        query = self.get_argument('q')
        client = tornado.httpclient.AsyncHTTPClient()
        response = yield client.fetch("https://cn.bing.com/search?q={}".format(query))

        body = response.body
        now = datetime.datetime.utcnow()
        self.write("""
            <div style="text-align: center">
                <div style="font-size: 72px">%s</div>
                <div style="font-size: 144px">%s</div>
            </div>""" % (self.get_argument('q'), now))
        self.finish() # 回调方法结尾处调用

def make_app():
    return tornado.web.Application(handlers=[
        (r"/", IndexHandler),
    ])

if __name__ == "__main__":
    tornado.options.parse_command_line()
    app = make_app()
    http_server = tornado.httpserver.HTTPServer(app)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()

示例中使用装饰器tornado.web.gen.coroutine 和Python的yield关键字,yield的使用允许运行在HTTP请求进行中执行其他任务,而不需要等待响应挂起进程,只需当HTTP响应完成后,RequestHandler方法在其停止的地方恢复并执行其余代码。

------------------------我的代码-------------------

异步

在async_tornado.py

import tornado.web
import tornado.ioloop
import tornado.httpclient


class IndexHandler(tornado.web.RequestHandler):
    # 这步是连接不会关闭
    @tornado.web.asynchronous
    def get(self):
        q = self.get_argument('q')
        client = tornado.httpclient.AsyncHTTPClient()
        # 这里想访问要卡着,所以我们要连接不能断开才有@tornado这步
        client.fetch('https://cn.bing.com/search?q=%s' % q, callback=self.on_response)

        self.write('异步测试')

    # 回调, 当页面响应,则调用回调函数on_response
    def on_response(self, response):
        print(response)
        self.write('回调执行')
        # 完成响应了之后, 就可以关闭连接了
        self.finish()


def make_app():
    return tornado.web.Application(handlers=[
        (r'/index/', IndexHandler)
    ])


if __name__ == '__main__':
    app = make_app()
    app.listen(8080)
    tornado.ioloop.IOLoop.current().start()

异步变同步(优化代码),它还是异步

在async_tornado2.py中

import tornado.web
import tornado.ioloop
import tornado.httpclient


# 异步变同步(优化代码),它还是异步
class IndexHandler(tornado.web.RequestHandler):
    # 这步是让连接不会关闭
    @tornado.web.asynchronous
    @tornado.web.gen.coroutine
    def get(self):
        q = self.get_argument('q')
        client = tornado.httpclient.AsyncHTTPClient()
        # 这里想访问要卡着,所以我们要连接不能断开才有@tornado这步
        response = yield client.fetch('https://cn.bing.com/search?q=%s' % q)
        print(response)
        self.write('异步测试')

    # 改为同步了(优化了), 下面就不需要了
    # 回调, 当页面响应,则调用回调函数on_response
    # def on_response(self, response):
    #     print(response)
    #     self.write('回调执行')
    #     # 完成响应了之后, 就可以关闭连接了
    #     self.finish()


def make_app():
    return tornado.web.Application(handlers=[
        (r'/index/', IndexHandler)
    ])


if __name__ == '__main__':
    app = make_app()
    app.listen(8080)
    tornado.ioloop.IOLoop.current().start()

同步

在sync_tornado.py中

import tornado.web
import tornado.ioloop
import tornado.httpclient


class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        # 路由中传递的参数, /index/?q=python
        q = self.get_argument('q')
        # 向地址发送请求: https://cn.bing.com/search?q=
        client = tornado.httpclient.HTTPClient()
        # 因为是同步的,它必须等着第一个请求完成才能,所以不需要连接 -- @tornado
        response = client.fetch('https://cn.bing.com/search?q=%s' % q)
        print(response)
        self.write('同步测试')


def make_app():

    return tornado.web.Application(handlers=[
        (r'/index/', IndexHandler)
    ])


if __name__ == '__main__':
    app = make_app()
    app.listen(8000)
    tornado.ioloop.IOLoop.current().start()

相关文章

  • tornado4 --异步

    1. 前言 Tornado在处理严峻的网络流量时表现的非常强悍,是Python编写的强大、易扩展的Web服务,其利...

  • (大纲44)Python08tornado 第4节、模板

    8、Tornado4、模板 1.4.0模板1.4.1静态文件1.4.2使用模板1.4.3练习

  • tornado后台热加载配置

    后台使用tornado4开发,需要搭建一个后台开发环境,让代码修改后自动重启 热加载; 1.简介 Python后端...

  • JavaScript之异步概述

    1,异步(什么是异步,异步出现的原因,异步解决的问题,如何实现异步,什么时候需要异步模式。) JavaScript...

  • 动态Web编程

    一、AJAX异步提交 异步提交AJAX = Asynchronous JavaScript and XML(异步的...

  • 网络请求数据并且解析的过程(推荐AFNetworking)

    demo1.http请求,get同步异步,post同步异步get同步 get异步 post异步 2.session...

  • 异步基础

    异步 为什么要用异步方法? 为什么异步很难 怎么异步? 基于回调这是传统的异步模型,最常用的方法,早期版本的C# ...

  • android mediacodec 使用注意事项

    mediaCodec有同步模式和异步模式 异步模式 【注意】异步模式 onInputBufferAvailable...

  • ES6 Primise异步编程

    异步操作流程化的手段 #Promise处理异步操作 Promise,使异步操作变得流程化的手段之一,例如“异步A ...

  • 模拟异步函数

    异步函数实现机制(async.js) 实现了支持同步函数和异步函数的声明 声明异步函数 调用异步函数

网友评论

      本文标题:tornado4 --异步

      本文链接:https://www.haomeiwen.com/subject/gxvsmqtx.html