緩存是一個非常廉價而又高效的改進網頁效率的方法。通過在緩存中保存近期的靜態數據,然後在接受請求的時候,我們就節省了生成數據的時間。
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提供的動態內容。
一個動態內容意味着一個輸出片段不能用於緩存,即使是在一個片段緩存內。要是動態數據一直都動態,每次都需要重新生成數據,就算緩存已經提供了數據。出於這個原因,我們要求動態數據由方法或者是函數生成。
我們調用CController::renderDynamic()在需要的飛插入動態數據:
- ...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...
上面的例子中,$callback是指向一個有效的PHP回調。可以是一個當前控制器的方法名,也可以是一個全局的函數名。他還可以是一個包含類方法的數組。任何renderDynamic()附件的參數都會被傳遞給callback。callback會返回動態內容,而不是顯示他。