Tornado入门(八)【运行和部署】
运行和部署
由于Tornado自身提供了HTTPServer
, 所以它的运行和部署与其他Python Web 框架不一样。我们可以直接写一个main()
方法来启动一个服务器,而不是配置WSGI
容器。
def main():
app = make_app()
app.listen(8888)
IOLoop.current().start()
if __name__ == '__main__':
main()
配置操作系统以启动服务器,注意可能需要提高系统中单个进程可打开的文件数。
进程和端口
由于Python GIL锁的限制,需要通过多线程的方式来尽可能的利用好机器的多核CPU。最好是每个CPU运行一个进程。
Tornado内置了多进程模式,只需要在main
方法中做一点点改动:
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=True
给HTTPServer
构造器,这样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;
}
}
}
静态文件和缓存
通过配置static_path
来支持静态文件的分发。
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。
<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
分发静态文件。
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=True
当RequestHandler
中发生异常而没有被捕获时,生成一个包含错误信息的页面。
自动重启模式与HTTPServer
的多线程模式不兼容,所以当使用自动重启模式时,调用HTTPServer
或者tornado.process.fork_processes
时,只能指定一个进程。
自动重启模式也可以作为单独的模块使用,调用tornado.autoreload
即可。通过组合调试模式和自动重启模式,可以实现更为稳健的应用。在应用中设置debug=True
,当出现语法错误时,调用python -m tornado.autoreload myserver.py
进行重启。
重启的时候,Python编译器的命令行参数就失效了,因为使用sys.executable
和sys.argv
来执行Python。
WSGI
Tornado一般不需要类似WSGI的容器就可以运行,但是有些情况下,又只能使用WSGI,这时Tornado支持有限的非异步操作。不支持的操作包括协程,@asynchronous
修饰器,AsyncHTTPClient
, auth
模块和WebSocket
。
也可以调用tornado.wsgi.WSGIAdapter
将Tornado应用转换为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)