[2009/04/16]发布《Drupal项目实战-公司订餐系统(四)》本博客内容均为原创(Original),如有雷同,纯属巧合。转载请注明出处。同时欢迎学术探讨与批评。

2009年1月6日星期二

Drupal 缓存基础

关键字:DRUPAL,缓存,API

方医生注

本文介绍了Drupal的缓存机制,主要是自定义模块开发时,如何使用缓存的问题。英文原文地址为:http://www.lullabot.com/articles/a_beginners_guide_to_caching_data。不过我在翻译时,并没有100%按照原文,进行了一些改进,使其读其来更像中文文章。如果大家觉得有不明白的地方,欢迎指出。

正文开始

在Drupal中创建复杂的、动态的内容是很easy的,但需要做出一定的牺牲的。现在很多Web 2.0的特性的加入,使得网站在变得很cool的同时,也坠入了“性能恶梦(performance nightmare)”的深渊中。每一个节点的加载或一个页面的加载,都会引起高负载、大量的数据库访问、复杂的计算和大量的客户端脚本的执行。

一个解决方案是在Drupal的管理界面开启页面缓存。这个可以使匿名用户(即不登录的情况下)访问速度增加,因为Drupal会把每一个要输出的页面预先缓存(方医生注:请注意!不是缓存成HTML页面,而是把整个页面的HTML内容序列化为字符串,然后存入数据库),这样可以大大的减少数据库的查询次数。但是,这个对于登录的用户是没有任何效果的:因为页面级的缓存是一种“要么全是要么就一点都没有(all-or-nothing)”的缓存方式,它只有在很标准的情况下才有效,并且所有用户看到的视图都是一样的。

不过,早晚有一天你需要挖掘你的代码,找出数据库的查询访问热点(hot spots),然后自己对程序进行缓存。幸运的是(我最喜欢看到Fortunately这个单词),Drupal有一些内建的关于缓存的API和一些指导性的文档,使得我们可以很easy的自定义Drupal程序的缓存。

基础

做优化和缓存的第一条(The first rule)就是:永远不要使消耗时间的操作执行两次!要充分利用第一次执行的结果,并重用它们(Never do something time consuming twice if you can hold onto the results and re-use them.)下面我们看一个简单的示例:

<?php

function my_module_function($reset = FALSE) {
  static $my_data;
  if (!isset($my_data) || $reset) {
    // 在些进行复杂的计算,并使用正确的内容生成$my_data变量
  }
  return $my_data;
}

?>

这段代码中最重要的部分,就是这个函数创建了一个静态变量——$my_data.静态变量的好处在于,当其第一次被赋值后,就可以被持续的使用,即使这个函数被重新调用。也就是说,我们先检查这个变量是否被赋值,如果已经有值了,那就不需要重新计算,只需要直接返回其值即可。

这种模式经常在Drupal中使用——其中包括很多关键的函数,如node_load。这个函数在被第一次调用时,根据传入的节点ID,查询数据库得到节点对像,并赋值于一个静态变量;然后如果这个节点再在一个Block中被调用,或被列表中调用,就不会再次查询数据库了。

另一个重要的特性是使用$reset参数。缓存确实很好,但偶尔你也需要确保用户访问的页面所得到的数据是绝对新鲜的(absolute freshest)。这时$reset变量就派上用场了。我们可以在需要更新缓存的时候,将$reset设置为TRUE。

Drpual的缓存函数

你可能注意到,静态变量只能在一个单独的页面生存周期内有效。如果需要更好的性能,我们可以使缓存的数据更持久……

<?php

function my_module_function($reset = FALSE) {
  static $my_data;
  if (!isset($my_data) || $reset) {
    if (!$reset && ($cache = cache_get('my_module_data')) && !empty($cache->data)) {
      $my_data = unserialize($cache->data);
    }
    else {
      // 处理复杂计算,生成$my_data变量
      cache_set('my_module_data', 'cache', serialize($my_data));
    }
  }
  return $my_data;
}
?>
这个版本的函数仍然使用静态变量,但是它增加了新的一个层(layer):数据缓存层。Drupal的API提供三个关键的函数你需要了解:cache_get(), cache_set()和cache_clear_all()。接下来让我们看看如何使用它们。

回到上面的函数,在对静态变量做初始检查后,这个函数就会通过一个键来查找Drupal缓存(数据库中的缓存)。如果能够查到,并且$cache->data元素是非空的,函数公反序列化缓存数据,并将其存入$my_data变量中。

如果没有缓存被找到(或如果$reset == TRUE),函数就会实际生成数据。然后它会将其序列化再存入数据库,这样以后就可以在将来被使用了。缓存的键值(key)的命名最好以你自定义模块的名称为前缀,这样便于标识。

最终的结果是什么?一个很聪明很轻便的函数可能节省很多的时间——首先检查内存中是否已有变量存在(静态变量),然后再检查数据库缓存,如果这些都没有那就重新生成。如果你查看很多针对数据库查询的模块的话,会发现很多模块都应用了这种模式。

保持最新

发生了什么呢?如果你缓存的数据过期了,并且需要重新计算呢?默认的情况下,缓存的信息将会一直存在直到某模块执行了cache_clear_all()函数,清空所有的数据。如果你的数据偶尔的被更新,那么当每次你需要做一些更改时你或许可以考虑简单的调用cache_clear_all(‘my_module_data’, ‘cache’)。如果你缓存了很多数据(比如你为一个模块存储了n个缓存,或为每个角色都做了个缓存),那么可以传入第三个参数给cache_clear_all函数,以使其全部清空:

<?php

cache_clear_all('my_module', 'cache', TRUE);

?>

上面的函数将清空所有以“my_module”为前缀的键值的缓存。

如果你不需要使你的缓存“时刻保持最新“,但又需要使其“适当的”新鲜(reasonably fresh),你可以给cache_set函数传入一个过期时间。比如:

<?php
cache_set('my_module_data', 'cache', serialize($my_data), time() + 360);
?>

最后一个参数是一个UNIX时间戳,用来表示过期时间。最简单的方法是使用time()函数,再加上这个缓存的生存时间(以秒为单位)。过期的缓存会被自动的清除(应该是通过cron函数来实现)。

高级缓存

你可能注意到了cache_set()函数的第二个参数是“cache”——这是Drupal用于存放缓存的默认的数据表的名称。如果你要存储大量的数据在缓存中,你可以设置一个自定义的单独的缓存表,并通过这个参数传递给Drupal。这样可以使缓存更快一些,因为可以与其它的模块的缓存表分开。我们熟悉的Views模块就是使用的这种模式(单独创建缓存数据表),尤其当需要清除所有缓存时,单独的缓存表可以避免删除其它模块的缓存数据。

如果你真的希望充分利用服务器的每一个资源,Drupal还提供了可以使用其它缓存系统的机制。只需要改settings.php的一行代码,你就可以使三个标准函数cache_set(), cache_get(),和cache_clear_all()函数有不同的实现。基于文件的缓存(File-based caching),与memcached集成,或使用其它的缓存系统都成为可能。而且,只要你使用了Drupal的标准的缓存函数(就是上面提到的三个),那么你不需要在你的模块中更改任何代码,就可以实现缓存系统的移植。

一些忠告

过犹不及(Like all good things, it’s possible to overdo it)。有时用缓存是不需要的——如果你只是查找一条数据的话,把它存入缓存数据表就是很愚蠢的行为了。可以使用Devel模块来定位哪些查询降低了加载的性能。它可以高亮一些查询时间较长的查询,以及那些被反复执行的查询。

在其它时间,你使用的数据可能会不适合标准的缓存系统。如果你需要在SQL查询中连接缓存数据,比如,cache_set()将会把数据字符串序列化,有时就会造成麻烦(因为不能直接使用)。在这种情形下,你需要找一个针对你的模块的缓存解决方案。VotingAPI模块维护了一个数据表用于存放每个用户的投票结果,另一个数据表用于存储计算结果(如平均值、总和等),这样可以在SQL查询时,快速的进行连接排序和过滤节点。

最后,一定要记住:缓存不是一个长久的存储方式!因为如果其它的模块调用了cache_clear_all()函数的话,那么所有的缓存数据就都会消失。因此如果你要存放某些只能计算一次的数据的话,还是找个安全点的地方放比较好。

去西部吧,年青的Drupaler!

祝贺你:你现在拥有一套加速你的代码的强有力的工具了!前进,优化你的代码吧。

后记

本文的几个示例函数比较有参考价值,大家在写自定义模块时可以参考一下。缓存对网站的性能影响是相当的大,一定要在适当的条件下使用适当的缓存机制。

2 条评论:

  1. 太有贡献,期待以后更多此类的文章翻译。

    回复删除