缓存

缓存是一个非常廉价而又高效的改进网页效率的方法。通过在缓存中保存近期的静态数据,然后在接受请求的时候,我们就节省了生成数据的时间。

Yii中使用缓存,主要是设计配置文件以及调用一个缓存应用组件。下面的配置指定了一个缓存组件,2个缓存服务的内存缓存。

  1. array(  
  2.     ......  
  3.     'components'=>array(  
  4.         ......  
  5.         'cache'=>array(  
  6.             'class'=>'system.caching.CMemCache',  
  7.             'servers'=>array(  
  8.                 array('host'=>'server1''port'=>11211, 'weight'=>60),  
  9.                 array('host'=>'server2''port'=>11211, 'weight'=>40),  
  10.             ),  
  11.         ),  
  12.     ),  
  13. ); 

当程序运行起来以后,可以通过Yii::app()->cache 来访问缓存组件。

Yii提供了多种缓存组件来支持不同媒介数据。例如,CMemCache组件组件封装了PHP的memchche扩展,使用内存作为存储的缓冲区;CApcCache组件封装了PHP APC的扩展;CDbCache组件作为数据库的缓冲组件。以下是一些可用缓存组件:

●CMemCache: uses PHP memcache extension.
●CApcCache: uses PHP APC extension.
●CXCache: uses PHP XCache extension.
●CEAcceleratorCache: uses PHP EAccelerator extension.
●CDbCache:用数据库的表来作缓存数据。默认情况下,会在当前运行目录下使用SQLite3的数据库。也可以通过设置他的connectionID属性来显示的指定他的数据库。
●CZendDataCache: uses Zend Data Cache as the underlying caching medium.
●CFileCache:用文件来缓存数据。在缓存大数据库的时候,这个方法特别适用。
●CDummyCache:虚假缓存,实际上并不缓存任何数据。这个组件的目的是简化那些检测缓存可用性的代码。例如,在开发过程中,服务器并不支持缓存,我们就可以用这个组件。当实际的缓存服务器可用的时候,我们可以切换到可用的当前可用的缓存服务器。

TIP:因为所有的组件都是继承于CCache,所以在使用的时候,可以不需要更改代码,就在各种组件之间做切换。

缓存可以用于不同的层次。最低级别的,我们用缓存来存储一个单独的数据,例如变量,我们称之为数据缓存。上一级别的,我们存储视图脚本生成的页面碎片。最高层次的,缓存整个网页。

在后续的章节中,我们将阐述如果在不同级别使用缓存。

Note:缓存是作为中间的存储器,他无法保证缓存的数据是否已经过期。所以,不要用缓存来作为长效存储(也不要用缓存来存储session数据)

1.2 数据缓存

数据缓存是把一些PHP的变量存储在缓存中,当读取该变量的时候直接从缓存中读取。出于这个目的,基于CCache的缓存组件提供了两个最常用的方法get()和set()。

例如要存储一个变量$value,我们用一个唯一的ID,调用set()保存他:

  1. Yii::app()->cache->set($id$value); 

数据缓存会一直保存这个值,直到这个变量由于缓存策略被删除了(例如,缓冲区满了,最早的数据被删除了)。要改变这种属性,我们也可以通过参数设置他的生命期,这些数据就会在生命期到了以后被删除。

  1. // keep the value in cache for at most 30 seconds  
  2. Yii::app()->cache->set($id$value, 30); 

然后,当我们需要访问这个变量的时候(不管是跟之前一个页面,还是另外的页面),我们调用get()来从缓存获取他的值。如果获取到的值是false,那么意味着该值已经失效了,需要重新生成。(我在想,如果boolean的值本来就是false呢?留待以后解决)

  1. $value=Yii::app()->cache->get($id);  
  2. if($value===false)  
  3. {  
  4.     // regenerate $value because it is not found in cache  
  5.     // and save it in cache for later use:  
  6.     // Yii::app()->cache->set($id,$value);  

当为变量选择ID进行存储时,必须保证该ID在所有可能缓存的变量中是唯一的。但是没必要保证该ID在所有交叉的工程中都保持唯一,cache组件还是能够智能的区分不同应用之间的ID。

有些缓存组件,例如MemCache,APC,支持批量的读取,这可以减少调用的开支。mget()这个方法就是用来实现该功能的。在有些不支持这种方式的缓存中,他还是可以模拟实现。

要从缓存中删除数据,可以调用delete();如果是要清空缓存,可以调用flush()。用flush的时候要很小心,因为他会删除其他项目的数据。

TIPS:因为CCache实现了数组访问,所以当访问一个缓存的时候,可以跟访问数组一样的调用:

  1. $cache=Yii::app()->cache;  
  2. $cache['var1']=$value1// equivalent to: $cache->set('var1',$value1);  
  3. $value2=$cache['var2']; // equivalent to: $value2=$cache->get('var2'); 

1.2.1 缓存依赖

除了设置有效期,缓存数据也可能因为一些依赖的关系变了而失效。例如我们缓存一些文件的内容,然后文件内容改变了,我们就要在缓存作废之前的拷贝,然后重新读取一份最新的内容。

我们用CCacheDependency或者其子类的实例来代表这种依赖关系。在我们调用set方法时,我们也把这种关系的对象传递进去。

  1. // the value will expire in 30 seconds  
  2. // it may also be invalidated earlier if the dependent file is changed  
  3. Yii::app()->cache->set($id$value, 30, new CFileCacheDependency('FileName')); 

现在当我们调用get去获取缓存值的时候,缓存依赖会去验证他所依赖的内容是否已经改变。如果我们获取的值是false,则代表这些缓存数据需要重新生成了。

以下是可用的依赖关系列表:

●CFileCacheDependency: 如果文件的最新修改时间已经改变,那么依赖关系也改变了。
●CDirectoryCacheDependency:如果该目录下的文件,或者是子目录有变化了,该依赖关系也改变了。
●CDbCacheDependency:如果指定的SQL语句查询结果变了,依赖关系也变了。
●CGlobalStateCacheDependency:如果指定的全局变量改变了,那么依赖关系也改变了。全局变量是在一个工程中,一直存在于多种请求和会话的变量。用CApplication::setGlobalState()来定义。
●CChainedCacheDependency:如果依赖链中的任何一个依赖关系发生了变化,那么这个也变了。
●CExpressionDependency:如果指定的PHP异常变化了,那么这个依赖关系也改变了。

1.2.2 查询缓存

从1.1.7版本开始,Yii开始支持了缓存查询。建于数据缓存的最高层,查询缓存存储着数据库缓存查询结果,并可能会保存数据库查询的时间,如果这个查询在将来会被用到的话。这样缓存就可以直接用来返回查询结果了。
 

INFO:某些数据库系统(如MySql)也支持在数据库端查询缓存。相较于服务端提供的缓存查询,我们所提供的比较灵活,而且,可能更加高效哦。

启用查询缓存

要使用查询缓存,首先要确保CDbConnection::queryCacheID指向一个可用的缓存组件ID(默认是cache)。

用DAO查询缓存

要使用查询缓存,我们在数据库查询的时候调用CDbConnection::cache()这个方法。例如:

  1. $sql = 'SELECT * FROM tbl_post LIMIT 20';  
  2. $dependency = new CDbCacheDependency('SELECT MAX(update_time) FROM tbl_post');  
  3. $rows = Yii::app()->db->cache(1000, $dependency)->createCommand($sql)->queryAll(); 

执行以上语句的时候,Yii会首先检查一下缓存是否包含一个该语句的查询结果。检查步骤是以下的三个条件:

●如果缓存包含了SQL语句中的入口索引
●如果入口还没过期(少于保存后的1000秒)
●如果依赖关系没有变化(update_time的最大值是跟查询结果保存到缓存时一致)

如果以上3个条件都满足了,缓存的结果就会直接返回给请求。否则,SQL语句就会被传递到数据库系统去执行,得到的结果会保存到缓存,返回给请求。

用AR查询缓存

我们也可以用AR来查询缓存。我们用一个类似的方法,调用CActiveRecord::cache():

  1. $dependency = new CDbCacheDependency('SELECT MAX(update_time) FROM tbl post');  
  2. $posts = Post::model()->cache(1000, $dependency)->findAll();  
  3. // relational AR query  
  4. $posts = Post::model()->cache(1000, $dependency)->with('author')->findAll(); 

上面的cache()方法,实际上是CDbConnection::cache()的快捷方式。在内部,当执行AR的查询语句是,Yii会尝试我们之前讲述过的查询缓存。

缓存的多种查询

默认情况下,我们每次调用cache()(不管是CDbConnection 还是 CActiveRecord),都会标记下次要缓存的SQL,其他任何的SQL查询都不会被缓存,除非我们再次调用cache(),例如:

  1. $sql = 'SELECT * FROM tbl post LIMIT 20';  
  2. $dependency = new CDbCacheDependency('SELECT MAX(update_time) FROM tbl post');  
  3. $rows = Yii::app()->db->cache(1000, $dependency)->createCommand($sql)->queryAll();  
  4. // query caching will NOT be used  
  5. $rows = Yii::app()->db->createCommand($sql)->queryAll(); 

通过传递另一个参数$queryCount到cache()的方法中,我们可以强制多次查询缓存。下面的例子中,通过调用call(),我们指定这个换成必须用于接下来的2次

  1. // ...  
  2. $rows = Yii::app()->db->cache(1000, $dependency, 2)->createCommand($sql)->queryAll();  
  3. // query caching WILL be used  
  4. $rows = Yii::app()->db->createCommand($sql)->queryAll(); 

如你所知,当我们执行关联AR查询时,可能是执行多句的SQL。例如,Post与Coment之间的关系是HAS_MANY,以下的SQL语句是数据库中真正执行的。

it first selects the posts limited by 20;
it then selects the comments for the previously selected posts.

  1. $posts = Post::model()->with('comments')->findAll(array(  
  2. 'limit'=>20,  
  3. )); 

如果我们用下面的语句查询缓存,只有第一句会被缓存:

  1. $posts = Post::model()->cache(1000, $dependency)->with('comments')->findAll(array(  
  2.     'limit'=>20,  
  3. )); 

如果要缓存两个,我们要提供额外的参数来指明接下来我们要缓存几句:

  1. $posts = Post::model()->cache(1000, $dependency, 2)->with('comments')->findAll(array(  
  2.     'limit'=>20,  
  3. )); 

限制

如果查询结果中包含资源句柄,查询访问就不能用了。例如,我们在某些数据库系统中使用BLOB作为字段类型,查询结果会返回一个资源句柄给这个字段。

有些缓存器会有大小限制。例如mencache限制每个入口大小为1M,所以,当一个查询结果大于该大小时,会缓存失败。

1.3 片段缓存

片段缓存涉及到缓存一个页面的片段。例如,一个页面显示一个年销售的表格,我们可以缓存这个表格,这样就可以减少每次查询的时间开销。

要用片段缓存,我们可以在控制器的视图脚本调用CController::beginCache() 和CController::endCache()。这两个方法标志着要缓存内容的开始以及结束。跟数据缓存一样,片段缓存也需要一个ID来识别缓存的片段:

  1. ...other HTML content...  
  2. <?php if($this->beginCache($id)) f ?>  
  3. ...content to be cached...  
  4. <?php $this->endCache(); g ?>  
  5. ...other HTML content... 

在上面的代码中,如果beginCache返回false,缓存的内容将会自动插入到该地方,否则,if里的内容就会被执行,然后再endCache被执行的时候,缓存到缓冲区。

1.3.1 缓存选项

当调用beginCache()的时候,我们会提供一个数组作为第二个参数,用来自定义片段缓存。实际上,beginCache()跟endCache()就是COutputCache工具的外皮。所以,缓存选项可以用任何COutputCache的属性来赋值。

有效性

或许最常用的选项就是duration,用来指定缓存内容的时间。这个跟CCache::set()的有效期选出类似。以下代码演示缓存片段至少有效1小时:

  1. ...other HTML content...  
  2. <?php if($this->beginCache($idarray('duration'=>3600))) f ?>  
  3. ...content to be cached...  
  4. <?php $this->endCache(); g ?>  
  5. ...other HTML content... 

如果我们不设置duration, 默认是60,意味着缓存的内容保存60秒。

从Yii的1.1.8版本开始,如果duration设置为0,任何缓存的数据都会被移除。如果duration是负值,缓存就会被禁用,但是已有的缓存还是会被保留。

依赖关系

跟数据缓存一样,片段缓存也一样会有缓存依赖关系。例如,一个显示的文章依赖于他的评论修改了没。

为了表明依赖关系,我们设定了dependency选项,这选项可以是一个实施对象也可以是一个配置数组可以用来生成依赖关系的。下面的代码展示了片段缓存依赖于lastModified字段值:

  1. ...other HTML content...  
  2. <?php if($this->beginCache($idarray('dependency'=>array(  
  3.     'class'=>'system.caching.dependencies.CDbCacheDependency',  
  4.     'sql'=>'SELECT MAX(lastModified) FROM Post')))) f ?>  
  5. ...content to be cached...  
  6. <?php $this->endCache(); g ?>  
  7. ...other HTML content... 

变体

缓存的内容也可以通过某些参数变体。例如,用户配置会因为不同用户而不同。要缓存配置的内容,我们希望缓存的内容根据不同用户而变。这意味着我们在调用beginCache()的时候,需要指明用户ID。

我们不需要在开发的时候根据不同的ID来做框架,COutputCache内置了一个这玩意:

●varyByRoute:设置这个参数为true,缓存的内容就会根据路由route的不同而缓存。这样,每个关联的控制器以及动作就会有独立的缓存内容。
●varyBySession:设置这个参数为true,缓存的内容就会根据每个sessionID来缓存。这样,每个用户看到的缓存内容就不一样了。
●varyByParam:设置这个参数为一个名称数组,这样我们可以让缓存内容根据每个GET参数值不同而不同。例如,一个网页通过GET参数获得的id来显示内容,这样我们设置varyByParam为array('id'),这样就可以为每篇文章缓存了。如果不是这样,我们就只能缓存一篇文章了。
●varyByExpression:设定这个参数为一个PHP语句,我们可以根据PHP语句执行的不同结果来缓存。

结果类型

有时候我们只想某些类型的请求来使用片段缓存。例如一个网页显示一个表单,我们只在这个窗口是通过GET方法初始化的时候缓存。任何后面的请求(via POST)的表单都不被缓存,因为这个表单是用户提交的,可能包含用户数据。所以,我们可以通过指定requestTypes选项:

  1. ...other HTML content...  
  2. <?php if($this->beginCache($idarray('requestTypes'=>array('GET')))) f ?>  
  3. ...content to be cached...  
  4. <?php $this->endCache(); g ?>  
  5. ...other HTML content... 

1.3.2 嵌套缓存

片段缓存可以被嵌套的。也就是,一个片段缓存是包含在另一个大的片段缓存中。例如,Coment是缓存在一个里面的片段缓存,外部是post的片段缓存:

  1. ...other HTML content...  
  2. <?php if($this->beginCache($id1)) f ?>  
  3. ...outer content to be cached...  
  4. <?php if($this->beginCache($id2)) f ?>  
  5. ...inner content to be cached...  
  6. <?php $this->endCache(); g ?>  
  7. ...outer content to be cached...  
  8. <?php $this->endCache(); g ?>  
  9. ...other HTML content... 

不同的选项可以设置到嵌套缓存中。例如,上例中里面的缓存跟外面的缓存参数可以设置不同。当外面的缓存数据失效时,里面的缓存还将提供有效的数据。但是,反之却不亦然了。如果外面的缓存有可用数据,那么就都会提供缓存的数据,不管他内部的缓存数据是否已经过期了。

1.4 页面缓存

页面缓存也就是缓存整个页面内容。在不同的地方都可能会有页面缓存,例如,选择一个适合的网页头,客户端的浏览器可能会在一定时间内缓存该页。Web工程也可以自己缓存这个页面内容。在本节中,我们主要研究后面的这种方法。

页面缓存可以认为是片段缓存的特例。因为网页的内容经常是用layout输出到视图的,如果我们仅仅只是在layout设计中调用beginCache(),endCache(),他们是不起作用的。因为layout是在调用CController::render()后才生效的,而视图在之前就已经应用了。

要缓存页面,我们必须跳过网页生成的动作。我们可以用COutputCache作为作为过滤器。下面的代码演示我们如何配置缓存过滤器:

  1. public function filters()  
  2. {  
  3.     return array(  
  4.         array(  
  5.             'COutputCache',  
  6.             'duration'=>100,  
  7.             'varyByParam'=>array('id'),  
  8.         ),  
  9.     );  

上面的过滤器配置,会应用于控制器的所有动作。如果要限制只是作用于一个或者几个动作,我们可以使用+号。更多关于过滤器的详细信息可以参考过滤器。

TIP:我们可以用COutputCache作为过滤器,是因为他是继承于CFilterWidget。也就意味着,他既是过滤器,也是一个小工具。实际上,一个小工具的工作流程跟过滤器非常的相似:一个小工具(过滤器)在所有内容生效之前工作,然后再所有内容完成之后结束。

1.5 动态内容

当使用片段查询或者是页面查询时,我们经常遭遇的情况是,输出内容中,绝大多都是静态的,只有某些地方是动态的。例如,帮助页面都是静态的,除了顶部的用户登录信息。

为了解决这个问题,我们可以把缓存的内容设定为跟用户名变化而变化,但是这样对于我们的缓存来说是巨大的浪费,因为除了用户名不同,其他内容都一样。我们也可以把页面分成几个部分,每个部分做片段缓存,但是这样会搞得我们的视图以及代码很复杂。一个更好的办法是使用CController提供的动态内容。

一个动态内容意味着一个输出片段不能用于缓存,即使是在一个片段缓存内。要是动态数据一直都动态,每次都需要重新生成数据,就算缓存已经提供了数据。出于这个原因,我们要求动态数据由方法或者是函数生成。

我们调用CController::renderDynamic()在需要的飞插入动态数据:

  1. ...other HTML content...  
  2. <?php if($this->beginCache($id)) f ?>  
  3. ...fragment content to be cached...  
  4. <?php $this->renderDynamic($callback); ?>  
  5. ...fragment content to be cached...  
  6. <?php $this->endCache(); g ?>  
  7. ...other HTML content... 

上面的例子中,$callback是指向一个有效的PHP回调。可以是一个当前控制器的方法名,也可以是一个全局的函数名。他还可以是一个包含类方法的数组。任何renderDynamic()附件的参数都会被传递给callback。callback会返回动态内容,而不是显示他。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章