Drupal 具有非常强大的灵活性和可扩展性,利用 Drupal 可以很方便地开发出我们想要的网站,然而伴随而来的也有其一直令人诟病的性能问题。尤其当网站变得很复杂时,每一次页面访问,需要进行大量的数据库查询与计算。因此有必要做一些性能方面的优化处理,一般我们会开启 Drupal 自带的页面缓存功能(在 admin/settings/performance 这里),它可以缓存页面的输出内容,减少数据库的查询。还可以安装Boost模块,该模块可以生成静态缓存页面,极大地提高页面响应速度。不过这两种方法都是只对匿名用户有效,对于登录网站的用户却没有作用。
本文主要介绍一下 Drupal 代码层面的缓存方法,在模块开发中如何利用 Drupal 内置的缓存API来处理我们自己的数据。
静态变量缓存
我们先来看一个简单的缓存处理:
| <?php
function mymodule_function($reset = FALSE) {
static $my_data;
if (!isset($my_data) || $reset) {
// 处理数据并将结果保存到 $my_data
}
return $my_data;
}
?>
|
注意静态变量 $my_data。函数第一次被调用的时候,静态变量的值为空,因此程序会处理数据并将结果保存到该静态变量,当函数第二次被调用的时候,静态变量会保持住上一次的赋值,因此程序直接将该变量返回,不再需要重复处理数据。这样就缩短了程序的执行时间,相应的就提升了性能。
另一个要注意的是 $reset 变量的使用。缓存虽然好,但是当我们更新了内容后,程序返回的却还是之前的旧数据就不好了。这就是 $reset 变量的作用了,我们在调用该函数时,将参数 $reset 设置为 TRUE,强制函数重新处理数据,就能刷新缓存了。
Drupal 的缓存API
估计大多数朋友已经知道,基于 PHP 一次请求的特性,前面说的静态变量的缓存方法只能在当前会话期间有效,显然实用性并不是那么高。在 Drupal 中我们可以使用更好的缓存办法,看下面代码:
| <?php
function mymodule_function($reset = FALSE) {
static $my_data;
if (!isset($my_data) || $reset) {
if (!$reset && ($cache = cache_get('mymodule:mydata'))) {
$my_data = $cache->data;
}
else {
// 处理数据并将结果保存到 $my_data
cache_set('mymodule:mydata', $my_data);
}
}
return $my_data;
}
?>
|
注意这里我们仍然使用了静态变量,只是加了一些其他处理:数据库层面的缓存。
Drupal中提供了三个关于缓存处理的函数:cache_get(),cache_set() 和 cache_clear_all(),具体如何使用请参考API详细页面。我们来看一下上面函数是怎么工作的:
在不强制刷新的前提下(默认 $reset = FALSE),如果 $my_data 不为空,那么函数直接将其返回,否则先去数据库查找是否有(mymodule:mydata) 对应的缓存内容,如果有,则将 $cache->data 赋给 $my_data ,否则程序就会走一遍数据处理流程,并将结果保存到 $my_data 以及数据库缓存中以供将来使用。
前面三个缓存API函数的第一个参数都是cid(缓存ID),ID我们可以随便写,但要注意的是避免与其他模块冲突,因此强烈建议缓存ID以你的模块名称加冒号开头(如 mymodule:mydata)。
这里其实相当于有两级缓存:先检查静态变量,再查找数据库缓存,如果都没有才需要处理数据并生成缓存,这种做法在 Drupal 模块里是很常见的。
清理缓存与更新内容
通过上面的方法,我们已经可以做到较长时间的缓存数据了。当我们更新了网站的内容后,缓存的数据可能就没有啥意义了,那我们要如何保证内容的及时更新呢?
除了上面提到的 $reset 强制刷新的方式,我们还可以清理掉旧的无用的缓存,让程序可以重新获取最新的内容。 默认地,只有其他某个模块调用了 cache_clear_all() 函数,我们的缓存才可能被清理掉。cache_clear_all() 不但可以清理所有的缓存,也可以只清理指定的缓存内容,就像这样:
| <?php
cache_clear_all('mymodule:mydata', 'cache');
?>
|
如果想要一次清理掉比较多的缓存,还可以使用该函数的第三个参数 $wildcard(默认为 FALSE),可以像下面这样使用:
| <?php
cache_clear_all('mymodule:', 'cache', TRUE);
?>
|
将 $wildcard 设为 TRUE,可以清理掉所有缓存ID以第一个参数(mymodule:)开头的缓存。现在知道为什么建议缓存ID以自己的模块名称接冒号开头了吧?
如果我们有些内容更新频率很有规律的话,其实还可以有另外一种方法。使用 cache_set() 函数设置缓存时,我们可以指定一个缓存过期的时间点:
| <?php
cache_set('mymodule:data', $my_data, 'cache', time() + 360);
?>
|
cache_set() 的最后一个参数可以是一个unix时间戳,以秒为单位,表示缓存过期的时间点。因此上面代码是指ID为 mymodule:data 的缓存将在生成后的1小时后过期。过期后的缓存会在下一次系统清理缓存时被移除。所以理论上说,cache_get() 是不会获取到过期的缓存的,但是目前这里有个bug,即使缓存过期了也还会被获取到。解决办法是使用 cache_get() 获取缓存以后,判断一下过期时间,如果当前时间小于缓存过期时间,就使用缓存中的数据,否则就重新获取最新的数据并刷新缓存:
| <?php
function mymodule_function($reset = FALSE) {
static $my_data;
if (!isset($my_data) || $reset) {
if (!$reset && ($cache = cache_get('mymodule:mydata') && time() < $cache->expire)) {
// if 条件中加了过期时间判断
$my_data = $cache->data;
}
else {
// 处理数据并将结果保存到 $my_data
cache_set('mymodule:mydata', $my_data);
}
}
return $my_data;
}
?>
|
自定义缓存处理
细心的朋友可能注意到 cache_set() 的第三个参数 ‘cache’,这是数据库中保存缓存的数据表名称,默认为 ‘cache’,也可以指定为其他的表名,内置可用的表名请参考该函数的API页面,也可以在数据库管理工具中查看一下。如果我们有很多数据需要缓存,最好是定义我们自己的缓存表,这样可以与其他模块的数据隔离开,也能相应地提升一点速度。
定义自己的数据表需要用到 hook_schema() 函数,可以使用 drupal_get_schema_unprocessed() 函数直接复制已存在的缓存表定义:
| <?php
function mymodule_schema() {
// 定义自己的缓存数据表 cache_mymodule
$schema['cache_mymodule'] = drupal_get_schema_unprocessed('system', 'cache');
return $schema;
}
?>
|
除了自定义缓存表,我们还可以替换 Drupal 内置的缓存处理。通过修改网站的 settings.php 文件,可以将缓存处理指向其他的实现方式,比较流行的是与开源的 memcached 项目集成,当然另外还有一些方式也是可以的(比如基于文件的缓存)。由于这些其他方式都是对于 cache_get(), cache_set() 和 cache_clear_all() 的不同实现,因此我们在模块中的代码是不需要修改的。
一些注意事项
任何事情都有个度的问题,过犹不及,我们处理缓存的时候也一样。有一些缓存是很没有意义的,比如我只需要从数据库中查询很简单的一两条记录,就完全没必要设置缓存了,有时反而会带来多余的开销。要缓存的一般是比较耗时的,或者会重复执行多次的查询,在 Drupal 中可以使用 Devel 模块来帮助我们找到满足条件的查询:它会记录页面中执行的所有查询,并高亮超过一定时间或者多次执行的查询,非常方便。
另外,缓存的数据是被序列化后存到数据库的,在某些场合中使用它是不太适合的,比如我们想用缓存的数据来拼接 SQL 查询语句,多表联合查询之类肯定是不行的。更好的解决方法是仔细思考模块的功能,设计出合适的数据表结构。
最后请注意,缓存数据也只是暂时存在的,因为如果其他模块调用了 cache_clear_all() 函数,你的缓存数据很可能就被清理掉了。因此,如果你无法再次生成你要的结果数据,那就千万不要将你的结果数据存到缓存中去。
结语
Drupal 的性能优化是我们开发中永远需要面对的问题,虽然默认的 Drupal 可能会慢得像乌龟爬,但是如果我们把它优化好了,也是能够像兔子那样跑得快了。本文介绍了 Drupal6 中最基本的缓存知识,对于 Drupal 还有很多其他的优化方法,如果对 Drupal 性能方面比较感兴趣,可以参考 Pressflow和 Drupal与高性能网站架构 这两个网站。