在这本书中,我们提到了驱使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的最健壮搭配。
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> 指令后则是虚拟主机)都会由 Django 来处理。
注意
如果你在一个比 / 位置更深的子目录中部署 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 程序是完全可能的。当你是一个独立的 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 缓存预载入了 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 。另一个选择是在模板页面中加入调试信息。
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)都可以帮你识别共享依赖和可能的版本冲突。
尽管将使用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的服务进程。 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是在前台运行的,那么只需按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,并且安装mod_fastcgi。请参见Apache和mod_fastcgi文档: http://www.djangoproject.com/r/mod_fastcgi/ 。
当完成了安装,通过 httpd.conf (Apache的配置文件)来让Apache和Django FastCGI互相通信。你需要做两件事:
使用 FastCGIExternalServer 指明FastCGI的位置。
使用 mod_rewrite 为FastCGI指定合适的URL。
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处理(下一部分详细讨论)。
第二步是告诉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>
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允许你使用条件配置来为每个站点分别提供设置。为了支持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主机。
许多共享主机的服务提供商不允许运行你自己的服务进程,也不允许修改 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的价格已经降到了每G大约200美元。购买尽可能多的RAM,再在别的上面投资一点点。
高速的处理器并不会大幅度地提高性能;大多数的Web服务器90%的时间都浪费在了硬盘IO上。当硬盘上的数据开始交换,性能就急剧下降。更快速的硬盘可以改善这个问题,但是比起RAM来说,那太贵了。
如果你拥有多台服务器,首要的是要在数据库服务器上增加内存。如果你能负担得起,把你整个数据库都放入到内存中。这不会很难。LJWorld.com等网站的数据库保存了大量从1989年起至今的报纸和文章,内存的消耗也不到2G。
下一步,最大化Web服务器上的内存。最理想的情况是,没有一台服务器进行磁盘交换。如果你达到了这个水平,你就能应付大多数正常的流量。
Keep-Alive 是HTTP提供的功能之一,它的目的是允许多个HTTP请求复用一个TCP连接,也就是允许在同一个TCP连接上发起多个HTTP请求,这样有效的避免了每个HTTP请求都重新建立自己的TCP连接的开销。
这一眼看上去是好事,但它足以杀死Django站点的性能。如果你从单独的媒体服务器上向用户提供服务,每个光顾你站点的用户都大约10秒钟左右发出一次请求。这就使得HTTP服务器一直在等待下一次keep-alive 的请求,空闲的HTTP服务器和工作时消耗一样多的内存。
尽管Django支持多种不同的cache后台机制,没有一种的性能可以 接近 memcached。如果你有一个高流量的站点,不要犹豫,直接选择memcached。
当然,选择了memcached而不去使用它,你不会从中获得任何性能上的提升。第13章将为你提供有用的信息:学习如何使用Django的cache框架,并且尽可能地使用它。大量的可抢占式的高速缓存通常是一个站点在大流量下正常工作的唯一瓶颈。
Django相关的每一个部分,从Linux到Apache到PostgreSQL或者MySQL背后,都有一个非常棒的社区支持。如果你真想从你的服务器上榨干最后1%的性能,加入开源社区寻求帮助。多数的自由软件社区成员都会很乐意地提供帮助。
别忘了Django社区。这本书谦逊的作者只是Django开发团队中的两位成员。我们的社区有大量的经验可以提供。
你已经看到了正文的结束部分了。下面的这些附录都包含了许多参考资料,当你构建你的Django项目时,有可能会用到。
我们希望你的Django站点运行良好,无论你的站点是你和你朋友之间的一个小玩具,还是下一个Google。
关于本评注系统
本站使用上下文关联的评注系统来收集反馈信息。不同于一般对整章做评注的做法, 我们允许你对每一个独立的“文本块”做评注。一个“文本块”看起来是这样的:
一个“文本块”是一个段落,一个列表项,一段代码,或者其他一小段内容。 你选中它会高亮度显示:
要对文本块做评注,你只需要点击它旁边的标识块:
我们会仔细阅读每个评论,如果可能的话我们也会把评注考虑到未来的版本中去:
如果你愿意你的评注被采用,请确保留下你的全名 (注意不是昵称或简称)
Many, many thanks to Jack Slocum; the inspiration and much of the code for the comment system comes from Jack's blog, and this site couldn't have been built without his wonderful
YAHOO.ext
library. Thanks also to Yahoo for YUI itself.