http://www.mangbar.com/document/5d023b2114eab9f40114f76d30461133
如何优化、提高 Plone 的性能

大家关注的Plone的主要问题之一是速度。不可否认,如果你第一次使用Plone,会感觉很慢。

总体来说,Plone不是世界上最快的系统。从一开始,Plone就是用Python构建的,Python是一种不需要编译的解释语言。这种语言带来了巨大的灵活性,但是无法追到最快速的应用。

影响Plone速度的首要因素,Plone是一个非常复杂的系统。对于一个平常的HTML网站,可以做到从服务器载入只需要百分之一秒的时间(显然在不考虑网络条件下),Plone的主页面是完全动态地根据每个人的每次请求生成出来的。这导致可能需要5-6秒钟的时间载入一个页面。此外,Plone 中包含的javascript和css页面超过100kB,这样会导致超过30秒的时间下载,这就会导致非常慢的感觉。显然,如果你同一时间内有更多的用户访问,plone基本上就变得不可用了。

这可不是一个好消息。

不过,情况并不是完全糟糕。Plone底层的Zope应用程序从来就没有说是一个web服务器。还好,我们可以使用Apache作为前端Web服务器,并通过反向代理的程序来处理所有来自Web的连接,

本文后面部分中,所有的比对数据都是在一台1.8GHz, RAM 128MB的Intel Celeron服务器上测试的。该服务器运行基本的Redhat 7.3版本。Python使用Python.org上预编译好的RPM包,而Zope也是编译好的。使用的Plone版本是Plone 2.0 rc3.

要设置一个反向代理非常容易。首先配置你的Zope不要运行在80端口上。你可以修改Zope 2.5/2.6的启动脚本,或者编辑Zope 2.7中zope.conf的基本端口参数。下面的例子中,基本端口设置为8080, 就是说Zope会运行在8080端口上。

访问一下http://yoursite.com:8080进行测试。

现在,你需要在你的Zope的根文件夹中添加一个 Virtual Host Monster 。 名称可以任意,不过必须添加在Zope的根文件夹中,而不是Plone站点的根文件夹中。

你还要边界portal_skins/plone_templates/global_cache_settings模板来阻止Plone发送出 ‘Pragma: no-cache’ 的HTTP报头。缺省情况下,Plone屏蔽了所有的HTTP缓存。在ZMI中找到该模板,把它定制到你的custom皮肤文件夹中。现在,把它的内容编辑成::

<metal:cacheheaders define-macro="cacheheaders">
<metal:block tal:define="dummy python:request.RESPONSE.setHeader(‘Content-Type’, ‘text/html;;charse t= %s’ % charset)" />
<metal:block tal:define="dummy python:request.RESPONSE.setHeader(‘Content-Language’, lang)" />
<metal:block tal:define="dummy python:request.RESPONSE.setHeader(‘Expires’, ‘Sat, 1 Jan 2000 00:00:00 GMT’)" />
</metal:cacheheaders>

好,现在安装apache.(我使用的是Apache 1.3, 不过稍作调整即可适用于Apache 2).

这样,apache会运行在80端口上,你可以访问http://yoursite.com的缺省页面.

现在需要编辑apache的配置文件,其中有几个需要检查的事项:

1.

保证mod_proxy激活。找到载入libproxy.so模块的语句,确保一下代码行没有被注释掉:

LoadModule proxy_module modules/libproxy.so
AddModule mod_proxy.c

2.

为你的域名设置一个虚拟主机VirtualHost, 该虚拟主机为了将请求传递给Zope,需要使用反向代理。下面的例子中的代码可以剪切并粘贴到/etc/httpd/conf/httpd.conf配置文件的底部,你只需要编译几行:

<VirtualHost *>
# A sample VirtualHost section for using Apache as a webserver
# instead of Zope.
# ServerName is the url of your website.
ServerName yoursite.com
# Add serverAlias lines for other doamin names that should
# point to this website. They will be rewritten by Apache to
# the ServerName, so that anyone going to www.yoursite.com
# will be invisibly redirected to yoursite.com in their browser.
ServerAlias www.yoursite.com
# ServerAdmin is your email address, which shows up on error
# pages when Apache cannot connect to Zope.
ServerAdmin webmaster@yoursite.com
# The ProxyPass and ProxyPassReverse lines are the magic
# ingredients. They rewite requests to http://yoursite.com and
# pass the entire request rhough to Zope on
# http://yoursite.com:8080. The VirtualHostBase ensures that
# when the page goes back to the browser, it goes out through
# Apache, and appears to have come from http://yoursite.com.
# The line is made up from:
# ProxyPass or ProxyPassReverse
# / is the url at http://yoursite.com that you wish to use to
# point to the Zope site. You could keep http://yoursite.com as a
# flat HTML site in Apache, and replace / with /zope to make
# http://yoursite/com/zope point to your zope site.
# http://yoursite.com:8080 is the address that your zope is
# running on.
# /VirtualHostBase/http/yoursite.com:80 makes sure that zope
# *thinks* it is running at http://yousite.com instead of at
# http://yoursite.com:8080. You don’t have to do anything else
# in Zope to make this work.
# /yourplonesite is the location of your Plone Site within Zope.
# If you added a Plone Site into the root of your Zope with an id
# of ‘mysite’, then you just change this bit to /mysite
# /VirtualHostRoot/ makes your Plone site think it is the root of the site.
ProxyPass / http://yoursite.com:8080/VirtualHostBase/http/yoursite.com:80/yourplonesite/VirtualHostRoot/
ProxyPassReverse / http://yoursite.com:8080/VirtualHostBase/http/yoursite.com:80/yourplonesite/VirtualHostRoot/
</VirtualHost>

现在重启apache, 你应当能够看到http://yoursite.com已经变成你的Plone网站了。(如果你有任何问题,首先要确保/etc/httpd/modules/目录中有libproxy.so)

下载,你已经有了一个很好的设置:Apache专门用来接受web请求,而Zope是后断服务器。你现在还可以就有一个完全的虚拟主机的设置,因此你可以在只有单个IP地址的单个服务器上运行多个不同域名的网站。你需要作的所有工作就是复制并编辑每个网站的VirtualHost部分。

现在,你可能想看看这样的设置运行速度有多快。要知道的是,至此,我们并没有获得任何真正的速度提速,我们只是为此进行了一些基础工作。你可以在一台终端上使用Apache Benchmark程序来测试网站的速度。该程序通常在 ‘/usr/sbin/ab’

/usr/sbin/ab -n 100 http://yousite.com/

该命令会发送100次连续的请求到你的网站。你应当注意到请求需要大约0.5-1.0秒,没有很大的变化。虽然这样的结果对于一个动态页面来说不是太糟糕,但要记住该测试提取的仅仅是一个HTML页面。对于完整的带有CSS、Javascript和图片的页面,处理时间会长挺多的。

该测试的典型的输出结果像这样:

Benchmarking yoursite.com (be patient)…..done
Server Software: Zope/(unreleased
Server Hostname: yoursite.com
Server Port: 80
Document Path: /
Document Length: 32560 bytes
Concurrency Level: 1
Time taken for tests: 68.901 seconds
Complete requests: 100
Failed requests: 0
Broken pipe errors: 0
Total transferred: 3293500 bytes
HTML transferred: 3256000 bytes
Requests per second: 1.45 [#/sec] (mean)
Time per request: 689.01 [ms] (mean)
Time per request: 689.01 [ms] (mean, across all concurrent requests)
Transfer rate: 47.80 [Kbytes/sec] received

那么,我们任何才能提高速度呢?

我们能够做的第一件事是让Apache缓存页面结果。由于我们采用Apache作为前端服务器,因此通过完全可以做到,我们已经创建了一个带有缓存的反向代理,也就是说,所有的Zope生成的页面现在都通过Apache来传递给浏览器,并且可以被缓存起来,显著提高页面访问的性能。只是现在我们还没有告诉Apache要缓存任何东西。

要让Apache知道我们希望缓存的、具有某个过期时间设置的内容,必须安装mod_expires。查看你的Apache模块目录(通常为/etc/httpd/modules)时候有mod_expires.so,然后确认你的Apache配置文件中有下面几行:

LoadModule expires_module modules/mod_expires.so
AddModule mod_expires.c

现在,在你的虚拟主机部分,在</VirtualHost>行之前添加下面几行:

# CacheRoot is the location on the filesystem to store files that
# Apache caches. This directory must be created, and the user that
# Apache runs as must have full write permissions to it.
CacheRoot "/tmp/proxy/yoursite.com"
# CacheSize determines how big this cache can get in KB. It’s a
# good idea that this number is about 30% less than the available
# space in the CacheRoot directory. Here we choose to cache 10MB
# of data, which is enough for a personal website, but not for
# anything larger.
CacheSize 10000
# CacheGcInterval specifies how often (in hours) to examine the
# cache and delete obsolete files.
CacheGcInterval 2
# CacheLastModifiedFactor allows the estimation of an expiry date
# for a page if it doesn’t have an expiry-date specified in the
# HTTP headers returned from Zope. This is based on (time since
# last modification * CacheLastModifiedFactor), so that content
# that is ten hours old would be given an expiry date of 1 hour in
# the future.
CacheLastModifiedFactor 0.1
# CacheDefaultExpire sets a default expiry time of 1 hour into the
# future for cached pages.
CacheDefaultExpire 1
# CacheDirLength sets the number of characters used in directory
# names for subdirectories of CacheRoot
CacheDirLength 2
# The following definitions set expiry times for various content
# types. In this list, each content type defined is cached for a
# maximum period of 1 hour (3600 seconds) before it must be checked
# again. Non-listed content types are not cached.
ExpiresActive On
ExpiresByType image/gif A3600
ExpiresByType image/png A3600
ExpiresByType image/jpeg A3600
ExpiresByType text/css A3600
ExpiresByType text/javascript A3600
ExpiresByType application/x-javascript A3600
ExpiresByType text/html A3600
ExpiresByType text/xml A3600

完成后,保持配置文件。然后进入/tmp文件夹,此机一个刚才你编辑的配置文件中CacheRoot定义的目录,修改该目录的权限为apache用户可写。在我们的例子中,是这样的:

mkdir /tmp/proxy
mkdir /tmp/proxy/yoursite.com
chown -R apache:apache /tmp/proxy/yoursite.com

现在重启Apache让修改的配置生效。

至此,尽管Apache能够处理缓存,但是还没有缓存你的Plone网站中的任何页面。我们可以用wget来测试一下HTTP响应报文的报头:

wget -sS –delete-after http://yoursite.com/
–03:16:51– http://yoursite.com/
=> `index.html’
Resolving yoursite.com… done.
Connecting to yoursite.com[127.0.0.1]:80… connected.
HTTP request sent, awaiting response…
1 HTTP/1.1 200 OK
2 Date: Mon, 19 Jan 2004 03:16:51 GMT
3 Server: Zope/(unreleased version, python 2.3.2, linux2) ZServer/1.1 Plone/2.0-RC3
4 Vary: Accept-Encoding
5 Content-Length: 32560
6 Content-Language:
7 Expires: Mon, 19 Jan 2004 04:16:52 GMT
8 Etag:
9 Cache-Control: max-age=3600
10 Content-Type: text/html;charset=utf-8
11 X-Cache: MISS from yoursite.com
12 Connection: close

注意X-Cache报头显示缓存标志为MISS(缓存中没有的标志).首次访问一个页面时会遇到这样的情况(因为该页面的内容尚未被缓存),如果缓存正常的话,在后续对该页面的请求中,我们会遇到HIT(在缓存中的标志)。

接下来,在Apache成为一个有用的缓存器之前,需要设置你得内容页面的HTTP报头的属性,这样Zope就会告诉Apache哪些东西需要缓存,哪些不要缓存。不幸的是,由于Zope的Accelrated HTTP Cache Mangaer中有一个尚未解决的bug,设置工作比设想的要复杂一些。

首先,你需要编辑Accelerated HTTP Cache Manager的源代码。即Zope目录中的lib/python/Products/StandardCacheManagers/AcceleratedHTTPCacheManager.py.

把ZCache_set函数的最后部分(在101行附近)改为:

# Set HTTP Expires and Cache-Control headers
seconds=self.interval
expires=rfc1123_date(time.time() + seconds)
# note that in the original, this line was commented out.
RESPONSE.setHeader(‘Last-Modified’,rfc1123_date(time.time()))
RESPONSE.setHeader(‘Cache-Control’, ‘max-age=%d’ % seconds)
RESPONSE.setHeader(‘Expires’, expires)

这样就能够确保Zope返回一个有效的Last-Modified HTTP报头。如果没有这样修改,缓存会假设你的内容是动态生成的,不适合进行缓存。

现在,重启Zope使修改生效。

现在,你得系统具有了完整的缓存处理能力,接下来所要作的就是告诉你的Plone站点的Accelerated HTTP Cache Manager 应当缓存哪些内容。

用管理员权限的用户登入Zope站点,在ZMI中进入Plone。点击HTTPCache,查看Associate页签。注意,当前还没有任何相关的内容。

When associating items with the HTTPCache, you’re not associating content items, instead you associate the different views available. This means that you can ensure that your view template is cached, but edit templates are not. Ensuring that the options All for Locate Cacheable Objects

在关联HTTPCache内容条目时,你并不是把内容条目关联起来,二十把不同的可用的视图关联起来。就是说你可以确认你得视图模板被缓存起来,而编辑模板没有被缓存起来。需要确认的是选择Locate Cacheable Object的ALL、types(s)的ALL、和搜索Subfolders都已经选定,然后点击Locate按钮。稍过一会,你会看到你的网站中所有的皮肤元素中的一个列表。

需要特别注意的是你所选择的那些条目。你可以缓存所有的图片、文件、CSS和所有的JavaScripts.此外,你应当确认所有内容类型的view模板被缓存(比如,新闻项的newsitem_view)。特别注意的是在另一个皮肤文件夹中定制的那些条目。

现在,你应当有了一个非常长的缓存项列表,点击SAVE保存修改。

假设你已经正确完成上面的工作,你会发现Apache已经开始缓存你的页面。我们使用wget来测试,其输出的HTTP响应报头如下:

wget -sS –delete-after http://yoursite.com/
–03:28:46– http://yoursite.com/
=> `index.html’
Resolving yoursite.com… done.
Connecting to poked.org[217.199.181.94]:80… connected.
HTTP request sent, awaiting response…
1 HTTP/1.1 200 OK
2 Date: Mon, 19 Jan 2004 03:28:46 GMT
3 Server: Zope/(unreleased version, python 2.3.2, linux2) ZServer/1.1 Plone/2.0-RC3
4 Vary: Accept-Encoding
5 Content-Length: 32125
6 Expires: Mon, 19 Jan 2004 04:28:43 GMT
7 Last-Modified: Mon, 19 Jan 2004 03:28:43 GMT
8 Cache-Control: max-age=3600
9 Content-Type: text/html;charset=utf-8
10 Age: 4
11 X-Cache: HIT from yoursite.com
12 Connection: close

注意,下载X-Cache的报头为cache HIT。这正是我们要的结果。

既然Apache已经缓存了你的Plone页面,你就可以测试一下,看看有什么不同。还是使用Apache Benchmark工具,我们可以这样测试:

/usr/sbin/ab -n 100 http://yoursite.com/
Benchmarking poked.org (be patient)…..done
Server Software: Zope/(unreleased
Server Hostname: yoursite.com
Server Port: 80
Document Path: /
Document Length: 32125 bytes
Concurrency Level: 1
Time taken for tests: 0.116 seconds
Complete requests: 100
Failed requests: 0
Broken pipe errors: 0
Total transferred: 3252200 bytes
HTML transferred: 3212500 bytes
Requests per second: 862.07 [#/sec] (mean)
Time per request: 1.16 [ms] (mean)
Time per request: 1.16 [ms] (mean, across all concurrent requests)
Transfer rate: 28036.21 [Kbytes/sec] received

Note particulary the time taken for this test: 0.116 seconds, or a much healtier 0.0016 seconds per request! This gives some indication of how fast this site will be capable of running.

特别注意该测试使用的时间:0.116秒,或者是更有价值的每个请求的处理时间为0.0016!这是网站能够运行有多快的指标。

使用Apache Benchmark连续测试100次请求难以真正的评估出你的网站的运行速度,不过,要真正的模拟评估,需要使用并发特性,他可以让你同时发送请求。本例中ab用同时100个请求的方式共进行10000次的请求。这是相当于企业级的中/大型网站的负载量:

/usr/sbin/ab -n 100000 -c 100 http://yoursite.com/
Benchmarking poked.org (be patient)
Server Software: Zope/(unreleased
Server Hostname: poked.org
Server Port: 80
Document Path: /
Document Length: 32125 bytes
Concurrency Level: 100
Time taken for tests: 115.732 seconds
Complete requests: 100000
Failed requests: 0
Broken pipe errors: 0
Total transferred: -1041856680 bytes
HTML transferred: -1081567796 bytes
Requests per second: 864.07 [#/sec] (mean)
Time per request: 115.73 [ms] (mean)
Time per request: 1.16 [ms] (mean, across all concurrent requests)
Transfer rate: -9002.32 [Kbytes/sec] received

These results show that the combination of Apache and Zope is capable of serving 100000 requests in just under 2 minutes. It’s worth pointing out again that this is on a 1.8GHz Intel Celeron with only 128MB of RAM, running a basic Redhat 7.3 setup, which is very, very slow for a production server. On a typical Dual Intel Xeon 2.4GHz server with 1GB of RAM, expect a speed increase of over 100%.

该结果显示Apache和Zope的组合能够在2分钟内处理100000个请求。值对一提的是这是在一台运行基本Redhat7.3配置的、 1.8GHz仅有128MB内存的IntelCeleron服务器,这对于一个生产服务器来说是非常、非常慢的。在一台典型的双Intel Xeon 2.4GHz CPU、1GB内存的服务器上,处理速度可以提高超过100%。

值对一提的是这样的缓存是有一定的局限的。把Last-Modified报头设置成页面提取的时间,并且把最大时效设置为3600秒,该网站中的所有内容的生命周期都是1个小时。这就是说在最坏情况下,该网站的更新会在一个小时后才发生。这可以通过强制对某个特定页面进行缓存校验来解决,只需要在浏览器中进行CTRL-刷新页面即可。

另一个局限在于,如果你使用你的主域名来登入和编辑你的Plone网站,你可能会在浏览器中遇到一些奇怪的情况,比如无法正确缓存报头。一个简单的办法是为需要编辑网站的用户添加一个之域,比如cms.yoursite.com。这样,在你的Apache配置文件中,只需要为你得CMS简单新建一个没有任何缓存设置的VirtualHost部分。:

<VirtualHost *>
ServerName cms.yoursite.com
ServerAdmin webmaster@yoursite.com
ProxyPass / http://cms.yoursite.com:8080/VirtualHostBase/http/cms.yoursite.com:80/yourplonesite/VirtualHostRoot/
ProxyPassReverse / http://cms.yoursite.com:8080/VirtualHostBase/http/cms.yoursite.com:80/yourplonesite/VirtualHostRoot/
</VirtualHost>

这样,任何需要编辑网站内容的人都会整个绕过缓存。