zoukankan      html  css  js  c++  java
  • Tornado开发技巧,简单了解tornado

    tornado基础入门(一)——简单了解tornado

    参考:http://demo.pythoner.com/itt2zh/ch1.html

    tornado是一个轻量级的web框架,是一个用python写的web服务器,它有三个最大的特点(优势)速度、简单和可扩展性。

    编写tornado应用做多最多的工作就是定义一个类去继承tornado中的RequestHandler类。

    来看一个例子:

    # hello.py

    复制代码
     1 import tornado.httpserver
     2 import tornado.ioloop
     3 import tornado.options
     4 import tornado.web
     5 
     6 from tornado.options import define, options
     7 define("port", default=8000, help="run on the given port", type=int)
     8 
     9 class IndexHandler(tornado.web.RequestHandler):
    10     def get(self):
    11         greeting = self.get_argument('greeting', 'Hello')
    12         self.write(greeting + ', friendly user!')
    13 
    14 if __name__ == "__main__":
    15     tornado.options.parse_command_line()
    16     app = tornado.web.Application(handlers=[(r"/", IndexHandler)])
    17     http_server = tornado.httpserver.HTTPServer(app)
    18     http_server.listen(options.port)
    19     tornado.ioloop.IOLoop.instance().start()
    复制代码

    这个例子编写了一个非常简单的应用。

    我们可以在命令行中启动它:

    python hello.py --port=8000

    然后再从浏览器中打开这个应用,输入网址:

    http://localhost:8000/

    可以看到网页上显示:

    Hello, friendly user!

    " 我们在命令行输入的  ’prot' 的参数是通过第七行中的define传递的,define是tornado.options模块中的一个函数,这个模块用来读取命令行中的一些设置。例如我们在命令行中设置了它的端口prot=8888,define就匹配出它所设置的同名变量 ‘prot’ ,将它从默认值改为 ‘8888’(默认值为8000),这时我们则需要从这个网址进入“http://localhost:8888/" "

    这显示的是我们代码第12行 ’write‘进的内容。

    我们可以定制一下这个内容,可以看到 ’write' 的有个自定义的变量 ‘greeting' ,而这个变量的默认值为 'Hello’ , 它是通过tornado中的内建函数 ‘ get_argument ‘ 来传递给 ’write‘ ,

    所以我们来改变这个变量,从哪入手呢? 从网址入手!

    http://localhost:8000/?greeting=Hi

    在 ‘?’后给变量重新赋值,这时可以发现网页上的内容变为:

    Hi, friendly user!

    来梳理以下这段代码的工作流程:

    复制代码
    14 if __name__ == "__main__":
    15     tornado.options.parse_command_line()
    16     app = tornado.web.Application(handlers=[(r"/", IndexHandler)])
    17     http_server = tornado.httpserver.HTTPServer(app)
    18     http_server.listen(options.port)
    19     tornado.ioloop.IOLoop.instance().start()
    复制代码

      1. 我们的应用真正运行起来的时 ‘15、16 ’两行,通过tornado.options模块解析命令行参数,再创建了一个tornado的Application类的实例,最重要的参数时 ‘handler’ 它来告诉tornado应该用哪个类来响应请求。

      2. 后面开始,这段代码将会被反复使用,Application对象被创建,我们将其传递给tornado中的HTTPServer对象,然后使用我的命令行指定端口监听(通过options模块取出)。

      3. 最后,程序准备好接受http请求后,我们创建一个tornado的IOLoop的实例。

    Tornado开发技巧

    Tornado框架算是很轻量级的 单线程 异步 编程框架,只是额外加了很基础的模板、HEADER、COOKIE、路由等的WEB相关的支持(都定义在web.py里),它的大部分代码是在封装nonblocking socket、epoll、event loop等基础的异步编程模块。所以,在使用tornado之前一定要先了解这个框架的定位,它不单能够用于WEB服务编程,也非常适用于需要异步编程的后台服务。

    tornado框架

    Tornado框架的编码水准很高,虽然不了解tornado源码不妨碍使用,但深入了解一下还是有助于提高自己的PYTHON水平的,这里有一份源码详解

    从这个框架图也能看出,tornado 完整地实现了HTTP服务器和客户端,在此基础上提供WEB服务。它可以分为四层:

    在TCP层使用tornado,可以从TCPServer派生,但是IO方面需要做进一步处理。示例:

    class EchoServer(netutil.TCPServer):
        def handle_stream(self, stream, address):
            self._stream = stream
            self._read_line()
    
        def _read_line(self):
            self._stream.read_until('
    ', self._handle_read)
    
        def _handle_read(self, data_in):
            self._stream.write('You sent: %s' % data_in)
            self._read_line()
    
    server = EchoServer()
    server.listen(12345)
    ioloop.IOLoop.instance().start()
    

    在HTTP/WEB层使用tornado,可以借助Application类,也是我们常用的开发方式。示例:

    application = web.Application([
        (r"/", MainPageHandler),
    ])
    server = httpserver.HTTPServer(application)
    server.listen(8080)
    ioloop.IOLoop.current().start()
    

    异步

    Tornado虽然是一个异步框架,但是如果使用不当很容易造成性能低下。需要理解一下服务并发量、吞吐量、响应时间这些衡量指标,异步并不能保证这些指标优秀,异步只是指任务不被线性执行。最常见的是如果你在tornado进程里执行一条耗时很长的MySQL查询操作,同样会block住整个tornado进程,而tornado是单线程框架,这意味着block住了该线程上的所有其他请求。

    要用好tornado的异步能力,需要掌握内置的异步模块和celery。

    web.asynchronous

    装饰器 web.asynchronous 只能用在verb函数之前(即get/post/delete等),并且需要搭配tornado异步客户端使用,如httpclient.AsyncHTTPClient。加上这个装饰器后,必须在异步回调函数里显式调用RequestHandler.finish 才会结束这次 HTTP 请求。

    tornado里大多的异步函数,包括web.asynchronous都会用到future特性。如果用到了内置的concurrent.Future,需要注意该类不是线程安全的。

    gen模块

    gen是tornado异步支持的核心模块。

    装饰器 gen.coroutine 可以把web.asynchronous装饰的多个分散的异步函数调用写成coroutine的形式,回调函数的结果直接用yield返回。这种用法的优势就不赘述了,目前基本已经在各种主流语言普及了。在版本3.1之后,可以直接省略web.asynchronous改用gen.coroutine替代。

    函数 gen.Task 是另外一个在coroutine里使用异步函数的辅助函数,它可以把一个带callback参数的异步调用包装起来,该异步调用传给callback函数的参数将作为yield的返回值。如果传给callback函数的是多参数,会返回一个简单的Argument对象。这里没有cencept定义理解起来可能有些费劲,下面是个示例,运行一遍有助于理解:

    import tornado.web
    import tornado.gen
    import logging
    
    def echo(message, callback):
        callback(message, 'that is callback message')
    
    @tornado.gen.coroutine
    def test():
        response = yield tornado.gen.Task(echo, 'this is first message')
        logging.warn(response.args[1])
    
    test()
    

    如果coroutine函数需要返回,需要借助 gen.Return,类似这样: raise tornado.gen.Return(XXX),捕捉这个异常得到返回值。

    celery

    上面所有的异步调用只使用到了内置的异步客户端httpclient.AsyncHTTPClient,可以参考一下,如果要自己写一个异步客户端还是比较麻烦的。比如,你的函数里包括了一个同步MySQL调用,那么不管你怎么加装饰器,还是yield返回都是没用的,因为你的MySQL调用本身是同步的。可以参考AsyncHTTPClient,借助tornado.ioloop.IOLoop封装一个异步的MySQL客户端,但成本还是很高的。

    所幸我们可以借助celery项目,这是一个分布式的任务队列。tornado接到请求后,可以把所有的复杂业务逻辑处理、数据库操作以及IO等各种耗时的同步任务交给celery,由这个任务队列异步处理完后,再返回给tornado。这样只要保证tornado和celery的交互是异步的,那么整个服务是完全异步的。

    Tornado和celery的交互可以借助 tcelery 这个适配器。

    具体实现可以借助gen模块,这里是个示例:

    @tornado.gen.coroutine
    def get(self, *args, **kwargs):
        response = yield tornado.gen.Task(example_task.apply_async, args=[self.request.arguments])
        self.finish(response.result)
    

    上面也说了gen.Task会给传入的example_task.apply_async添加一个callback参数,默认celery的apply_async函数是不认识这个参数的,而tcelery的功能就是对这俩做了适配,让gen.Taskapply_async无缝工作。目前tcelery应该只支持amqp backend。

    开发部署

    可以使用supervisord启动tornado。1

    使用nginx做负载均衡,此时建议给HTTPServer传递参数 xheaders=True2

    调试

    开发的时候如果想直接看到改动效果,而不是每次改完还要重新启动python进程,可以借助tornado.autoreload 模块:

    python -m tornado.autoreload server.py

    同时给Application传递如下参数

    debug=True
    autoreload=True
    compiled_template_cache=False
    static_hash_cache=False
    serve_traceback=True
    

    对于一些超时的调用直接打印出调用堆栈:

    IOLoop.instance().set_blocking_log_threshold(0.050)  # 当IOLoop阻塞50ms会打印调用栈
    

    其他

    常用工具类

    tornado.options

    tornado.log 有三个内置的log: access_logapp_log 和 gen_log

    sqlalchemy

    tornado里使用sqlalchemy,参考手册 ORM 2.6.9 Contextual/Thread-local Sessions 这一章。保证one session one request即可。

  • 相关阅读:
    [Luogu P3626] [APIO2009] 会议中心
    杭电 1869 六度分离 (求每两个节点间的距离)
    杭电 1874 畅通工程续 (求某节点到某节点的最短路径)
    最短路径模板
    杭电 2544 最短路径
    POJ 1287 Networking (最小生成树模板题)
    NYOJ 1875 畅通工程再续 (无节点间距离求最小生成树)
    POJ 2485 Highways (求最小生成树中最大的边)
    杭电 1233 还是畅通工程 (最小生成树)
    杭电 1863 畅通工程 (最小生成树)
  • 原文地址:https://www.cnblogs.com/timssd/p/10299052.html
Copyright © 2011-2022 走看看