部署

tornado是有它自己的HTTP服务器的

一般情况

def main():
    app = make_app()
    app.listen(8888)
    IOLoop.current().start()

if __name__ == '__main__':
    main()

需要 改变可以同时打开的文件数量,通过/etc/security/limits.conf或者supvisor的minfds设置。

启动多个进程

def main():
    app = make_app()
    server = tornado.httpserver.HTTPServer(app)
    server.bind(8888)
    server.start(0)  # 有几个CPU启动几个 共享一个端口
    IOLoop.current().start()

限制问题是每个进程是有自己的IOloop,不能动全局的IOloop;无法做不停机更新;因为都监听一个端口没法有效控制。

最好是分开启动它们,每个监听不同的端口,可以使用supvisor的process groups概念。当每个进程监听不同端口时,可以使用负载均衡例如HAProxy或nginx来分发。

在负载均衡之后

可以在初始化HTTPServer时传入xheaders=True来告诉tornado使用X-Real-IP来获得用户的IP地址而不是负载均衡的IP。

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;
        }
    }
}

静态文件部署

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)

所有以static开头的访问都会到static dir中去。包括robots.txt和favicon.ico。

<html>
<head>
    <title>FriendFeed - {{ _("Home") }}</title>
</head>
<body>
    <div>![]({{ static_url()</div>
</body>
</html>

使用static_url的url是/static/images/logo.png?v=aae54会包含一个该文件的hash值,这样在图片未改变之前都会cache。

使用nginx来提供静态文件:

location /static/ {
    root /var/friendfeed/static;
    if ($query_string) {
        expires max;
    }
}

debug和自动重载

启用了debug后

autoreload=True  监听文件变化并自动reload 使用了该设置则不能用多进程
compiled_template_cache=False: Templates will not be cached.
static_hash_cache=False: Static file hashes (used by the static_url function) will not be cached
serve_traceback=True: When an exception in a is not caught, an error page including a stack trace will be generated.

wsgi

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)

推荐阅读更多精彩内容