缓存是用于提升网站性能的一种即简单又有效的途径。通过存储相对静态的数据至缓存以备所需,我们可以省去生成这些数据的时间。
在 Yii 中使用缓存主要包括配置和访问缓存组件。如下的应用配置指定了一个使用两台缓存服务器的 memcache 缓存组件:
array(
......
'components'=>array(
......
'cache'=>array(
'class'=>'system.caching.CMemCache',
'servers'=>array(
array('host'=>'server1', 'port'=>11211, 'weight'=>60),
array('host'=>'server2', 'port'=>11211, 'weight'=>40),
),
),
),
);
程序运行的时候可以通过 Yii::app()->cache
来访问缓存组件。
Yii 提供多种缓存组件以便在不同的媒介上存储缓存数据。比如 CMemCache
组件封装了 PHP memcache 扩展,它使用内存作为存储缓存的媒介;CApcCache
组件封装了 PHP APC 扩展;CDbCache
组件在数据库里存储缓存数据。下面是各种缓存组件的简要说明:
CMemCache: 使用 PHP memcache 扩展。
CApcCache: 使用 PHP APC 扩展。
CXCache: 使用 PHP XCache 扩展。
CDbCache: 使用一张数据库表来存储缓存数据。它默认在运行时目录建立并使用一个 SQLite3 数据库,
你可以通过设置 connectionID 属性显式地指定一个数据库给它使用。
提示: 因为所有这些缓存组件都从同一个基础类 CCache 扩展而来,不需要修改使用缓存的代码即可在不同的缓存组件之间切换。
缓存可以在不同的级别使用。在最低级别,我们使用缓存来存储单个数据,比如一个变量,我们把它叫做 数据缓存。往上一级,我们缓存一个由视图脚本生成的页面片断。在最高级别,我们存储整个页面以便需要的时候直接从缓存读取。
接下来我们将阐述如何在这些级别上使用缓存。
注意: 按定义来讲,缓存是一个不稳定的存储媒介,它不保证缓存一定存在——不管该缓存是否过期。所以,不要使用缓存进行持久存储(比如,不要使用缓存来存储 SESSION 数据)。
一、数据缓存
数据缓存也就是在缓存中存储一些 PHP 变量,过一会再取出来。缓存基础类 CCache 提供了两个最常用的方法:set()
和 get()
。
要在缓存中存储变量 $value
,我们选择一个唯一 ID 并调用 set()
来存储它:
Yii::app()->cache->set($id, $value);
被缓存的数据会一直保留在缓存中,直到因一些缓存策略而被删除(比如缓存空间满了,删除最旧的数据)。要改变这一行为,我们还可以在调用 set()
时加一个过期参数,这样数据过一段时间就会自动从缓存中清除。
// 在缓存中保留该值最多 30 秒
Yii::app()->cache->set($id, $value, 30);
当我们稍后需要访问该变量时(不管是不是同一 Web 请求),我们调用 get()
(传入 ID)来从缓存中获取它。如果返回值为 false,说明该缓存不可用,需要我们重新生成它。
$value=Yii::app()->cache->get($id);
if($value===false)
{
// 因为在缓存中没找到,重新生成 $value
// 再缓存一下以备下次使用
// Yii::app()->cache->set($id,$value);
}
为一个要缓存的变量选择 ID 时,确保该 ID 在应用中是唯一的。不必保证 ID 在跨应用的情况下保证唯一,因为缓存组件有足够的智能来区分不同应用的缓存 ID。
要从缓存中删除一个缓存值,调用 delete()
;要清空所有缓存,调用 flush()
。调用 flush()
时要非常小心,因为它会把其它应用的缓存也清空。
提示: 因为 CCache 实现了 ArrayAccess 接口,可以像数组一样使用缓存组件。例如:
$cache=Yii::app()->cache;
$cache['var1']=$value1; // 相当于: $cache->set('var1',$value1);
$value2=$cache['var2']; // 相当于: $value2=$cache->get('var2');
缓存依赖
除了过期设置,缓存数据还会因某些依赖条件发生改变而失效。如果我们缓存了某文件的内容,而该文件后来又被更新了,我们应该让缓存中的拷贝失效,从文件中读取最新内容(而不是从缓存)。
我们把一个依赖关系表现为一个 CCacheDependency
或它的子类的实例,调用 set()
的时候把依赖实例和要缓存的数据一起传入。
// 缓存将在 30 秒后过期
// 也可能因依赖的文件有更新而更快失效
Yii::app()->cache->set($id, $value, 30, new CFileCacheDependency('FileName'));
如果我们现在调用 get() 从缓存中获取 $value,缓存组件将检查依赖条件。如果有变,我们会得到 false 值——数据需要重新生成。
下面是可用的缓存依赖的简要说明:
CFileCacheDependency: 该依赖因文件的最近修改时间发生改变而改变。
CDirectoryCacheDependency: 该依赖因目录(或其子目录)下的任何文件发生改变而改变。
CDbCacheDependency: 该依赖因指定的 SQL 语句的查询结果发生改变而改变。
CGlobalStateCacheDependency: 该依赖因指定的全局状态值发生改变而改变。
全局状态是应用中跨请求、跨 SESSION 的持久变量,
它由 CApplication::setGlobalState() 来定义。
CChainedCacheDependency: 该依赖因依赖链中的任何一环发生改变而改变。
二、片段缓存(Fragment Caching)
片段缓存指缓存网页某片段。例如,如果一个页面在表中显示每年的销售摘要,我们可以存储此表在缓存中,减少每次请求需要重新产生的时间。
要使用片段缓存,在控制器视图脚本中调用CController::beginCache()
和CController::endCache()
。这两种方法开始和结束包括的页面内容将被缓存。类似data caching
,我们需要一个编号,识别被缓存的片段。
...别的HTML内容...
beginCache($id)) { ?>
...被缓存的内容...
endCache(); } ?>
...别的HTML内容...
在上面的,如果beginCache()
返回false,缓存的内容将此地方自动插入; 否则,在if语句内的内容将被执行并在endCache()
触发时缓存。
1. 缓存选项(Caching Options)
当调用beginCache(),可以提供一个数组由缓存选项组成的作为第二个参数,以自定义片段缓存。事实上为了方便,beginCache()
和endCache()
方法是 COutputCache widget
的包装。因此COutputCache
的所有属性都可以在缓存选项中初始化。
2. 有效期(Duration)
也许是最常见的选项是duration,指定了内容在缓存中多久有效。和CCache::set()过期参数有点类似。下面的代码缓存内容片段最多一小时:
...其他HTML内容...
beginCache($id, array('duration'=>3600))) { ?>
...被缓存的内容...
endCache(); } ?>
...其他HTML内容...
如果我们不设定期限,它将默认为60 ,这意味着60秒后缓存内容将无效。
3. 依赖(Dependency)
像data caching ,内容片段被缓存也可以有依赖。例如,文章的内容被显示取决于文章是否被修改。 要指定一个依赖,我们建立了
dependency选项,可以是一个实现
ICacheDependency的对象或可用于生成依赖对象的配置数组。下面的代码指定片段内容取决
lastModified` 列的值是否变化:
...其他HTML内容...
beginCache($id, array('dependency'=>array(
'class'=>'system.caching.dependencies.CDbCacheDependency',
'sql'=>'SELECT MAX(lastModified) FROM Post')))) { ?>
...被缓存的内容...
endCache(); } ?>
...其他HTML内容...
4. 变化(Variation)
缓存的内容可根据一些参数变化。例如,每个人的档案都不一样。缓存的档案内容将根据每个人ID变化。这意味着,当调用beginCache()
时将用不同的ID。
COutputCache
内置了这一特征,程序员不需要编写根据ID变动内容的模式。以下是摘要。
varyByRoute: 设置此选项为true ,缓存的内容将根据route变化。
因此,每个控制器和行动的组合将有一个单独的缓存内容。
varyBySession: 设置此选项为true ,缓存的内容将根据session ID变化。
因此,每个用户会话可能会看到由缓存提供的不同内容。
varyByParam: 设置此选项的数组里的名字,缓存的内容将根据GET参数的值变动。
例如,如果一个页面显示文章的内容根据id的GET参数,我们可以指定varyByParam为array('id'),
以使我们能够缓存每篇文章内容。如果没有这样的变化,我们只能能够缓存某一文章。
5. 请求类型(Request Types)
有时候,我们希望片段缓存只对某些类型的请求启用。例如,对于某张网页上显示表单,我们只想要缓存initially requested
表单(通过GET请求)。任何随后显示(通过POST请求)的表单将不被缓存,因为表单可能包含用户输入。要做到这一点,我们可以指定requestTypes
选项:
...其他HTML内容...
beginCache($id, array('requestTypes'=>array('GET')))) { ?>
...被缓存的内容...
endCache(); } ?>
...其他HTML内容...
6. 嵌套缓存(Nested Caching)
片段缓存可以嵌套。就是说一个缓存片段附在一个更大的片段缓存里。例如,意见缓存在内部片段缓存,而且它们一起在外部缓存中在文章内容里缓存。
...其他HTML内容...
beginCache($id1)) { ?>
...外部被缓存内容...
beginCache($id2)) { ?>
...内部被缓存内容...
endCache(); } ?>
...外部被缓存内容...
endCache(); } ?>
...其他HTML内容...
嵌套缓存可以设定不同的缓存选项。例如, 在上面的例子中内部缓存和外部缓存可以设置时间长短不同的持续值。当数据存储在外部缓存无效,内部缓存仍然可以提供有效的内部片段。 然而,反之就不行了。如果外部缓存包含有效的数据, 它会永远保持缓存副本,即使内容中的内部缓存已经过期。
三、页面缓存
页面缓存指的是缓存整个页面的内容。页面缓存可以发生在不同的地方。例如,通过选择适当的页面头,客户端的浏览器可能会缓存网页浏览有限时间。 Web应用程序本身也可以在缓存中存储网页内容。 在本节中,我们侧重于后一种办法。
页面缓存可以被看作是 片段缓存 (/doc/guide/caching.fragment)一个特殊情况 。由于网页内容是往往通过应用布局来生成,如果我们只是简单的在布局中调用 beginCache()
和 endCache()
,将无法正常工作。这是因为布局在CController::render()
方法里的加载是在页面内容产生之后。
缓存整个页面,我们应该跳过产生网页内容的动作执行。我们可以使用 COutputCache
作为动作 过滤器 (/doc/guide/basics.controller#filter)来完成这一任务。下面的代码演示如何配置缓存过滤器:
public function filters()
{
return array(
array(
'system.web.widgets.COutputCache',
'duration'=>100,
'varyByParam'=>array('id'),
),
);
}
上述过滤器配置会使过滤器适用于控制器中的所有行动。我们可能会限制它在一个或几个行动通过使用插件操作器。更多的细节中可以看过滤器(/doc/guide/basics.controller#filter) 。
提示:我们可以使用 COutputCache
作为一个过滤器,因为它从CFilterWidget
继承过来 ,这意味着它是一个工具(widget)和一个过滤器。事实上, widge
的工作方式和过滤器非常相似:工具widget
(过滤器filter)是在action
动作里的内容执行前执行,在执行后结束。
四、动态内容(Dynamic Content)
当使用fragment caching
或page caching
,我们常常遇到的这样的情况整个部分的输出除了个别地方都是静态的。例如,帮助页可能会显示静态的帮助信息,而用户名称显示的是当前用户的。
解决这个问题,我们可以根据用户名匹配缓存内容,但是这将是我们宝贵空间一个巨大的浪费,因为缓存除了用户名其他大部分内容是相同的。我们还可以把网页切成几个片段并分别缓存,但这种情况会使页面和代码变得非常复杂。更好的方法是使用由 CController
提供的动态内容dynamic content
功能 。
动态内容是指片段输出即使是在片段缓存包括的内容中也不会被缓存。即使是包括的内容是从缓存中取出,为了使动态内容在所有时间是动态的,每次都得重新生成。出于这个原因,我们要求动态内容通过一些方法或函数生成。
调用CController::renderDynamic()在你想的地方插入动态内容。
...别的HTML内容...
beginCache($id)) { ?>
...被缓存的片段内容...
renderDynamic($callback); ?>
...被缓存的片段内容...
endCache(); } ?>
...别的HTML内容...
在上面的, $callback
指的是有效的PHP回调。它可以是指向当前控制器类的方法或者全局函数的字符串名。它也可以是一个数组名指向一个类的方法。其他任何的参数,将传递到renderDynamic()
方法中。回调将返回动态内容而不是仅仅显示它。