Tornado入门(八)【运行和部署】

2018-08-27 14:04:54 浏览数 (1)

运行和部署

由于Tornado自身提供了HTTPServer, 所以它的运行和部署与其他Python Web 框架不一样。我们可以直接写一个main()方法来启动一个服务器,而不是配置WSGI容器。

代码语言:javascript复制
def main():
    app = make_app()
    app.listen(8888)
    IOLoop.current().start()

if __name__ == '__main__':
    main()

配置操作系统以启动服务器,注意可能需要提高系统中单个进程可打开的文件数。

进程和端口

由于Python GIL锁的限制,需要通过多线程的方式来尽可能的利用好机器的多核CPU。最好是每个CPU运行一个进程。

Tornado内置了多进程模式,只需要在main方法中做一点点改动:

代码语言:javascript复制
def main():
    app = make_app()
    server = tornado.httpserver.HTTPServer(app)
    server.bind(8888)
    server.start(0)  # forks one process per cpu
    IOLoop.current().start()

这是启用多进程最简单的方式,所有进程共享同一个端口。但还是存在如下限制:

  • 每个子进程都拥有独立的IOLoop,所以在fork之前不要操作全局的IOLoop实例。
  • 由于所有进程共享一个端口,所以管理起来更为麻烦

对于更为复杂的部署,推荐独立启动进程,然后每个进程监听不同的端口。supervisord提供了进程组可以组织这些进程。

当每个进程使用不同的端口时,可以使用负载均衡的服务器,比如nginx将不同端口转发至同一端口。

运行在负载均衡服务器后面

当使用类似nginx的负载均衡服务时,推荐传递参数xheaders=TrueHTTPServer构造器,这样Tornado才能通过X-Real-IP等头部字段获取真是的请求来源IP而不是转发服务器的IP。

下面是一个示例配置文件:

代码语言:javascript复制
user nginx;
worker_processes 1;

error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
    use epoll;
}

http {
    # Enumerate all the Tornado servers here
    upstream frontends {
        server 127.0.0.1:8000;
        server 127.0.0.1:8001;
        server 127.0.0.1:8002;
        server 127.0.0.1:8003;
    }

    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    access_log /var/log/nginx/access.log;

    keepalive_timeout 65;
    proxy_read_timeout 200;
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    gzip on;
    gzip_min_length 1000;
    gzip_proxied any;
    gzip_types text/plain text/html text/css text/xml
               application/x-javascript application/xml
               application/atom xml text/javascript;

    # Only retry if there was a communication error, not a timeout
    # on the Tornado server (to avoid propagating "queries of death"
    # to all frontends)
    proxy_next_upstream error;

    server {
        listen 80;

        # Allow file uploads
        client_max_body_size 50M;

        location ^~ /static/ {
            root /var/www;
            if ($query_string) {
                expires max;
            }
        }
        location = /favicon.ico {
            rewrite (.*) /static/favicon.ico;
        }
        location = /robots.txt {
            rewrite (.*) /static/robots.txt;
        }

        location / {
            proxy_pass_header Server;
            proxy_set_header Host $http_host;
            proxy_redirect off;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Scheme $scheme;
            proxy_pass http://frontends;
        }
    }
}

静态文件和缓存

通过配置static_path来支持静态文件的分发。

代码语言:javascript复制
settings = {
    "static_path": os.path.join(os.path.dirname(__file__), "static"),
    "cookie_secret": "__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__",
    "login_url": "/login",
    "xsrf_cookies": True,
}
application = tornado.web.Application([
    (r"/", MainHandler),
    (r"/login", LoginHandler),
    (r"/(apple-touch-icon.png)", tornado.web.StaticFileHandler,
     dict(path=settings['static_path'])),
], **settings)

Tornado会自动分发以/static/开头的URL, 它会在static这个目录下查找这些文件。/robots.txt/favicon.ico也会自动分发。

在上面的配置中,我们明确指定使用StaticFileHandler来处理apple-touch-icon.png请求。而applie-touch-icon.png实际位于/static/目录下。

为了提高性能,可以告诉浏览器缓存这些静态页面。为了使用这个功能,在模板中应该使用static_url来代替正式的URL。

代码语言:javascript复制
<html>
   <head>
      <title>FriendFeed - {{ _("Home") }}</title>
   </head>
   <body>
     <div>![]({{ static_url()</div>
   </body>
 </html>

static_url会将相对路径转换成类似/static/images/logo.png?v=aae54这样的URL,这里的v是logo.png的哈希值。Tornado通过识别它来发送缓存头给浏览器。

由于参数v的值是基于文件内容构建的,所以更新文件或者重启服务器都会改变它的值,然后浏览器就会自动获取最新的文件。如果文件内容没有改变,则继续使用缓存的文件。

在生产环境中,你可能想直接通过 nginx分发静态文件。

代码语言:javascript复制
location /static/ {
    root /var/friendfeed/static;
    if ($query_string) {
        expires max;
    }
 }

调试模式和自动重启

如果设置debug=True,应用就会开启调试模式。在这种模式下,下面的特性会开启:

  • autoreload=True 当代码发生变化时,应用会自动重启,这样就省去了手动重启的麻烦,但是当出现语法错误的时候,重启会失败。
  • compile_template_cache=False 模板不会缓存
  • static_hash_cache=False 静态文件的哈希值不会缓存。
  • serve_traceback=TrueRequestHandler中发生异常而没有被捕获时,生成一个包含错误信息的页面。

自动重启模式与HTTPServer的多线程模式不兼容,所以当使用自动重启模式时,调用HTTPServer或者tornado.process.fork_processes时,只能指定一个进程。

自动重启模式也可以作为单独的模块使用,调用tornado.autoreload即可。通过组合调试模式和自动重启模式,可以实现更为稳健的应用。在应用中设置debug=True,当出现语法错误时,调用python -m tornado.autoreload myserver.py进行重启。

重启的时候,Python编译器的命令行参数就失效了,因为使用sys.executablesys.argv来执行Python。

WSGI

Tornado一般不需要类似WSGI的容器就可以运行,但是有些情况下,又只能使用WSGI,这时Tornado支持有限的非异步操作。不支持的操作包括协程,@asynchronous修饰器,AsyncHTTPClientauth模块和WebSocket

也可以调用tornado.wsgi.WSGIAdapter将Tornado应用转换为WSGI应用。

代码语言:javascript复制
import tornado.web
import tornado.wsgi

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")

tornado_app = tornado.web.Application([
    (r"/", MainHandler),
])
application = tornado.wsgi.WSGIAdapter(tornado_app)

0 人点赞