Tornado 文档学习:运行与部署

本文介绍了Tornado Web框架的部署方法及配置技巧,包括多进程配置、静态文件处理、调试模式使用等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转自:http://blog.chriscabin.com/coding-life/web-framework/tornado/1596.html

引言

由于 Tornado 提供了自己的 HTTPServer,运行与部署它会与其它 Python web 框架编写的应用有所不同。和配置一个 WSGI 容器查找你的应用不同的是,Tornado 应用中可以编写一个 main() 函数直接启动服务器:

 
  1. def main():
  2. app = make_app()
  3. app.listen(8888)
  4. IOLoop.current().start()
  5. if __name__ == '__main__':
  6. main()

配置你的操作系统或者进程管理器运行该程序从而启动服务器。此外,需要注意的是增加每个进程可打开的文件数量(避免 “Too many open files” 错误)。要想提高限制(比如设置为 50,000),你可以使用 ulimit命令,修改 /etc/security/limits.conf 或者在 supervisord 配置中设置 minfds

进程与端口

由于 Python GIL 的问题,有必要运行多个进程充分利用多核服务器。典型的做法是每个 CPU 都运行一个进程。

Tornado 内建了启动多进程的模式,只需要做少许改动即可:

 
  1. def main():
  2. app = make_app()
  3. server = tornado.httpserver.HTTPServer(app)
  4. server.bind(8888)
  5. server.start(0) # 为每个 CPU fork 出一个进程
  6. IOLoop.current().start()

虽然上述做法有些缺陷,但它的确是一次启动多个进程并且共享端口的最简单的方法了。首先,每个子进程都有自己的 IOLoop,所以在 fork 前不要直接或间接触碰全局的 IOLoop 是非常重要的。第二,该模式下很难做到零宕机时间更新。最后,由于多个进程共享端口,所以分别监控它们会变得更加困难。

对于更加复杂的部署方案,推荐单独启动多个进程,并且让每个进程监听不同的端口。supervisord 的进程组特性就非常适合管理这种情况。当每个进程都使用不同的端口后,就需要一个负载均衡器(HAProxy 或者 nginx)来对外提供单一的访问地址。

在负载均衡器后面运行

当在类似 nginx 的负载均衡器下运行时,推荐传递 xheaders=True 参数到 HTTPServer 构造器。这样 Tornado 可以通过 X-Real-IP 类似的头部获取用户的 IP 地址,从而避免将所有的流量都打到负载均衡器上。

以下是一个 nginx 配置骨架,和在 FriendFeed 使用的类似。它假设 nginx 和 Tornado 服务器运行在相同的机器上,并且 4 个 Tornado 服务器运行在 8000~8003 端口:

 
  1. user nginx;
  2. worker_processes 1;
  3. error_log /var/log/nginx/error.log;
  4. pid /var/run/nginx.pid;
  5. events {
  6. worker_connections 1024;
  7. use epoll;
  8. }
  9. http {
  10. # Enumerate all the Tornado servers here
  11. upstream frontends {
  12. server 127.0.0.1:8000;
  13. server 127.0.0.1:8001;
  14. server 127.0.0.1:8002;
  15. server 127.0.0.1:8003;
  16. }
  17. include /etc/nginx/mime.types;
  18. default_type application/octet-stream;
  19. access_log /var/log/nginx/access.log;
  20. keepalive_timeout 65;
  21. proxy_read_timeout 200;
  22. sendfile on;
  23. tcp_nopush on;
  24. tcp_nodelay on;
  25. gzip on;
  26. gzip_min_length 1000;
  27. gzip_proxied any;
  28. gzip_types text/plain text/html text/css text/xml
  29. application/x-javascript application/xml
  30. application/atom+xml text/javascript;
  31. # Only retry if there was a communication error, not a timeout
  32. # on the Tornado server (to avoid propagating "queries of death"
  33. # to all frontends)
  34. proxy_next_upstream error;
  35. server {
  36. listen 80;
  37. # Allow file uploads
  38. client_max_body_size 50M;
  39. location ^~ /static/ {
  40. root /var/www;
  41. if ($query_string) {
  42. expires max;
  43. }
  44. }
  45. location = /favicon.ico {
  46. rewrite (.*) /static/favicon.ico;
  47. }
  48. location = /robots.txt {
  49. rewrite (.*) /static/robots.txt;
  50. }
  51. location / {
  52. proxy_pass_header Server;
  53. proxy_set_header Host $http_host;
  54. proxy_redirect off;
  55. proxy_set_header X-Real-IP $remote_addr;
  56. proxy_set_header X-Scheme $scheme;
  57. proxy_pass http://frontends;
  58. }
  59. }
  60. }

静态文件和激进文件缓存

你可以在 Tornado 中配置 static_path 为静态文件提供访问服务:

 
  1. settings = {
  2. "static_path": os.path.join(os.path.dirname(__file__), "static"),
  3. "cookie_secret": "__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__",
  4. "login_url": "/login",
  5. "xsrf_cookies": True,
  6. }
  7. application = tornado.web.Application([
  8. (r"/", MainHandler),
  9. (r"/login", LoginHandler),
  10. (r"/(apple-touch-icon\.png)", tornado.web.StaticFileHandler,
  11. dict(path=settings['static_path'])),
  12. ], **settings)

上述设置会让所有前缀为 /static/ 的请求访问 static 目录,例如 http://localhost:8888/static/foo.png 会在特殊的静态文件目录中提供 foo.png 的访问服务。同时,/robots.txt 和 /favicon.ico 也会由静态文件目录自动提供访问服务(即便其没有 /static/ 前缀)。

在上面的配置中,我们使用 StaticFileHandler 显式地配置 Tornado 直接从根访问 apple-touch-icon.png,虽然它在物理层面是存储在静态文件目录的。正则表达式的捕获组会告诉 StaticFileHandler 请求的文件名。你也可以用同样的方式在网站的根下提供对 sitemap.xml 的访问。当然,你也可以在 HTML 中使用合适的 <link />标签来避免仿造一个根 app-touch-icon.png 文件。

为了提高性能,通常可以使用浏览器缓存静态资源,这样就不必发送大量的 If-Modified-Since 或者 Etag 请求从而导致页面渲染中阻塞问题的发生。Tornado 使用静态内容版本的方式提供了缓存支持。

为了使用这个特性,可以在 HTML 中使用 static_url 代替静态文件的 URL:

 
  1. <html>
  2. <head>
  3. <title>FriendFeed - {{ _("Home") }}</title>
  4. </head>
  5. <body>
  6. <div><img src="{{ static_url("images/logo.png") }}"/></div>
  7. </body>
  8. </html>

static_url() 函数会把相对路径翻译成资源的绝对路径,如 /static/images/logo.png?v=aae54。参数 v 是logo.pn 文件的哈希值,它的存在会让 Tornado 服务器向浏览器发送缓存头部,从而让浏览器将内容缓存下来。

由于参数 v 和文件内容有关,一旦你更新了文件并重启服务器后,它会发送一个新的 v 值,这样浏览器会自动访问新的文件。如果文件内容没有改变,浏览器会继续使用本地缓存的文件,从而大幅度提高渲染的效率。

在生产环境中,你可能想要用更好的文件服务器如 nginx 提供静态文件服务。你可以配置几乎所有的 Web 服务器识别 static_url() 发送的版本标签,从而设置相应的缓存头部。以下是我们在 FriendFeed 使用 nginx 的配置:

 
  1. location /static/ {
  2. root /var/friendfeed/static;
  3. if ($query_string) {
  4. expires max;
  5. }
  6. }

调试模式和自动重载

如果在 Application 初始化时传递 debug=True,应用汇运行在调试/开发模式。在该模式下,便于开发的一些特性将会启用(以下这下也可以使独立的标志;如果两个都设置了,则独立的标志优先级更高):

  • autoreload=True:应用汇监控源文件的变化,并在任何更改发生时重新加载。这样可以减少手动重启服务器的次数。然而,某些错误(如语法错误等)仍然会导致在调试模式下无法恢复。
  • compiled_template_cache=False:模板不会缓存。
  • static_hash_cache=False:静态文件哈希(用于 static_url 函数)不会被缓存。
  • serve_traceback=True:当发生在 RequestHandler 中的异常未捕获时,会产生一个错误页面,包含详细的堆栈追踪信息。

自动重载模式不能在多进程模式的 HTTPServer 下使用。如果需要使用该模式的话,就不能给 HTTPServer.start传递除了 1 以外的参数(或者调用 tornado.process.fork_process)。

调试模式下的自动重载功能可以由 tornado.autoreload 这个独立模块提供。组合使用这两个特性可以提供额外的鲁棒性:在 App 中设置 autoreload=True,从而在运行时检测变化,然后可以使用 python -m tornado.autoreload myserver.py 捕获启动时发生的语法错误等。

重载会导致 Python 解释器命令行参数丢失(如 -u),因为它使用 sys.executable 和 sys.argv 重新执行了 Python。此外,修改这些参数也会导致重载失败。

在某些平台上(包括 Windows 和 Mac OSX 10.6 之前的版本),不能原地更新处理;因此,一旦代码变更被检测到后,旧的服务器仍然存在,但也启动了一个新的服务器。这对某些 IDE 来说非常困惑。

WSGI 和 GAE

Tornado 可以在没有 WSGI 容器的情况下独立运行。然而,在某些环境下(如 Google App Engine),只允许 WSGI 应用允许,应用不能运行自己的服务器。对于这种情况,Tornado 提供了一个阉割模式,该模式提供了针对 WSGI 环境下有限的 Tornado 功能,并且不支持异步操作。在 WSGI 模式下,诸如协程、@asynchronous 装饰器,AsyncHTTPClient 和 auth 模块以及 WebSockets 都无法使用。

你可以使用 tornado.wsgi.WSGIAdapter 将一个 Tornado 应用转换成 WSGI 应用。示例如下:

 
  1. import tornado.web
  2. import tornado.wsgi
  3. class MainHandler(tornado.web.RequestHandler):
  4. def get(self):
  5. self.write("Hello, world")
  6. tornado_app = tornado.web.Application([
  7. (r"/", MainHandler),
  8. ])
  9. app = tornado.wsgi.WSGIAdapter(tornado_app)

完整的功能可以查看 appengine 示例应用

参考

版权声明

  1. 本文由 Christopher L 发表,采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。请确保你已了解许可协议,并在 转载 时声明。
  2. 本文固定链接: http://blog.chriscabin.com/?p=1596

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值