跳转到主要内容
edxxu 提交于 21 April 2013

原文地址:http://www.lullabot.com/blog/articles/beginners-guide-caching-data-drupal-7

在Drupal 7中建立复杂的,动态的内容是简单的,但是这是有代价的。当一个用户查看一个node或者载入一个特定页面的时候,每一次都执行复杂的查询和大量的计算对于一个大流量的网站来说是一场“恶梦”。

 一个解决方案在Drupal管理界面打开性能选项:缓存页面。这可以为匿名用户缓存页面输出来加速,大大的减少了数据库查询。但是这对注册用户并不起作用,然而:因为页面级别的缓存是一见孤注一掷的事情,它只对标准化的,总是相同的的匿名用户看到的页面起作用。最终,你只能深入研究你的代码,找出数据库查询热点并且自己加入缓存。幸运的是,Drupal内建的缓存APIs和一些简单的指导方针可以使这个任务变的简单。

  基本原则

  第一条优化原则是:不要让可以重用并掌握结果的比较耗费时间的操作执行2次。让我们具体看看一个这条原则的例子:

<?php
function my_module_function() {
  $my_data = &drupal_static(__FUNCTION__);
  if (!isset($my_data)) {
    // Do your expensive calculations here, and populate $my_data
    // with the correct stuff..
  }
  return $my_data;
}
?>

这个例子中我们需要重点看一下变量$my_data,我们通过一个看起来比较奇怪的函数drupal_static()来初始化这个变量。drupal_static()这个函数是Drupal 7中新加入的,这个函数为数据提供一个临时的“存储仓库”,甚至可以一直保存到它们被执行之后。drupal_static()在第一次执行的时候会返回一个空值,但是变量的任何改变都会被保存。那意味着我们的函数可以检查变量是否已经被计算,并且立即返回结果而不需要做更多的运算。

这个模式几乎在Drupal的任何地方都有出现 -- 包括一些重要的函数中,如:node_load()。为一个指定的node ID调用node_load()会在第一次时查询数据库并将结果信息在一个页面载入过程中保存到一个静态变量里。那样的话,现在一个列表中显示一个node,然后在一个区块中显示一个node,然后第三次显示在一个相关列表连接中,这样不需要完整的执行3次数据库查询过程。

在Drupal 6中, 这些静态变量是使用PHP关键字“static”来创建的而不是drupal_static()函数(例子:The Drupal 6 version of this article)。通常我们会为其提供一个$reset参数来使用这个模式,给需要最新信息的模块一种不通过缓存的方式。这种方法仍然在Drupal 7有效,drupal_static()允许进程被集中化。当一个模块需要最新的数据时,他们可以调用drupal_static_reset()来清除所有的临时缓存信息。

让其成为永久性的缓存:Drupal的缓存函数

你可能注意到了静态变量技术只能在一个页面加载周期内缓存数据。为了更好的性能,最好能将数据用一种永久性的方式缓存...

<?php
function my_module_function() {
  $my_data = &drupal_static(__FUNCTION__);
  if (!isset($my_data)) {
    if ($cache = cache_get('my_module_data')) {
      $my_data = $cache->data;
    }
    else {
      // Do your expensive calculations here, and populate $my_data
      // with the correct stuff..
      cache_set('my_module_data', $my_data, 'cache');
    }
  }
  return $my_data;
}
?>

上面这个函数仍然使用了静态变量,但是它加入了另外一层逻辑:数据库缓存。Drupal的APIs提供了3个关键的函数,你需要对它们很熟悉:cache_get(),cache_set(),cache_clear_all()。让我们看一看它们是怎样被使用的。

  在最初对静态变量的检查之后,这个函数开始根据一个唯一的key来查找Drupal的缓存数据。如果找到了对应的数据,$my_data会被赋值为$cache->data。结合静态变量技术,在这页面请求的周期中请求相同的数据我们将不用再调用cache_get()函数!

如果没有找到之前缓存的数据,这个函数会执行真正的生成这个数据的过程。然后会将数据缓存,这样未来的请求将可以使用缓存的数据。caceh_set()函数中你传入的第一个参数你可以选择名称,但是重要的是你要避免与其他模块的缓存key同名,所以用你的模块名称来作为你的key的开头总是一个好的主意。

那这样做最终的结果是什么呢?这样一个函数可以节省时间 -- 首先检查内存中是否有缓存数据,然后检查数据库缓存,最后才是从头计算结果。如果你深入研究过数据密集的Drupal模块时,你会发现大量的此种做法。

保持数据是新的

尽管如此,那么当你缓存的数据过期了并需要被重新计算的时候会发生什么呢?默认的情况下,缓存的信息一直会保存到直到有模块调用cache_clear_all()函数来清除你的缓存。如果你的数据只需要部分的更新,你可能只需要简单的调用cache_clear_all('my_module_data', 'cache')来更新你的缓存。如果你缓存了很多碎片化的数据(可能是针对角色的一个特定区块的版本信息),这里有第三个'通配符'参数:

<?php
cache_clear_all('my_module', 'cache', TRUE); 
?>

这样的话会清除所有以'my_module'开头的key的缓存。

  如果你不需要你的缓存数据完美的已秒为单位保持最新,同事你想让它能合理的刷新,你可以通过在cache_set()函数中设置一个超时时间来做到。例如:

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

最后一个参数是一个unix时间戳,代表着缓存数据的超时时间。最简单的计算他的方法是使用time()函数,并且加上你希望的生存周期(单位:秒)。超时的数据会被自动丢弃。

为缓存的数据指定存储的位置

你可能已经注意到了cache_set()函数的第三个参数是'cache'-- 存储缓存数据的默认的数据表。如果你存储大量的数据,你可以建立你专门的缓存数据表并将表的名称作为第三个参数传入cache_set()。这样将帮助你保持你查找缓存的速度而不需要考虑其他模块的缓存。Views模块就使用了这种方法来维护它的缓存。

最简单的建立你自己的缓存表的地方是你的模块的 .install文件的hook_schema()函数中。这个函数定义了所有这个模块使用的数据库表,你甚至可以利用Drupal内部的帮助函数来简化这个过程。

<?php
function mymodule_schema() {
  $schema['cache_mymodule'] =drupal_get_schema_unprocessed('system', 'cache');
  return $schema;
}
?>

使用drupal_get_schema_unprocessed()函数,上面的代码获取了System模块定义的标准的缓存表结构,并且赋值给‘cache_mymodule’表。为自定义的缓存表添加前缀'cache'是Drupal的一个惯例,这样可以将各个缓存表更好的归纳组织。

如果你想继续压榨你服务器的性能,Drupal还支持使用其他缓存机制。你只需要在settings.php文件中简单的修改1行,你可以使用不同与标准的cache_set(),cache_get(), cache_clear_all()函数。最流行的整合方式是使用开源的memcached项目,但是其他的方法同样也是可行的(就像基于文件的缓存或者APC)。如果你使用的标准的Drupal缓存函数,你的模块的代码是不需要进行更改的。

 

对可显示的内容进行缓存

在Drupal 7中,“可显示数组”在构建每个页面显示的时候是被经常使用的。模块可以定义构建页面的元素,比如blocks,tables,forms,甚至是nodes作为结构化数组;当要显示页面HTML的时候,Drupal自动调用drupal_render()函数去处理他们,调用theme层和其他帮助函数。一些复杂的页面元素可能需要一些时间去转化为HTML。通过在可显示数组中添加专门的#cache属性,每当页面元素被构建的时候你可以使用drupal_render()函数去缓存和重用HTML。

<?php
$content['my_content'] = array(
  '#cache' => array(
    'cid' => 'my_module_data',
    'bin' => 'cache',
    'expire' => time() + 360,
  ),
  // Other element properties go here...
);
?>

如果你人工调用他们,#cache属性中含有一个值列表将参数映射到cache_get()和cache_set()。如果你想知道更多的关于缓存可显示数组是如何工作的信息,你可以查看具体的文档the drupal_render() function on api.drupal.org.

一些需要注意的地方

就想一些其他的好的东西,缓存有可能被过度的使用。一些时候,它根本就不需要 -- 如果你从数据库查找一条单独的记录,保存到数据库缓存中是脑残的行为。使用Devel模块是一个好方法去看看缓存是否合适:它可以记录你网站的数据库查询,并高亮显示慢查询,或者重复被使用很多次的查询。

另一些情况下,你正在使用的数据并不合适使用标准的缓存系统。如果你需要在SQL查询中联合查询你缓存的数据是,比如,cache_set()的处理字符串数据是会将字符串序列化就是一个问题。在这些例子中,你需要自己在你的模块中定制一个特殊的解决方案。VotingAPI模块维护了一张表来存储所有的单独的投票信息,并且为了在排序和过滤nodes时快速的查询维护了另外一张表用于存储所有的计算后的数据(平均数,和等)。

最后,请记住重要的一点,缓存不是一个长期的存储机制!因为其他模块可以调用cache_clear_all()来清苦缓存,你绝对不应该将一些不能重新计算得出的数据放到缓存中来当源数据使用。

标签
Drupal 版本