深入解析PHP的Yii框架中的緩存功能
數(shù)據(jù)緩存是指將一些 PHP 變量存儲到緩存中,使用時再從緩存中取回。它也是更高級緩存特性的基礎(chǔ),例如查詢緩存和內(nèi)容緩存。
如下代碼是一個典型的數(shù)據(jù)緩存使用模式。其中 $cache 指向緩存組件:
// 嘗試從緩存中取回 $data $data = $cache->get($key); if ($data === false) { // $data 在緩存中沒有找到,則重新計(jì)算它的值 // 將 $data 存放到緩存供下次使用 $cache->set($key, $data); } // 這兒 $data 可以使用了。
緩存組件
數(shù)據(jù)緩存需要緩存組件提供支持,它代表各種緩存存儲器,例如內(nèi)存,文件,數(shù)據(jù)庫。
緩存組件通常注冊為應(yīng)用程序組件,這樣它們就可以在全局進(jìn)行配置與訪問。如下代碼演示了如何配置應(yīng)用程序組件 cache 使用兩個 memcached 服務(wù)器:
'components' => [ 'cache' => [ 'class' => 'yii\caching\MemCache', 'servers' => [ [ 'host' => 'server1', 'port' => 11211, 'weight' => 100, ], [ 'host' => 'server2', 'port' => 11211, 'weight' => 50, ], ], ], ],
然后就可以通過 Yii::$app->cache 訪問上面的緩存組件了。
由于所有緩存組件都支持同樣的一系列 API ,并不需要修改使用緩存的業(yè)務(wù)代碼就能直接替換為其他底層緩存組件,只需在應(yīng)用配置中重新配置一下就可以。例如,你可以將上述配置修改為使用 yii\caching\ApcCache:
'components' => [ 'cache' => [ 'class' => 'yii\caching\ApcCache', ], ],
Tip: 你可以注冊多個緩存組件,很多依賴緩存的類默認(rèn)調(diào)用名為 cache 的組件(例如 yii\web\UrlManager)。
支持的緩存存儲器
Yii 支持一系列緩存存儲器,概況如下:
- yii\caching\ApcCache:使用 PHP APC 擴(kuò)展。這個選項(xiàng)可以認(rèn)為是集中式應(yīng)用程序環(huán)境中(例如:單一服務(wù)器,沒有獨(dú)立的負(fù)載均衡器等)最快的緩存方案。
- yii\caching\DbCache:使用一個數(shù)據(jù)庫的表存儲緩存數(shù)據(jù)。要使用這個緩存,你必須創(chuàng)建一個與 yii\caching\DbCache::cacheTable 對應(yīng)的表。
- yii\caching\DummyCache: 僅作為一個緩存占位符,不實(shí)現(xiàn)任何真正的緩存功能。這個組件的目的是為了簡化那些需要查詢緩存有效性的代碼。例如,在開發(fā)中如果服務(wù)器沒有實(shí)際的緩存支持,用它配置一個緩存組件。一個真正的緩存服務(wù)啟用后,可以再切換為使用相應(yīng)的緩存組件。兩種條件下你都可以使用同樣的代碼 Yii::$app->cache->get($key) 嘗試從緩存中取回?cái)?shù)據(jù)而不用擔(dān)心 Yii::$app->cache 可能是 null。
- yii\caching\FileCache:使用標(biāo)準(zhǔn)文件存儲緩存數(shù)據(jù)。這個特別適用于緩存大塊數(shù)據(jù),例如一個整頁的內(nèi)容。
- yii\caching\MemCache:使用 PHP memcache 和 memcached 擴(kuò)展。這個選項(xiàng)被看作分布式應(yīng)用環(huán)境中(例如:多臺服務(wù)器,有負(fù)載均衡等)最快的緩存方案。
- yii\redis\Cache:實(shí)現(xiàn)了一個基于 Redis 鍵值對存儲器的緩存組件(需要 redis 2.6.12 及以上版本的支持 )。
- yii\caching\WinCache:使用 PHP WinCache(另可參考)擴(kuò)展.
- yii\caching\XCache:使用 PHP XCache擴(kuò)展。
- yii\caching\ZendDataCache:使用 Zend Data Cache 作為底層緩存媒介。
- Tip: 你可以在同一個應(yīng)用程序中使用不同的緩存存儲器。一個常見的策略是使用基于內(nèi)存的緩存存儲器存儲小而常用的數(shù)據(jù)(例如:統(tǒng)計(jì)數(shù)據(jù)),使用基于文件或數(shù)據(jù)庫的緩存存儲器存儲大而不太常用的數(shù)據(jù)(例如:網(wǎng)頁內(nèi)容)。
緩存 API
所有緩存組件都有同樣的基類 yii\caching\Cache ,因此都支持如下 API:
- yii\caching\Cache::get():通過一個指定的鍵(key)從緩存中取回一項(xiàng)數(shù)據(jù)。如果該項(xiàng)數(shù)據(jù)不存在于緩存中或者已經(jīng)過期/失效,則返回值 false。
- yii\caching\Cache::set():將一項(xiàng)數(shù)據(jù)指定一個鍵,存放到緩存中。
- yii\caching\Cache::add():如果緩存中未找到該鍵,則將指定數(shù)據(jù)存放到緩存中。
- yii\caching\Cache::mget():通過指定的多個鍵從緩存中取回多項(xiàng)數(shù)據(jù)。
- yii\caching\Cache::mset():將多項(xiàng)數(shù)據(jù)存儲到緩存中,每項(xiàng)數(shù)據(jù)對應(yīng)一個鍵。
- yii\caching\Cache::madd():將多項(xiàng)數(shù)據(jù)存儲到緩存中,每項(xiàng)數(shù)據(jù)對應(yīng)一個鍵。如果某個鍵已經(jīng)存在于緩存中,則該項(xiàng)數(shù)據(jù)會被跳過。
- yii\caching\Cache::exists():返回一個值,指明某個鍵是否存在于緩存中。
- yii\caching\Cache::delete():通過一個鍵,刪除緩存中對應(yīng)的值。
- yii\caching\Cache::flush():刪除緩存中的所有數(shù)據(jù)。
- 有些緩存存儲器如 MemCache,APC 支持以批量模式取回緩存值,這樣可以節(jié)省取回緩存數(shù)據(jù)的開支。 yii\caching\Cache::mget() 和 yii\caching\Cache::madd() API提供對該特性的支持。如果底層緩存存儲器不支持該特性,Yii 也會模擬實(shí)現(xiàn)。
由于 yii\caching\Cache 實(shí)現(xiàn)了 PHP ArrayAccess 接口,緩存組件也可以像數(shù)組那樣使用,下面是幾個例子:
$cache['var1'] = $value1; // 等價(jià)于: $cache->set('var1', $value1); $value2 = $cache['var2']; // 等價(jià)于: $value2 = $cache->get('var2');
緩存鍵
存儲在緩存中的每項(xiàng)數(shù)據(jù)都通過鍵作唯一識別。當(dāng)你在緩存中存儲一項(xiàng)數(shù)據(jù)時,必須為它指定一個鍵,稍后從緩存中取回?cái)?shù)據(jù)時,也需要提供相應(yīng)的鍵。
你可以使用一個字符串或者任意值作為一個緩存鍵。當(dāng)鍵不是一個字符串時,它將會自動被序列化為一個字符串。
定義一個緩存鍵常見的一個策略就是在一個數(shù)組中包含所有的決定性因素。例如,yii\db\Schema 使用如下鍵存儲一個數(shù)據(jù)表的結(jié)構(gòu)信息。
[ __CLASS__, // 結(jié)構(gòu)類名 $this->db->dsn, // 數(shù)據(jù)源名稱 $this->db->username, // 數(shù)據(jù)庫登錄用戶名 $name, // 表名 ];
如你所見,該鍵包含了可唯一指定一個數(shù)據(jù)庫表所需的所有必要信息。
當(dāng)同一個緩存存儲器被用于多個不同的應(yīng)用時,應(yīng)該為每個應(yīng)用指定一個唯一的緩存鍵前綴以避免緩存鍵沖突??梢酝ㄟ^配置 yii\caching\Cache::keyPrefix 屬性實(shí)現(xiàn)。例如,在應(yīng)用配置中可以編寫如下代碼:
'components' => [ 'cache' => [ 'class' => 'yii\caching\ApcCache', 'keyPrefix' => 'myapp', // 唯一鍵前綴 ], ],
為了確?;ネㄐ?,此處只能使用字母和數(shù)字。
緩存過期
默認(rèn)情況下,緩存中的數(shù)據(jù)會永久存留,除非它被某些緩存策略強(qiáng)制移除(例如:緩存空間已滿,最老的數(shù)據(jù)會被移除)。要改變此特性,你可以在調(diào)用 yii\caching\Cache::set() 存儲一項(xiàng)數(shù)據(jù)時提供一個過期時間參數(shù)。該參數(shù)代表這項(xiàng)數(shù)據(jù)在緩存中可保持有效多少秒。當(dāng)你調(diào)用 yii\caching\Cache::get() 取回?cái)?shù)據(jù)時,如果它已經(jīng)過了超時時間,該方法將返回 false,表明在緩存中找不到這項(xiàng)數(shù)據(jù)。例如:
// 將數(shù)據(jù)在緩存中保留 45 秒 $cache->set($key, $data, 45); sleep(50); $data = $cache->get($key); if ($data === false) { // $data 已過期,或者在緩存中找不到 }
緩存依賴
除了超時設(shè)置,緩存數(shù)據(jù)還可能受到緩存依賴的影響而失效。例如,yii\caching\FileDependency 代表對一個文件修改時間的依賴。這個依賴條件發(fā)生變化也就意味著相應(yīng)的文件已經(jīng)被修改。因此,緩存中任何過期的文件內(nèi)容都應(yīng)該被置為失效狀態(tài),對 yii\caching\Cache::get() 的調(diào)用都應(yīng)該返回 false。
緩存依賴用 yii\caching\Dependency 的派生類所表示。當(dāng)調(diào)用 yii\caching\Cache::set() 在緩存中存儲一項(xiàng)數(shù)據(jù)時,可以同時傳遞一個關(guān)聯(lián)的緩存依賴對象。例如:
// 創(chuàng)建一個對 example.txt 文件修改時間的緩存依賴 $dependency = new \yii\caching\FileDependency(['fileName' => 'example.txt']); // 緩存數(shù)據(jù)將在30秒后超時 // 如果 example.txt 被修改,它也可能被更早地置為失效狀態(tài)。 $cache->set($key, $data, 30, $dependency); // 緩存會檢查數(shù)據(jù)是否已超時。 // 它還會檢查關(guān)聯(lián)的依賴是否已變化。 // 符合任何一個條件時都會返回 false。 $data = $cache->get($key);
下面是可用的緩存依賴的概況:
- yii\caching\ChainedDependency:如果依賴鏈上任何一個依賴產(chǎn)生變化,則依賴改變。
- yii\caching\DbDependency:如果指定 SQL 語句的查詢結(jié)果發(fā)生了變化,則依賴改變。
- yii\caching\ExpressionDependency:如果指定的 PHP 表達(dá)式執(zhí)行結(jié)果發(fā)生變化,則依賴改變。
- yii\caching\FileDependency:如果文件的最后修改時間發(fā)生變化,則依賴改變。
- yii\caching\GroupDependency:將一項(xiàng)緩存數(shù)據(jù)標(biāo)記到一個組名,你可以通過調(diào)用 yii\caching\GroupDependency::invalidate() 一次性將相同組名的緩存全部置為失效狀態(tài)。
查詢緩存
查詢緩存是一個建立在數(shù)據(jù)緩存之上的特殊緩存特性。它用于緩存數(shù)據(jù)庫查詢的結(jié)果。
查詢緩存需要一個 yii\db\Connection 和一個有效的 cache 應(yīng)用組件。查詢緩存的基本用法如下,假設(shè) $db 是一個 yii\db\Connection 實(shí)例:
$duration = 60; // 緩存查詢結(jié)果60秒 $dependency = ...; // 可選的緩存依賴 $db->beginCache($duration, $dependency); // ...這兒執(zhí)行數(shù)據(jù)庫查詢... $db->endCache();
如你所見,beginCache() 和 endCache() 中間的任何查詢結(jié)果都會被緩存起來。如果緩存中找到了同樣查詢的結(jié)果,則查詢會被跳過,直接從緩存中提取結(jié)果。
查詢緩存可以用于 ActiveRecord 和 DAO。
Info: 有些 DBMS (例如:MySQL)也支持?jǐn)?shù)據(jù)庫服務(wù)器端的查詢緩存。你可以選擇使用任一查詢緩存機(jī)制。上文所述的查詢緩存的好處在于你可以指定更靈活的緩存依賴因此可能更加高效。
配置
查詢緩存有兩個通過 yii\db\Connection 設(shè)置的配置項(xiàng):
yii\db\Connection::queryCacheDuration: 查詢結(jié)果在緩存中的有效期,以秒表示。如果在調(diào)用 yii\db\Connection::beginCache() 時傳遞了一個顯式的時值參數(shù),則配置中的有效期時值會被覆蓋。
yii\db\Connection::queryCache: 緩存應(yīng)用組件的 ID。默認(rèn)為 'cache'。只有在設(shè)置了一個有效的緩存應(yīng)用組件時,查詢緩存才會有效。
限制條件
當(dāng)查詢結(jié)果中含有資源句柄時,查詢緩存無法使用。例如,在有些 DBMS 中使用了 BLOB 列的時候,緩存結(jié)果會為該數(shù)據(jù)列返回一個資源句柄。
有些緩存存儲器有大小限制。例如,memcache 限制每條數(shù)據(jù)最大為 1MB。因此,如果查詢結(jié)果的大小超出了該限制,則會導(dǎo)致緩存失敗。
片段緩存
片段緩存指的是緩存頁面內(nèi)容中的某個片段。例如,一個頁面顯示了逐年銷售額的摘要表格,可以把表格緩存下來,以消除每次請求都要重新生成表格的耗時。片段緩存是基于數(shù)據(jù)緩存實(shí)現(xiàn)的。
在視圖中使用以下結(jié)構(gòu)啟用片段緩存:
if ($this->beginCache($id)) { // ... 在此生成內(nèi)容 ... $this->endCache(); }
調(diào)用 yii\base\View::beginCache() 和 yii\base\View::endCache() 方法包裹內(nèi)容生成邏輯。如果緩存中存在該內(nèi)容,yii\base\View::beginCache() 方法將渲染內(nèi)容并返回 false,因此將跳過內(nèi)容生成邏輯。否則,內(nèi)容生成邏輯被執(zhí)行,一直執(zhí)行到 yii\base\View::endCache() 時,生成的內(nèi)容將被捕獲并存儲在緩存中。
和[數(shù)據(jù)緩存]一樣,每個片段緩存也需要全局唯一的 $id 標(biāo)記。
緩存選項(xiàng)
如果要為片段緩存指定額外配置項(xiàng),請通過向 yii\base\View::beginCache() 方法第二個參數(shù)傳遞配置數(shù)組。在框架內(nèi)部,該數(shù)組將被用來配置一個 yii\widget\FragmentCache 小部件用以實(shí)現(xiàn)片段緩存功能。
過期時間(duration)
或許片段緩存中最常用的一個配置選項(xiàng)就是 yii\widgets\FragmentCache::duration 了。它指定了內(nèi)容被緩存的秒數(shù)。以下代碼緩存內(nèi)容最多一小時:
if ($this->beginCache($id, ['duration' => 3600])) { // ... 在此生成內(nèi)容 ... $this->endCache(); }
如果該選項(xiàng)未設(shè)置,則默認(rèn)為 0,永不過期。
依賴
和[數(shù)據(jù)緩存]一樣,片段緩存的內(nèi)容一樣可以設(shè)置緩存依賴。例如一段被緩存的文章,是否重新緩存取決于它是否被修改過。
通過設(shè)置 yii\widgets\FragmentCache::dependency 選項(xiàng)來指定依賴,該選項(xiàng)的值可以是一個 yii\caching\Dependency 類的派生類,也可以是創(chuàng)建緩存對象的配置數(shù)組。以下代碼指定了一個片段緩存,它依賴于 update_at 字段是否被更改過的。
$dependency = [ 'class' => 'yii\caching\DbDependency', 'sql' => 'SELECT MAX(updated_at) FROM post', ]; if ($this->beginCache($id, ['dependency' => $dependency])) { // ... 在此生成內(nèi)容 ... $this->endCache(); }
變化
緩存的內(nèi)容可能需要根據(jù)一些參數(shù)的更改而變化。例如一個 Web 應(yīng)用支持多語言,同一段視圖代碼也許需要生成多個語言的內(nèi)容。因此可以設(shè)置緩存根據(jù)應(yīng)用當(dāng)前語言而變化。
通過設(shè)置 yii\widgets\FragmentCache::variations 選項(xiàng)來指定變化,該選項(xiàng)的值應(yīng)該是一個標(biāo)量,每個標(biāo)量代表不同的變化系數(shù)。例如設(shè)置緩存根據(jù)當(dāng)前語言而變化可以用以下代碼:
if ($this->beginCache($id, ['variations' => [Yii::$app->language]])) { // ... 在此生成內(nèi)容 ... $this->endCache(); }
開關(guān)
有時你可能只想在特定條件下開啟片段緩存。例如,一個顯示表單的頁面,可能只需要在初次請求時緩存表單(通過 GET 請求)。隨后請求所顯示(通過 POST 請求)的表單不該使用緩存,因?yàn)榇藭r表單中可能包含用戶輸入內(nèi)容。鑒于此種情況,可以使用 yii\widgets\FragmentCache::enabled 選項(xiàng)來指定緩存開關(guān),如下所示:
if ($this->beginCache($id, ['enabled' => Yii::$app->request->isGet])) { // ... 在此生成內(nèi)容 ... $this->endCache(); }
緩存嵌套
片段緩存可以被嵌套使用。一個片段緩存可以被另一個包裹。例如,評論被緩存在里層,同時整個評論的片段又被緩存在外層的文章中。以下代碼展示了片段緩存的嵌套使用:
if ($this->beginCache($id1)) { // ...在此生成內(nèi)容... if ($this->beginCache($id2, $options2)) { // ...在此生成內(nèi)容... $this->endCache(); } // ...在此生成內(nèi)容... $this->endCache(); }
可以為嵌套的緩存設(shè)置不同的配置項(xiàng)。例如,內(nèi)層緩存和外層緩存使用不同的過期時間。甚至當(dāng)外層緩存的數(shù)據(jù)過期失效了,內(nèi)層緩存仍然可能提供有效的片段緩存數(shù)據(jù)。但是,反之則不然。如果外層片段緩存沒有過期而被視為有效,此時即使內(nèi)層片段緩存已經(jīng)失效,它也將繼續(xù)提供同樣的緩存副本。因此,你必須謹(jǐn)慎處理緩存嵌套中的過期時間和依賴,否則外層的片段很有可能返回的是不符合你預(yù)期的失效數(shù)據(jù)。
譯注:外層的失效時間應(yīng)該短于內(nèi)層,外層的依賴條件應(yīng)該低于內(nèi)層,以確保最小的片段,返回的是最新的數(shù)據(jù)。
動態(tài)內(nèi)容
使用片段緩存時,可能會遇到一大段較為靜態(tài)的內(nèi)容中有少許動態(tài)內(nèi)容的情況。例如,一個顯示著菜單欄和當(dāng)前用戶名的頁面頭部。還有一種可能是緩存的內(nèi)容可能包含每次請求都需要執(zhí)行的 PHP 代碼(例如注冊資源包的代碼)。這兩個問題都可以使用動態(tài)內(nèi)容功能解決。
動態(tài)內(nèi)容的意思是這部分輸出的內(nèi)容不該被緩存,即便是它被包裹在片段緩存中。為了使內(nèi)容保持動態(tài),每次請求都執(zhí)行 PHP 代碼生成,即使這些代碼已經(jīng)被緩存了。
可以在片段緩存中調(diào)用 yii\base\View::renderDynamic() 去插入動態(tài)內(nèi)容,如下所示:
if ($this->beginCache($id1)) { // ...在此生成內(nèi)容... echo $this->renderDynamic('return Yii::$app->user->identity->name;'); // ...在此生成內(nèi)容... $this->endCache(); }
yii\base\View::renderDynamic() 方法接受一段 PHP 代碼作為參數(shù)。代碼的返回值被看作是動態(tài)內(nèi)容。這段代碼將在每次請求時都執(zhí)行,無論其外層的片段緩存是否被存儲。
相關(guān)文章
js+php實(shí)現(xiàn)靜態(tài)頁面實(shí)時調(diào)用用戶登陸狀態(tài)的方法
這篇文章主要介紹了js+php實(shí)現(xiàn)靜態(tài)頁面實(shí)時調(diào)用用戶登陸狀態(tài)的方法,采用在靜態(tài)頁面中使用js調(diào)用php頁面從而實(shí)現(xiàn)用戶登錄狀態(tài)的實(shí)時調(diào)用功能,需要的朋友可以參考下2015-01-01PHP學(xué)習(xí)筆記 IIS7下安裝配置php環(huán)境
PHP學(xué)習(xí)筆記 IIS7下安裝配置php環(huán)境,需要的朋友可以參考下2012-10-10Opcache導(dǎo)致php-fpm崩潰nginx返回502
這篇文章主要介紹了Opcache導(dǎo)致php-fpm崩潰nginx返回502的解決方法,十分實(shí)用,需要的朋友可以參考下2015-03-03Docker 安裝 PHP并與Nginx的部署實(shí)例講解
這篇文章主要介紹了Docker 安裝 PHP并與Nginx的部署實(shí)例講解,文中圖文操作步驟講解的很清楚,有感興趣的同學(xué)可以研究下2021-02-02mysql_fetch_row,mysql_fetch_array,mysql_fetch_assoc的區(qū)別
一直以來,有很多初學(xué)者搞不懂這些Mysql中從查詢結(jié)果集中取得數(shù)據(jù)的函數(shù)之間有什么區(qū)別,今天我就來秀一把,在秀之前先給大家一段PHP實(shí)例2009-04-04Trying to clone an uncloneable object of class Imagic的解決方法
使用網(wǎng)上流傳的一個程序?qū)崿F(xiàn)pdf截圖為png,需要使用Imagic擴(kuò)展2012-01-01