緩存是一個非常廉價而又高效的改進網頁效率的方法。通過在緩存中儲存近期的靜态資料,然後在接受請求的時候,我們就節省了生成資料的時間。
Yii中使用緩存,主要是設計配置檔案以及調用一個緩存應用元件。下面的配置指定了一個緩存元件,2個緩存服務的記憶體緩存。
- 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的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()儲存他:
- Yii::app()->cache->set($id, $value);
資料緩存會一直儲存這個值,直到這個變量由于緩存政策被删除了(例如,緩沖區滿了,最早的資料被删除了)。要改變這種屬性,我們也可以通過參數設定他的生命期,這些資料就會在生命期到了以後被删除。
- // keep the value in cache for at most 30 seconds
- Yii::app()->cache->set($id, $value, 30);
然後,當我們需要通路這個變量的時候(不管是跟之前一個頁面,還是另外的頁面),我們調用get()來從緩存擷取他的值。如果擷取到的值是false,那麼意味着該值已經失效了,需要重新生成。(我在想,如果boolean的值本來就是false呢?留待以後解決)
- $value=Yii::app()->cache->get($id);
- if($value===false)
- {
- // regenerate $value because it is not found in cache
- // and save it in cache for later use:
- // Yii::app()->cache->set($id,$value);
- }
當為變量選擇ID進行存儲時,必須保證該ID在所有可能緩存的變量中是唯一的。但是沒必要保證該ID在所有交叉的工程中都保持唯一,cache元件還是能夠智能的區分不同應用之間的ID。
有些緩存元件,例如MemCache,APC,支援批量的讀取,這可以減少調用的開支。mget()這個方法就是用來實作該功能的。在有些不支援這種方式的緩存中,他還是可以模拟實作。
要從緩存中删除資料,可以調用delete();如果是要清空緩存,可以調用flush()。用flush的時候要很小心,因為他會删除其他項目的資料。
TIPS:因為CCache實作了數組通路,是以當通路一個緩存的時候,可以跟通路數組一樣的調用:
- $cache=Yii::app()->cache;
- $cache['var1']=$value1; // equivalent to: $cache->set('var1',$value1);
- $value2=$cache['var2']; // equivalent to: $value2=$cache->get('var2');
1.2.1 緩存依賴
除了設定有效期,緩存資料也可能因為一些依賴的關系變了而失效。例如我們緩存一些檔案的内容,然後檔案内容改變了,我們就要在緩存廢棄之前的拷貝,然後重新讀取一份最新的内容。
我們用CCacheDependency或者其子類的執行個體來代表這種依賴關系。在我們調用set方法時,我們也把這種關系的對象傳遞進去。
- // the value will expire in 30 seconds
- // it may also be invalidated earlier if the dependent file is changed
- 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()這個方法。例如:
- $sql = 'SELECT * FROM tbl_post LIMIT 20';
- $dependency = new CDbCacheDependency('SELECT MAX(update_time) FROM tbl_post');
- $rows = Yii::app()->db->cache(1000, $dependency)->createCommand($sql)->queryAll();
執行以上語句的時候,Yii會首先檢查一下緩存是否包含一個該語句的查詢結果。檢查步驟是以下的三個條件:
●如果緩存包含了SQL語句中的入口索引
●如果入口還沒過期(少于儲存後的1000秒)
●如果依賴關系沒有變化(update_time的最大值是跟查詢結果儲存到緩存時一緻)
如果以上3個條件都滿足了,緩存的結果就會直接傳回給請求。否則,SQL語句就會被傳遞到資料庫系統去執行,得到的結果會儲存到緩存,傳回給請求。
用AR查詢緩存
我們也可以用AR來查詢緩存。我們用一個類似的方法,調用CActiveRecord::cache():
- $dependency = new CDbCacheDependency('SELECT MAX(update_time) FROM tbl post');
- $posts = Post::model()->cache(1000, $dependency)->findAll();
- // relational AR query
- $posts = Post::model()->cache(1000, $dependency)->with('author')->findAll();
上面的cache()方法,實際上是CDbConnection::cache()的快捷方式。在内部,當執行AR的查詢語句是,Yii會嘗試我們之前講述過的查詢緩存。
緩存的多種查詢
預設情況下,我們每次調用cache()(不管是CDbConnection 還是 CActiveRecord),都會标記下次要緩存的SQL,其他任何的SQL查詢都不會被緩存,除非我們再次調用cache(),例如:
- $sql = 'SELECT * FROM tbl post LIMIT 20';
- $dependency = new CDbCacheDependency('SELECT MAX(update_time) FROM tbl post');
- $rows = Yii::app()->db->cache(1000, $dependency)->createCommand($sql)->queryAll();
- // query caching will NOT be used
- $rows = Yii::app()->db->createCommand($sql)->queryAll();
通過傳遞另一個參數$queryCount到cache()的方法中,我們可以強制多次查詢緩存。下面的例子中,通過調用call(),我們指定這個換成必須用于接下來的2次
- // ...
- $rows = Yii::app()->db->cache(1000, $dependency, 2)->createCommand($sql)->queryAll();
- // query caching WILL be used
- $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.
- $posts = Post::model()->with('comments')->findAll(array(
- 'limit'=>20,
- ));
如果我們用下面的語句查詢緩存,隻有第一句會被緩存:
- $posts = Post::model()->cache(1000, $dependency)->with('comments')->findAll(array(
- 'limit'=>20,
- ));
如果要緩存兩個,我們要提供額外的參數來指明接下來我們要緩存幾句:
- $posts = Post::model()->cache(1000, $dependency, 2)->with('comments')->findAll(array(
- 'limit'=>20,
- ));
限制
如果查詢結果中包含資源句柄,查詢通路就不能用了。例如,我們在某些資料庫系統中使用BLOB作為字段類型,查詢結果會傳回一個資源句柄給這個字段。
有些緩存器會有大小限制。例如mencache限制每個入口大小為1M,是以,當一個查詢結果大于該大小時,會緩存失敗。
1.3 片段緩存
片段緩存涉及到緩存一個頁面的片段。例如,一個頁面顯示一個年銷售的表格,我們可以緩存這個表格,這樣就可以減少每次查詢的時間開銷。
要用片段緩存,我們可以在控制器的視圖腳本調用CController::beginCache() 和CController::endCache()。這兩個方法标志着要緩存内容的開始以及結束。跟資料緩存一樣,片段緩存也需要一個ID來識别緩存的片段:
- ...other HTML content...
- <?php if($this->beginCache($id)) f ?>
- ...content to be cached...
- <?php $this->endCache(); g ?>
- ...other HTML content...
在上面的代碼中,如果beginCache傳回false,緩存的内容将會自動插入到該地方,否則,if裡的内容就會被執行,然後再endCache被執行的時候,緩存到緩沖區。
1.3.1 緩存選項
當調用beginCache()的時候,我們會提供一個數組作為第二個參數,用來自定義片段緩存。實際上,beginCache()跟endCache()就是COutputCache工具的外皮。是以,緩存選項可以用任何COutputCache的屬性來指派。
有效性
或許最常用的選項就是duration,用來指定緩存内容的時間。這個跟CCache::set()的有效期選出類似。以下代碼示範緩存片段至少有效1小時:
- ...other HTML content...
- <?php if($this->beginCache($id, array('duration'=>3600))) f ?>
- ...content to be cached...
- <?php $this->endCache(); g ?>
- ...other HTML content...
如果我們不設定duration, 預設是60,意味着緩存的内容儲存60秒。
從Yii的1.1.8版本開始,如果duration設定為0,任何緩存的資料都會被移除。如果duration是負值,緩存就會被禁用,但是已有的緩存還是會被保留。
依賴關系
跟資料緩存一樣,片段緩存也一樣會有緩存依賴關系。例如,一個顯示的文章依賴于他的評論修改了沒。
為了表明依賴關系,我們設定了dependency選項,這選項可以是一個實施對象也可以是一個配置數組可以用來生成依賴關系的。下面的代碼展示了片段緩存依賴于lastModified字段值:
- ...other HTML content...
- <?php if($this->beginCache($id, array('dependency'=>array(
- 'class'=>'system.caching.dependencies.CDbCacheDependency',
- 'sql'=>'SELECT MAX(lastModified) FROM Post')))) f ?>
- ...content to be cached...
- <?php $this->endCache(); g ?>
- ...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選項:
- ...other HTML content...
- <?php if($this->beginCache($id, array('requestTypes'=>array('GET')))) f ?>
- ...content to be cached...
- <?php $this->endCache(); g ?>
- ...other HTML content...
1.3.2 嵌套緩存
片段緩存可以被嵌套的。也就是,一個片段緩存是包含在另一個大的片段緩存中。例如,Coment是緩存在一個裡面的片段緩存,外部是post的片段緩存:
- ...other HTML content...
- <?php if($this->beginCache($id1)) f ?>
- ...outer content to be cached...
- <?php if($this->beginCache($id2)) f ?>
- ...inner content to be cached...
- <?php $this->endCache(); g ?>
- ...outer content to be cached...
- <?php $this->endCache(); g ?>
- ...other HTML content...
不同的選項可以設定到嵌套緩存中。例如,上例中裡面的緩存跟外面的緩存參數可以設定不同。當外面的緩存資料失效時,裡面的緩存還将提供有效的資料。但是,反之卻不亦然了。如果外面的緩存有可用資料,那麼就都會提供緩存的資料,不管他内部的緩存資料是否已經過期了。
1.4 頁面緩存
頁面緩存也就是緩存整個頁面内容。在不同的地方都可能會有頁面緩存,例如,選擇一個适合的網頁頭,用戶端的浏覽器可能會在一定時間内緩存該頁。Web工程也可以自己緩存這個頁面内容。在本節中,我們主要研究後面的這種方法。
頁面緩存可以認為是片段緩存的特例。因為網頁的内容經常是用layout輸出到視圖的,如果我們僅僅隻是在layout設計中調用beginCache(),endCache(),他們是不起作用的。因為layout是在調用CController::render()後才生效的,而視圖在之前就已經應用了。
要緩存頁面,我們必須跳過網頁生成的動作。我們可以用COutputCache作為作為過濾器。下面的代碼示範我們如何配置緩存過濾器:
- public function filters()
- {
- return array(
- array(
- 'COutputCache',
- 'duration'=>100,
- 'varyByParam'=>array('id'),
- ),
- );
- }
上面的過濾器配置,會應用于控制器的所有動作。如果要限制隻是作用于一個或者幾個動作,我們可以使用+号。更多關于過濾器的詳細資訊可以參考過濾器。
TIP:我們可以用COutputCache作為過濾器,是因為他是繼承于CFilterWidget。也就意味着,他既是過濾器,也是一個小工具。實際上,一個小工具的工作流程跟過濾器非常的相似:一個小工具(過濾器)在所有内容生效之前工作,然後再所有内容完成之後結束。
1.5 動态内容
當使用片段查詢或者是頁面查詢時,我們經常遭遇的情況是,輸出内容中,絕大多都是靜态的,隻有某些地方是動态的。例如,幫助頁面都是靜态的,除了頂部的使用者登入資訊。
為了解決這個問題,我們可以把緩存的内容設定為跟使用者名變化而變化,但是這樣對于我們的緩存來說是巨大的浪費,因為除了使用者名不同,其他内容都一樣。我們也可以把頁面分成幾個部分,每個部分做片段緩存,但是這樣會搞得我們的視圖以及代碼很複雜。一個更好的辦法是使用CController提供的動态内容。
- ...other HTML content...
- <?php if($this->beginCache($id)) f ?>
- ...fragment content to be cached...
- <?php $this->renderDynamic($callback); ?>
- ...fragment content to be cached...
- <?php $this->endCache(); g ?>
- ...other HTML content...