雲客Drupal8原始碼分析之快取系統Cache
在介紹drupal8的快取系統前我們先了解一下快取系統的本質及特性,快取的存在依賴於兩個目的:節省資源和提高速度,起不到這兩作用則快取沒有存在的必要,當一個結果需要進行大量計算才能得到,而它又不會頻繁更新那麼快取結果可以節省大量算力,快取的是一個結果,這個結果可以存放在多臺伺服器上面實現負載均衡,從而進一步提高訪問速度,在高訪問網站中快取非常重要,drupal8的快取設計也是圍繞這兩個目的而設計。
Symfony沒有提供快取元件,drupal8完全自己實現了快取系統,druapl8的快取系統不只是快取響應頁面,它還快取許多系統中間資料,比如容器定義資料、配置資料、擴充套件資料等等,你覺得有必要快取的資料它都可以快取,預設的它使用資料庫來存放快取資料(流媒體、二進位制檔案等預設不進行資料庫儲存),在資料庫中以cache_為字首的資料表均存放的是快取資料,此外有一個叫做cachetags的資料表用來儲存快取系統的維護資料,當手動清理快取的時候清空他們即可,包括cachetags
drupal8的快取系統可以進行多種方式快取資料,不僅僅是資料庫,還可以結合配置外部快取記憶體等等,下面介紹它的實現原理:
在理解drupal8快取系統時請記住它實現了兩大塊內容,一是如何儲存與取回資料、二是如何判斷快取資料是否過期及可快取性質。
關於如何儲存與取回資料drupal8有兩個概念:cache_bin和cache.backend
bin可以理解為存放資料的箱子、容器、資料池等等,比如在預設實現中資料庫的一個快取表就是一個bin,這個表就是一個箱子,裡面是一條一條的資料。backend
關於如何判斷快取資料是否過期及可快取性質有三個概念:CacheMaxAge、CacheTags、CacheContexts
CacheMaxAge:代表快取最大有效時長
CacheTags:快取標籤代表快取的資料是什麼
CacheContexts:快取上下文代表同一資料在不同情況下的狀態,上下文大多來自請求物件(但不是全部),比如請求的語言、資料格式、使用者代理、使用者、ip、cookie資料等等,同一資料在這些上下文下可以表現出不同的狀態。
系統定義了一個可快取依賴介面如果一個數據是可快取的,那麼可以獲得實現該介面的可快取元資料,裡面定義了以上三個概念的值。
下面看一看具體程式碼實現:
drupal8核心Cache系統在\core\lib\Drupal\Core\Cache,它提供最核心的功能,供其他快取模組和需要使用快取的地方使用。
所有的backend都需要實現CacheBackendInterface介面,此接口裡面定義了backend可用的公用方法(在drupal8裡面所有的命名都有約定規則,介面以Interface結尾,特徵以Trait結尾,工廠方法以Factory結尾,一個檔案一個類,檔名與類名一致,所以你可以根據檔名大致推斷檔案的用途,更多約定規則請看社群文件),基於介面的軟體設計是大型軟體設計的精華之一,使用者不需要知道具體實現,針對介面提供的方法使用即可。
drupal8預設提供了以下Backend:
Apcu4Backend、ApcuBackend、DatabaseBackend、MemoryBackend、MemoryCounterBackend、NullBackend、PhpBackend
此外有兩個特殊的Backend:
BackendChain將多個Backend結合起來使用,形成一個Backend鏈
ChainedFastBackend為了改善效能將一個快速Backend和一個一般Backend結合起來使用
以上Backend都由Backend工廠負責例項化(BackendChain除外),預設定義的Backend工廠有:
ApcuBackendFactory、ChainedFastBackendFactory、DatabaseBackendFactory、MemoryBackendFactory、NullBackendFactory、PhpBackendFactory
這些工廠都實現了CacheFactoryInterface介面,該介面很簡單,僅僅定義了一個get方法接受bin引數,它返回Backend物件,預設的bin和Backend有一套對應關係,如何自定義這種關係呢?在站點配置檔案settings.php裡面定義即可,格式如下:
$settings['cache']['bins']['你定義的bin']="快取工廠服務的服務ID";
預設的對應關係定義在CacheFactory類的get方法裡面,原始碼如下:
public function get($bin) {
$cache_settings = $this->settings->get('cache');
if (isset($cache_settings['bins'][$bin])) {
$service_name = $cache_settings['bins'][$bin];
}
elseif (isset($cache_settings['default'])) {
$service_name = $cache_settings['default'];
}
elseif (isset($this->defaultBinBackends[$bin])) {
$service_name = $this->defaultBinBackends[$bin];
}
else {
$service_name = 'cache.backend.database';
}
return $this->container->get($service_name)->get($bin);
}
這裡看一看使用最多的DatabaseBackend,它是預設Backend,負責資料庫快取操作,在DatabaseBackend類裡面定義了資料庫快取bin的資料結構,先看一看預設的資料庫快取表,它們都以cache_為字首,表字段如下:
cid:快取id,一個255以內的ascii字串
data:快取的資料內容
expire:快取到期時間戳,永久為-1
created:快取建立的時間
serialized:快取的資料是否經過序列化
tags:快取標籤
checksum:快取資料有效性校驗值
在前面我們說到了關於如何判斷快取資料是否過期有三個概念,那麼系統在這個資料表結構裡面如何反映出來呢?對應關係如下:
cid對應著上下文依賴,不同的上下文組合得到的快取cid不一樣,使用中的cid和快取bin裡面儲存的cid不一樣,儲存的cid是經過雜湊等方法轉換得到的一個255以內的ascii字元串,轉換過程如下:
protected function normalizeCid($cid) {
// Nothing to do if the ID is a US ASCII string of 255 characters or less.
$cid_is_ascii = mb_check_encoding($cid, 'ASCII');
if (strlen($cid) <= 255 && $cid_is_ascii) {
return $cid;
}
// Return a string that uses as much as possible of the original cache ID
// with the hash appended.
$hash = Crypt::hashBase64($cid);
if (!$cid_is_ascii) {
return $hash;
}
return substr($cid, 0, 255 - strlen($hash)) . $hash;
}
使用中的cid是由CacheContexts快取上下文組合得到
expire對應著時間依賴,指示過期的時間,這個簡單不必多講
tags對應著資料種類依賴,一條快取可能由多個標籤對應的資料組成,其中任何一個tag對應的資料發生變化都會造成該條快取失效,每一個tag對應的資料又可能被多條快取使用,那麼當tag對應的資料發生變化時我們如何及時得知這些快取條目過期了呢?這是drupal8快取設計的一個精髓,答案就在於checksum有效性校驗值,上文說過系統中存在一個快取維護資料表,表名稱是cachetags,裡面有兩個欄位:標籤及失效次數,每當一個標籤對應的資料產生修改動作,那麼失效欄位就會加一,而checksum的值就是該條快取所有標籤對應的失效欄位值之和,也就是說任意一個tag對應的資料只要產生修改動作,那麼就會引起使用到它的快取校驗值變化,系統就可以根據這個來判斷快取是否失效,這個計算校驗值和判斷的工作是由DatabaseCacheTagsChecksum類來完成的,它計算出當前校驗值再和快取bin裡面的校驗值比較,不同則快取失效,往往比失效的校驗值大,如果快取的資料無標籤則校驗值永遠為0。
資料庫bin裡面每個條目就是一個快取,drupal8還很貼心的為我們做了進一步的工作,在一個快取條目裡面儲存很大的一個數據集,資料集以陣列的方式儲存在快取bin的data欄位,對這個資料集的操作drupal8提供了CacheCollector類,它實現CacheCollectorInterface, DestructableInterface介面,Destructable介面用於服務在毀滅時做一些收尾工作,類似於解構函式。
以上就是資料庫快取的資料結構及存取情況,下面來看一看資料的可快取性:
需要快取的資料物件被注入了可快取元資料物件,那麼它就具備了可快取性,可快取元資料物件實現了可快取依賴介面:CacheableDependencyInterface,該接口裡面僅僅定義了三個get方法,分別對應於上下文、標籤、過期時間,有了這些方法就可以知道需要快取的資料物件如何快取,CacheMaxAge、CacheTags、CacheContexts也稱之為可快取屬性,CacheTags、CacheContexts都是字串表示。在任何網站系統中可快取資料均有這三方面的依賴。
在\core\lib\Drupal\Core\Cache\Context中定義了常用的上下文依賴。
以上就是drupal8快取系統的核心,下面介紹兩個應用核心快取功能的模組,他們是Page Cache和Dynamic Page Cache,它們都是系統預設提供的模組,且不可關閉,位於core\modules,在系統管理後臺-擴充套件:模組列表中能找到。
Page Cache模組為匿名使用者提供頁面快取,在我前面發的《雲遊Drupal8系列之HttpKernel堆疊》裡面有提到,它實現了HttpKernelInterface介面,屬於http中介軟體,快取的資料位於資料庫表cache_render
裡面,使用URI和請求格式作為cid,這個模組的bin就是render
,Backend是DatabaseBackend。
Dynamic Page Cache模組為任意使用者提供動態的快取頁面,資料儲存於資料庫cache_dynamic_page_cache表,它通過使用事件訂閱機制在在動態響應產生時快取資料,關於事件訂閱敬請期待後續雲遊系列主題。
如果你看到這裡,你應該已經大致瞭解了drupal8的快取運作機制,明天就是國慶假期,祝大家節日快樂,總算趕在節前出了這篇主題,有不明地方歡迎留言
我是雲客,【雲遊天下,做客四方】,微信號:php-world,歡迎轉載,但須註明出處,討論請加qq群203286137