第二十章:部署 Django
在这本书中,我们提到了驱使 Django 发展的很多目标。易用,对初学者友好,重复任务的抽象, 这些都驱使着 Django 继续发展。
然而,从 Django 一开始,就有另一个重要的目标:Django 应该容易被部署,并且它应该能够用 有限的资源提供大量的服务。
这样的动机是很明显的,当你看到 Django 的背景:堪萨斯州一个小巧的、家族式报纸企业 负担不起高品质的服务器硬件,所以 Django 的最初开发者们都非常的关心如何才能从有限 的资源中挤压出最好的性能。确实,这些年来 Django 的开发者们充当了他们自己的系统管 理员。虽然他们的站点每天处理上千万的点击量,但他们确实没有那么多数量的硬件以至于*需要*专门的系统管理员。
当 Django 成为一个开源项目后,关注在其性能和开发的简易性因为某些原因变得特别重要:业余爱好者也有同样的需求。有一些人想要花费仅仅 10 美元并使用 Django 来体验制作一个 中小规模流量的站点。
但是小规模的应用只是目标的一半而已。Django 也需要能够增加其规模来满足大型公司和集团 的需求。这里,Django 采取了类似于 LAMP 形式的 Web 集的哲学理论,通常称为 无共享(shared nothing) 。
什么是 LAMP?
LAMP 这个缩写最初是创造它来描述一系列驱动 Web 站点的流行的开源软件:
Linux(操作系统)
Apache(Web 服务器)
MySQL(数据库)
PHP(编程语言)
随着时间的推移,这个缩写已经变得涉及了更多开源软件栈的哲学思想,而不仅仅再是局限于特定的某一种了。所以当 Django 使用 Python 并可以使用多种数据库产品时,LAMP 软件栈证实的一些理论就渗透到 Django 中了。
这里尝试建立一个类似的缩写来描述 Django 的技术栈。本书的作者喜欢用 LAPD(Linux, Apache, PostgreSQL, and Django)或 PAID(PostgreSQL, Apache, Internet, and Django)。使用 Django 并采用 PAID 吧!
无共享
无共享哲学的核心实际上就是应用程序在整个软件栈上的松耦合思想。这个架构的产生直接响应了当时的主流架构背景:将 web 应用服务器作为不可分的整体,它将语言、数据库、web服务器甚至操作系统的一部分封装到单个进程中(如 java)。
当需要伸缩性是,就会碰到下面这个主要问题:几乎不可能把混为一体的进程所干的事情分解到许多不同的物理机器上,因此这类应用就必须要有极为强大的服务器来支撑。这些服务器,需要花费数万甚至数十万美金,从而使这类大规模 Web 网站远离了财政不宽裕的个人和小公司。
LAMP 社区注意到,如果将 Web 栈分解为多个独立的组件,人们就能从容的从廉价的服务器开始自己的事 业,而在发展壮大时只需要添加更多的廉价服务器。如果 3000 美元的数据库服务器不足以处理负载,只需要简单的购买第二个(或第三、第四个)直到足够。如 果需要更多的存储空间,只需增加新的 NFS 服务器。
但是,为了使这个成为可能,Web 应用必须不再假设由同一个服务器处理所有的请求,甚至处理单个请求的所有部分。在大规模 LAMP(以及 Django)的部署环境中,处理一个页面甚至会涉及到多达半打的服务器!这一条件会对各方面产生诸多影响,但可以归结到以下几点:
不能在本地保存状态 。也就是说,任何需要在多个请求间共享的数据都必须保存在某种形式的持久性存储(如数据库)或集中化缓存中。
软件不能假设资源是本地的 。例如,Web 平台不能假设数据库运行于同一个服务器上;因此,它必须能够连接到远程数据库服务器。
Web 栈中的每个部分都必须易于移动或复制 。如果在部署时因为某种原因 Apache 不能工作,必须能够在最小的代价下切换到其它服务器。或者,在硬件层次,如果 Web服务器崩溃,必须能够在最小的 宕机时间内替换到另一台机器。请记住,整个哲学基于将应用部署在廉价的、商品化的硬件上。因此,本就应当预见到单个机器的失效。
就所期望的那样,Django 或多或少透明的处理好了这件事情,没有一个部分违反这些原则。但是,了解架构设计背后的哲学,在我们处理伸缩性时将会有所裨益。
但这果真解决问题?
这一哲学可能在理论上(或者在屏幕上)看起来不错,但是否真的能解决问题?
好了,我们不直接回答这个问题,先请看一下将业务建立在上述架构上的公司的不完全列表。大家可能已经熟悉其中的一些名字:
Amazon
Blogger
Craigslist
LiveJournal
Slashdot
Wikipedia
Yahoo
YouTube
请允许我用 当哈利遇见沙莉 中的著名场景来比喻:他们有的我们也会有!
关于个人偏好的备注
在进入细节之前,短暂跑题一下。
开源运动因其所谓的宗教战争而闻名;许多墨水(以及墨盒、硒鼓等)被挥洒在各类争论上:文本编辑器(emacs vs. vi ),操作系统(Linux vs.Windows vs. Mac OS),数据库引擎(MySQL vs.
PostgreSQL), 当然还包括编程语言。
我们希望远离这些战争。仅仅因为没有足够的时间。
但是,在部署 Django 确实有很多选择,而且我们也经常被问及个人偏好。表述这些会使社区处于引发上类战争的危险边缘,所以我们在一直以来非常克制。但是,出于完整性和全无保留的目的,我们将在这里表述自己的偏好。以下是我们的优先选择:
操作系统: Linux(具体而言是 Ubuntu)
Web 服务器: Apache 和 mod_python
数据库服务器:PostgreSQL
当然,我们也看到,许多做了不同选择的 Django 用户同样也大获成功。
用 Apache 和 mod_python 来部署 Django
目前,Apache 和 mod_python 是在生产服务器上部署 Django 的最健壮搭配。 mod_python (http://www.djangoproject.com/r/mod_python/)是一个在 Apache 中嵌入
Python 的 Apache 插件,它在服务器启动时将 Python 代码加载到内存中。(译注:这里的内存是指虚拟内存) 代码在 Apache 进程的整个生命周期中都驻留在内存中,与其它服务器的做法相比,这将带来重要的性能提升。
Django 要求 Apache2.x 和 mod_python3.x,并且我们优先考虑 Apache 的 prefork MPM 模式,而不是 worker MPM。
备注
如何配置 Apache 超出了本书的范围,因此下面将只简单介绍必要的细节。幸运的是,如果需要进一步学习 Apache 的相关知识,可以找到相当多的绝佳资源。下面是我们所中意的部分资料:
开源的 Apache 在线文档,位于 http://www.djangoproject.com/r/apache/docs/
Pro Apache,第三版 (Apress, 2004),作者 Peter Wainwright, 位于 http://www.djangoproject.com/r/books/pro-apache/
Apache: The Definitive Guide, 第三版 (OReilly, 2002),作者 Ben Laurie 和 Peter
Laurie, 位于 http://www.djangoproject.com/r/books/apache-pra/
基本配置
为了配置基于 mod_python 的 Django,首先要安装有可用的 mod_python 模块的 Apache。这通常意味着应该有一个 LoadModule 指令在 Apache 配置文件中。它看起来就像是这样:
LoadModule python_module /usr/lib/apache2/modules/mod_python.so然后,编辑你的 Apache 的配置文件,添加如下内容:
<Location "/">
SetHandler python-program
PythonHandler django.core.handlers.modpython SetEnv DJANGO_SETTINGS_MODULE mysite.settings PythonDebug On
</Location>
要确保把 DJANGO_SETTINGS_MODULE 中的 mysite.settings 项目换成与你的站点相应的内容。
它告诉 Apache,任何在 / 这个路径之后的 URL 都使用 Django 的 mod_python 来处理。它将 DJANGO_SETTINGS_MODULE 的值传递过去,使得 mod_python 知道这时应该使用哪个配置。
注意这里使用 <Location> 指令而不是 <Directory> 。后者用于指向你的文件系统中的一个位置,然而 <Location> 指向一个 Web 站点的 URL 位置。
Apache 可能不但会运行在你正常登录的环境中,也会运行在其它不同的用户环境中;也可能会有不同的文件路径或 sys.path。你需要告诉 mod_python 如何去寻找你的项目及 Django的位置。
PythonPath "['/path/to/project', '/path/to/django'] + sys.path"
你也可以加入一些其它指令,比如 PythonAutoReload Off 以提升性能。查看 mod_python 文档获得详细的指令列表。
注意,你应该在成品服务器上设置 PythonDebug Off 。如果你使用 PythonDebug On 的话,在程序产生错误时,你的用户会看到难看的(并且是暴露的) Python 回溯信息。
重启 Apache 之后所有对你的站点的请求(或者是当你用了 <VirtualHost> 指令后则是虚拟主机)都会由 Djanog 来处理。
注意
如果你在一个比 / 位置更深的子目录中部署 Django,它 不会 对你的 URL 进行修整。所以如果你的 Apache 配置是像这样的:
<Location "/mysite/"> SetHandler python-program
PythonHandler django.core.handlers.modpython SetEnv DJANGO_SETTINGS_MODULE mysite.settings PythonDebug On
</Location>
则你的 所有 URL 都要以 "/mysite/" 起始。基于这种原因我们通常建议将 Django,部署 在主机或虚拟主机的根目录。另外还有一个选择就是,你可以简单的将你的 URL 配置转换成像下面这样:
urlpatterns = patterns('',
(r'^mysite/', include('normal.root.urls')),
)
在同一个 Apache 的实例中运行多个 Django 程序
在同一个 Apache 实例中运行多个 Django 程序是完全可能的。当你是一个独立的 Web 开发人员并有多个不同的客户时,你可能会想这么做。
只要像下面这样使用 VirtualHost 你可以实现: NameVirtualHost *
<VirtualHost *>
ServerName www.example.com
# ...
SetEnv DJANGO_SETTINGS_MODULE mysite.settings
</VirtualHost>
<VirtualHost *>
ServerName www2.example.com
# ...
SetEnv DJANGO_SETTINGS_MODULE mysite.other_settings
</VirtualHost>
如果你需要在同一个 VirtualHost 中运行两个 Django 程序,你需要特别留意一下以 确保
mod_python 的代码缓存不被弄得乱七八糟。使用 PythonInterpreter 指令来将不 同的
<Location> 指令分别解释:
<VirtualHost *>
ServerName www.example.com
# ...
<Location "/something">
SetEnv DJANGO_SETTINGS_MODULE mysite.settings PythonInterpreter mysite
</Location>
<Location "/otherthing">
SetEnv DJANGO_SETTINGS_MODULE mysite.other_settings PythonInterpreter mysite_other
</Location>
</VirtualHost>
这个 PythonInterpreter 中的值不重要,只要它们在两个 Location 块中不同。
用 mod_python 运行一个开发服务器
因为 mod_python 缓存预载入了 Python 的代码,当在 mod_python 上发布 Django 站点时,你每 改动了一次代码都要需要重启 Apache 一次。这还真是件麻烦事,所以这有个办法来避免它:只要 加入 MaxRequestsPerChild 1 到配置文件中强制 Apache 在每个请求时都重新载入所有的 代码。但是不要在产品服务器上使用这个指令,这会撤销 Django 的特权。
如果你是一个用分散的 print 语句(我们就是这样)来调试的程序员,注意这 print 语 句在 mod_python 中是无效的;它不会像你希望的那样产生一个 Apache 日志。如果你需要在
mod_python 中打印调试信息,可能需要用到 Python 标准日志包(Pythons standard logging
package)。 更多的信息请参见 http://docs.python.org/lib/module-logging.html 。另一个选择是在模板页面中加入调试信息。
使用相同的 Apache 实例来服务 Django 和 Media 文件
Django 本身不用来服务 media 文件;应该把这项工作留给你选择的网络服务器。我们推荐使用一个单独的网络服务器(即没有运行 Django 的一个)来服务 media。想了解更多信息,看下面的章节。
不过,如果你没有其他选择,所以只能在同 Django 一样的 Apache VirtualHost 上服务 media文件,这里你可以针对这个站点的特定部分关闭 mod_python:
<Location "/media/"> SetHandler None
</Location>
将 Location 改成你的 media 文件所处的根目录。
你也可以使用 <LocationMatch> 来匹配正则表达式。比如,下面的写法将 Django 定义到网站的根目录,并且显式地将 media 子目录以及任何以 .jpg , .gif , 或者 .png 结尾的
URL 屏蔽掉:
<Location "/">
SetHandler python-program
PythonHandler django.core.handlers.modpython SetEnv DJANGO_SETTINGS_MODULE mysite.settings
</Location>
<Location "/media/"> SetHandler None
</Location>
<LocationMatch "\.(jpg|gif|png)$"> SetHandler None
</LocationMatch>
在所有这些例子中,你必须设置 DocumentRoot ,这样 apache 才能知道你存放静态文件的位置。
错误处理
当你使用 Apache/mod_python 时,错误会被 Django 捕捉,它们不会传播到 Apache 那里,也不会出现在 Apache 的 错误日志 中。
有一个例外就是当确实你的 Django 设置混乱了时。在这种情况下,你会在浏览器上看到一个 内部服务器错误的页面,并在 Apache 的 错误日志 中看到 Python 的完整回溯信息。错误日志 的回溯信息有多行。当然,这些信息是难看且难以阅读的。
处理段错误
有时候,Apache 会在你安装 Django 的时候发生段错误。这时,基本上 总是 有以下两个与
Django 本身无关的原因其中之一所造成:
有可能是因为,你使用了 pyexpat 模块(进行 XML 解析)并且与 Apache 内置的版本相冲突。详情请见
http://www.djangoproject.com/r/articles/expat-apache-crash/.
也有可能是在同一个 Apache 进程中,同时使用了 mod_python 和 mod_php,而且都使用 MySQL 作为数据库后端。在有些情况下,这会造成 PHP 和 Python 的 MySQL 模块的版本冲突。在 mod_python 的 FAQ 中有更详细的解释。 http://www.djangoproject.com/r/articles/php-modpython-faq/.
如果还有安装 mod_python 的问题,有一个好的建议,就是先只运行 mod_python 站点,而不使用 Django 框架。这是区分 mod_python 特定问题的好方法。下面的这篇文章给出了更详细的解释。 http://www.djangoproject.com/r/articles/getting-modpython-working/.
下一个步骤应该是编辑一段测试代码,把你所有 django 相关代码 import 进去,你的
views,models,URLconf,RSS 配置,等等。把这些 imports 放进你的 handler 函数中,然后从浏览器进入你的 URL。如果这 些导致了 crash,你就可以确定是 import 的 django 代码引起了问题。逐个去掉这些 imports,直到不再冲突,这样就能找到引起问题的那个 模块。 深入了解各模块,看看它们的 imports。要想获得更多帮助,像 linux 的 ldconfig,Mac OS 的
otool 和 windows 的 ListDLLs(form sysInternals)都可以帮你识别共享依赖和可能的版本冲突。
使用 FastCGI 部署 Django 应用
尽管将使用 Apache 和 mod_python 搭建 Django 环境是最具鲁棒性的,但在很多虚拟主机平台上,往往只能使用 FastCGI
此外,在很多情况下,FastCGI 能够提供比 mod_python 更为优越的安全性和效能。针对小型站点,相对于 Apache 来说 FastCGI 更为轻量级。
FastCGI 简介
如何能够由一个外部的应用程序很有效解释WEB 服务器上的动态页面请求呢?答案就是使用
FastCGI! 它的工作步骤简单的描述起来是这样的:1、WEB 服务器收到客户端的页面请求 2、
WEB 服务器将这个页面请求委派给一个 FastCGI 外部进程(WEB 服务器于 FastCGI 之间是通过 socket 来连接通讯的) 3、FastCGI 外部进程得到 WEB 服务器委派过来的页面请求信息后进行处理,并且将处理结果(动态页面内容)返回给 WEB 服务器 4、Web 服务器将 FastCGI返回回来的结果再转送给客户端浏览器。
和 mod_python 一样,FastCGI 也是驻留在内存里为客户请求返回动态信息,而且也免掉了像传统的 CGI 一样启动进程时候的时间花销。但于 mod_python 不同之处是它并不是作为模块运行在 web 服务器同一进程内的,而是有自己的独立进程。
为什么要在一个独立的进程中运行代码?
在以传统的方式的几种以 mod_*方式嵌入到 Apache 的脚本语言中(常见的例 如:PHP,
Python/mod_python 和 Perl/mod_perl),他们都是以 apache 扩展模块的方式将自身嵌入到
Apache 进程中 的。尽管这种方式可以减低启动时候的时间花销(因为代码不用在每次收到访问请求的时候都读去硬盘数据),但这是以增大内存的开销来作为代价的。
每一个 Apache 进程都是一个 Apache 引擎的副本,它完全包括了所有 Apache 所具有的一切功能特性(哪怕是对 Django 毫无好处的东西也一并加载进来)。而 FastCGI 就不一样了,它仅仅把 Python 和 Django 等必备的东东弄到内存中。
依据 FastCGI 自身的特点可以看到,FastCGI 进程可以与 Web 服务器的进程分别运行在不同的用户权限下。对于一个多人共用的系统来说,这个特性对于安全性是非常有好处的,因为你可以安全的于别人分享和重用代码了。
如果你希望你的 Django 以 FastCGI 的方式运行,那么你还必须安装 flup 这个 Python 库,这个库就是用于处理 FastCGI 的。很多用户都抱怨 flup 的发布版太久了,老是不更新。其实不是的,他们一直在努力的工作着,这是没有放出来而已。但你可以通过
http://www.djangoproject.com/r/flup/ 获取他们的最新的 SVN 版本。
运行你的 FastCGI 服务器
FastCGI 是以客户机/服务器方式运行的,并且在很多情况下,你得自己去启动 FastCGI 的服务进程。 Web 服务器(例如 Apache,lighttpd 等等)仅仅在有动态页面访问请求的时候才会去与你的 Django-FastCGI 进程交互。因为 Fast-CGI 已经一直驻留在内存里面了的,所以它响应起来也是很快的。
注意
在虚拟主机上使用的话,你可能会被强制的使用 Web server-managed FastCGI 进程。在这样的情况下,请参阅下面的“在 Apache 共享主机里运行 Django”这一小节。
web 服务器有两种方式于 FastCGI 进程交互:使用 Unix domain socket(在 win32 里面是 命名管道 )或者使用 TCP socket.具体使用哪一个,那就根据你的偏好而定了,但是 TCP socket弄不好的话往往会发生一些权限上的问题。
开始你的服务器项目,首先进入你的项目目录下(你的 manage.py 文件所在之处),然后使用 manage.py runfcgi 命令:
./manage.py runfcgi [options]
想了解如何使用 runfcgi ,输入 manage.py runfcgi help 命令。
你可以指定 socket 或者同时指定 host 和 port 。当你要创建 Web 服务器时,你只需要将服务器指向当你在启动 FastCGI 服务器时确定的 socket 或者 host/port。
范例:
在 TCP 端口上运行一个线程服务器:
./manage.py runfcgi method=threaded host=127.0.0.1 port=3033在 Unix socket 上运行 prefork 服务器:
./manage.py runfcgi method=prefork socket=/home/user/mysite.sock pidfile=django.pid
启动,但不作为后台进程(在调试时比较方便):
./manage.py runfcgi daemonize=false socket=/tmp/mysite.sock停止 FastCGI 的行程
如果你的 FastCGI 是在前台运行的,那么只需按 Ctrl+C 就可以很方便的停止这个进程了。但如果是在后台运行的话,你就要使用 Unix 的 kill 命令来杀掉它。
如果你在 manage.py runfcgi 中指定了 pidfile 这个选项,那么你可以这样来杀死这个
FastCGI 后台进程: kill `cat $PIDFILE`
$PIDFILE 就是你在 pidfile 指定的那个。
你可以使用下面这个脚本方便地重启 Unix 里的 FastCGI 守护进程:
#!/bin/bash
# Replace these three settings. PROJDIR="/home/user/myproject" PIDFILE="$PROJDIR/mysite.pid" SOCKET="$PROJDIR/mysite.sock"
cd $PROJDIR
if [ -f $PIDFILE ]; then kill `cat -- $PIDFILE` rm -f -- $PIDFILE
fi
exec /usr/bin/env - \ PYTHONPATH="../python:.." \
./manage.py runfcgi socket=$SOCKET pidfile=$PIDFILE
在 Apache 中以 FastCGI 的方式使用 Django
在 Apache 和 FastCGI 上使用 Django,你需要安装和配置 Apache,并且安装 mod_fastcgi。请参见 Apache 和 mod_fastcgi 文档: http://www.djangoproject.com/r/mod_fastcgi/ 。
当完成了安装,通过 httpd.conf (Apache 的配置文件)来让 Apache 和 Django FastCGI 互相通信。你需要做两件事:
使用 FastCGIExternalServer 指明 FastCGI 的位置。
使用 mod_rewrite 为 FastCGI 指定合适的 URL。指定 FastCGI Server 的位置
FastCGIExternalServer 告诉 Apache 如何找到 FastCGI 服务器。 按照
FastCGIExternalServer 文档
( http://www.djangoproject.com/r/mod_fastcgi/FastCGIExternalServer/ ),你可以指明 socket 或者 host 。以下是两个例子:
# Connect to FastCGI via a socket/named pipe: FastCGIExternalServer /home/user/public_html/mysite.fcgi -socket
/home/user/mysite.sock
# Connect to FastCGI via a TCP host/port:
FastCGIExternalServer /home/user/public_html/mysite.fcgi -host 127.0.0.1:3033
在这两个例子中, /home/user/public_html/ 目录必须存在,而
/home/user/public_html/mysite.fcgi 文件不一定存在。它仅仅是一个 Web 服务器内部使用的接口,这个 URL 决定了对于哪些 URL 的请求会被 FastCGI 处理(下一部分详细讨论)。
使用 mod_rewrite 为 FastCGI 指定 URL
第二步是告诉 Apache 为符合一定模式的 URL 使用 FastCGI。为了实现这一点,请使用
mod_rewrite 模块,并将这些 URL 重定向到 mysite.fcgi (或者正如在前文中描述的那样,使用任何在 FastCGIExternalServer 指定的内容)。
在这个例子里面,我们告诉 Apache 使用 FastCGI 来处理那些在文件系统上不提供文件(译者注:也就是指向虚拟文件)和没有从 /media/ 开始的任何请求。如果你使用 Django 的 admin站点,下面可能是一个最普通的例子:
<VirtualHost 12.34.56.78> ServerName example.com
DocumentRoot /home/user/public_html
Alias /media /home/user/python/django/contrib/admin/media
RewriteEngine On
RewriteRule ^/(media.*)$ /$1 [QSA,L] RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^/(.*)$ /mysite.fcgi/$1 [QSA,L]
</VirtualHost>
FastCGI 和 lighttpd
lighttpd (http://www.djangoproject.com/r/lighttpd/) 是一个轻量级的 Web 服务器,通常被用来提供静态页面的访问。它天生支持 FastCGI,因此除非你的站点需要一些 Apache 特有的特性,否则,lighttpd 对于静态和动态页面来说都是理想的选择。
确保 mod_fastcgi 在模块列表中,它需要出现在 mod_rewrite 和 mod_access ,但是要在
mod_accesslog 之前。你也可能需要 mod_alias 模块,来提供管理所用的媒体资源。将下面的内容添加到你的 lighttpd 的配置文件中:
server.document-root = "/home/user/public_html" fastcgi.server = (
"/mysite.fcgi" => ( "main" => (
# Use host / port instead of socket for TCP fastcgi
# "host" => "127.0.0.1",
# "port" => 3033,
"socket" => "/home/user/mysite.sock", "check-local" => "disable",
)
),
)
alias.url = (
"/media/" => "/home/user/django/contrib/admin/media/",
)
url.rewrite-once = ( "^(/media.*)$" => "$1",
"^/favicon\.ico$" => "/media/favicon.ico", "^(/.*)$" => "/mysite.fcgi$1",
)
在一个 lighttpd 进程中运行多个 Django 站点
lighttpd 允许你使用条件配置来为每个站点分别提供设置。为了支持 FastCGI 的多站点,只需要在 FastCGI 的配置文件中,为每个站点分别建立条件配置项:
# If the hostname is 'www.example1.com'...
$HTTP["host"] == "www.example1.com" { server.document-root = "/foo/site1" fastcgi.server = (
...
)
...
}
# If the hostname is 'www.example2.com'...
$HTTP["host"] == "www.example2.com" { server.document-root = "/foo/site2" fastcgi.server = (
...
)
...
}
你也可以通过 fastcgi.server 中指定多个入口,在同一个站点上实现多个 Django 安装。请为每一个安装指定一个 FastCGI 主机。
在使用 Apache 的共享主机服务商处运行 Django
许多共享主机的服务提供商不允许运行你自己的服务进程,也不允许修改 httpd.conf 文件。尽管如此,仍然有可能通过 Web 服务器产生的子进程来运行 Django。
备注
如果你要使用服务器的子进程,你没有必要自己去启动 FastCGI 服务器。Apache 会自动产生一些子进程,产生的数量按照需求和配置会有所不同。
在你的 Web 根目录下,将下面的内容增加到 .htaccess 文件中: AddHandler fastcgi-script .fcgi
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*)$ mysite.fcgi/$1 [QSA,L]
接着,创建一个脚本,告知 Apache 如何运行你的 FastCGI 程序。创建一个 mysite.fcgi 文件,并把它放在你的 Web 目录中,打开可执行权限。
#!/usr/bin/python import sys, os
# Add a custom Python path. sys.path.insert(0, "/home/user/python")
# Switch to the directory of your project. (Optional.)
# os.chdir("/home/user/myproject")
# Set the DJANGO_SETTINGS_MODULE environment variable. os.environ['DJANGO_SETTINGS_MODULE'] = "myproject.settings"
from django.core.servers.fastcgi import runfastcgi runfastcgi(method="threaded", daemonize="false")
重启新产生的进程服务器
如果你改变了站点上任何的 python 代码,你需要告知 FastCGI。但是,这不需要重启 Apache,而只需要重新上传 mysite.fcgi 或者编辑改文件,使得修改时间发生了变化,它会自动帮你重启 Django 应用。
如果你拥有 Unix 系统命令行的可执行权限,只需要简单地使用 touch 命令: touch mysite.fcgi
可扩展性
既然你已经知道如何在一台服务器上运行 Django,让我们来研究一下,如何扩展我们的
Django 安装。这一部分我们将讨论,如何把一台服务器扩展为一个大规模的服务器集群,这样就能满足每小时上百万的点击率。
有一点很重要,每一个大型的站点大的形式和规模不同,因此可扩展性其实并不是一种千篇一律的行为。以下部分会涉及到一些通用的原则,并且会指出一些不同选择。
首先,我们来做一个大的假设,只集中地讨论在 Apache 和 mod_python 下的可扩展性问题。尽管我们也知道一些成功的中型和大型的 FastCGI 策略,但是我们更加熟悉 Apache。
运行在一台单机服务器上
大多数的站点一开始都运行在单机服务器上,看起来像图 20-1 这样的构架。
图 20-1:一个单服务器的 Django 安装。
这对于小型和中型的站点来说还不错,并且也很便宜,一般来说,你可以在 3000 美元以下就搞定一切。
然而,当流量增加的时候,你会迅速陷入不同软件的 资源争夺 之中。数据库服务器和 Web服务器都 喜欢 自己拥有整个服务器资源,因此当被安装在单机上时,它们总会争夺相同的资源(RAM, CPU),它们更愿意独享资源。
通过把数据库服务器搬移到第二台主机上,可以很容易地解决这个问题。这将在下一部分介绍。
分离出数据库服务器
对于 Django 来说,把数据库服务器分离开来很容易:只需要简单地修改 DATABASE_HOST ,设置为新的数据库服务器的 IP 地址或者 DNS 域名。设置为 IP 地址总是一个好主意,因为使用 DNS 域名,还要牵涉到 DNS 服务器的可靠性连接问题。
使用了一个独立的数据库服务器以后,我们的构架变成了图 20-2。
图 20-2:将数据库移到单独的服务器上。
这里,我们开始步入 n-tier 构架。不要被这个词所吓坏,它只是说明了 Web 栈的不同部分,被分离到了不同的物理机器上。
我们再来看,如果发现需要不止一台的数据库服务器,考虑使用连接池和数据库备份将是一个好主意。不幸的是,本书没有足够的时间来讨论这个问题,所以你参考数据库文档或者向社区求助。
运行一个独立的媒体服务器
使用单机服务器仍然留下了一个大问题:处理动态内容的媒体资源,也是在同一台机器上完成的。
这两个活动是在不同的条件下进行的,因此把它们强行凑和在同一台机器上,你不可能获得很好的性能。下一步,我们要把媒体资源(任何 不是 由 Django 视图产生的东西)分离到别的服务器上(请看图 20-3)。
图 20-3:分离出媒体服务器。
理想的情况是,这个媒体服务器是一个定制的 Web 服务器,为传送静态媒体资源做了优化。
lighttpd 和 tux (http://www.djangoproject.com/r/tux/) 都是极佳的选择,当然瘦身的 Apache 服务器也可以工作的很好。
对于拥有大量静态内容(照片、视频等)的站点来说,将媒体服务器分离出去显然有着更加重要的意义,而且应该是扩大规模的时候所要采取的 第一步措施 。
这一步需要一点点技巧,Django 的 admin 管理接口需要能够获得足够的权限来处理上传的媒体(通过设置 MEDIA_ROOT )。如果媒体资源在另外的一台服务器上,你需要获得通过网络写操作的权限。
最简单的方案是使用 NFS(网络文件系统)把媒体服务器的目录挂载到 Web 服务器上来。只要 MEDIA_ROOT 设置正确,媒体的上传就可以正常工作。
实现负担均衡和数据冗余备份
现在,我们已经尽可能地进行了分解。这种三台服务器的构架可以承受很大的流量,比如每天 1000 万的点击率。如果还需要进一步地增加,你就需要开始增加冗余备份了。
这是个好主意。请看图 20-3,一旦三个服务器中的任何一个发生了故障,你就得关闭整个站点。因此在引入冗余备份的时候,你并不只是增加了容量,同时也增加了可靠性。
我们首先来考虑 Web 服务器的点击量。把同一个 Django 的站点复制多份,在多台机器上同时运行很容易,我们也只需要同时运行多台机器上的 Apache 服务器。
你还需要另一个软件来帮助你在多台服务器之间均衡网络流量: 流量均衡器(load
balancer) 。你可以购买昂贵的专有的硬件均衡器,当然也有一些高质量的开源的软件均衡器可供选择。
Apaches 的 mod_proxy 是一个可以考虑的选择,但另一个配置更棒的选择是:Perlbal (http://www.djangoproject.com/r/perlbal/) 。Perlbal 是一个均衡器,同时也是一个反向代理,它的开发者和 memcached 的开发者是同一拨人(请见 13 章)。
备注
如果你使用 FastCGI,你同样可以分离前台的 web 服务器,并在多台其他机器上运行 FastCGI服务器来实现相同的负载均衡的功能。前台的服务器就相当于是一个均衡器,而后台的
FastCGI 服务进程代替了 Apache/mod_python/Django 服务器。
现在我们拥有了服务器集群,我们的构架慢慢演化,越来越复杂,如图 20-4。
图 20-4:负载均衡的服务器设置。
值得一提的是,在图中,Web 服务器指的是一个集群,来表示许多数量的服务器。一旦你拥有了一个前台的均衡器,你就可以很方便地增加和删除后台的 Web 服务器,而且不会造成任何网站不可用的时间。
慢慢变大
下面的这些步骤都是上面最后一个的变体:
当你需要更好的数据库性能,你可能需要增加数据库的冗余服务器。MySQL 内置了备份功能;PostgreSQL 应该看一下 Slony (http://www.djangoproject.com/r/slony/)
和 pgpool (http://www.djangoproject.com/r/pgpool/) ,这两个分别是数据库备份和连接池的工具。
如果单个均衡器不能达到要求,你可以增加更多的均衡器,并且使用轮训(round-robin)
DNS 来实现分布访问。
如果单台媒体服务器不够用,你可以增加更多的媒体服务器,并通过集群来分布流量。
如果你需要更多的高速缓存(cache),你可以增加 cache 服务器。
在任何情况下,只要集群工作性能不好,你都可以往上增加服务器。重复了几次以后,一个大规模的构架会像图 20-5。
图 20-5。大规模的 Django 安装。
尽管我们只是在每一层上展示了两到三台服务器,你可以在上面随意地增加更多。
当你到了这一个阶段,你有一些选择。附录 A 有一些开发者关于大型系统的信息。如果你想要构建一个高流量的 Django 站点,那值得一读。
性能优化
如果你有大笔大笔的钱,遇到扩展性问题时,你可以简单地投资硬件。对于剩下的人来说,性能优化就是必须要做的一件事。
备注
顺便提一句,谁要是有大笔大笔的钞票,请捐助一点 Django 项目。我们也接受未切割的钻石和金币。
不幸的是,性能优化比起科学来说更像是一种艺术,并且这比扩展性更难描述。如果你真想要构建一个大规模的 Django 应用,你需要花大量的时间和精力学习如何优化构架中的每一部分。
以下部分总结了多年以来的经验,是一些专属于 Django 的优化技巧。
RAM 怎么也不嫌多
写这篇文章的时候,RAM 的价格已经降到了每 G 大约 200 美元。购买尽可能多的 RAM,再在别的上面投资一点点。
高速的处理器并不会大幅度地提高性能;大多数的 Web 服务器 90%的时间都浪费在了硬盘 IO上。当硬盘上的数据开始交换,性能就急剧下降。更快速的硬盘可以改善这个问题,但是比起 RAM 来说,那太贵了。
如果你拥有多台服务器,首要的是要在数据库服务器上增加内存。如果你能负担得起,把你整个数据库都放入到内存中。这不会很难。LJWorld.com 等网站的数据库保存了大量从 1989年起至今的报纸和文章,内存的消耗也不到 2G。
下一步,最大化 Web 服务器上的内存。最理想的情况是,没有一台服务器进行磁盘交换。如果你达到了这个水平,你就能应付大多数正常的流量。
禁用 Keep-Alive
Keep-Alive 是 HTTP 提供的功能之一,它的目的是允许多个 HTTP 请求复用一个 TCP 连接,也就是允许在同一个 TCP 连接上发起多个 HTTP 请求,这样有效的避免了每个 HTTP 请求都重新建立自己的 TCP 连接的开销。
这一眼看上去是好事,但它足以杀死 Django 站点的性能。如果你从单独的媒体服务器上向用户提供服 务,每个光顾你站点的用户都大约 10 秒钟左右发出一次请求。这就使得 HTTP 服务器一直在等待下一次 keep-alive 的请求,空闲的 HTTP 服务器和工作时消耗一样多的内存。
使用 memcached
尽管 Django 支持多种不同的 cache 后台机制,没有一种的性能可以 接近 memcached。如果你有一个高流量的站点,不要犹豫,直接选择 memcached。
经常使用 memcached
当然,选择了 memcached 而不去使用它,你不会从中获得任何性能上的提升。第 13 章将为你提供有用的信息:学习如何使用 Django 的 cache 框架,并且尽可能地使用它。大量的可抢占式的高速缓存通常是一个站点在大流量下正常工作的唯一瓶颈。
参加讨论
Django 相关的每一个部分,从 Linux 到 Apache 到 PostgreSQL 或者 MySQL 背后,都有一个非常棒的社区支持。如果你真想从你的服务器上榨干最后 1%的性能,加入开源社区寻求帮助。多数的自由软件社区成员都会很乐意地提供帮助。
别忘了 Django 社区。这本书谦逊的作者只是 Django 开发团队中的两位成员。我们的社区有大量的经验可以提供。
下一步?
你已经看到了正文的结束部分了。下面的这些附录都包含了许多参考资料,当你构建你的 Django 项目时,有可能会用到。
我们希望你的 Django 站点运行良好,无论你的站点是你和你朋友之间的一个小玩具,还是下一个 Google。