1. 程式人生 > >雲客Drupal8原始碼分析之快取系統Cache

雲客Drupal8原始碼分析之快取系統Cache

在介紹drupal8的快取系統前我們先了解一下快取系統的本質及特性,快取的存在依賴於兩個目的:節省資源和提高速度,起不到這兩作用則快取沒有存在的必要,當一個結果需要進行大量計算才能得到,而它又不會頻繁更新那麼快取結果可以節省大量算力,快取的是一個結果,這個結果可以存放在多臺伺服器上面實現負載均衡,從而進一步提高訪問速度,在高訪問網站中快取非常重要,drupal8的快取設計也是圍繞這兩個目的而設計。

Symfony沒有提供快取元件,drupal8完全自己實現了快取系統,druapl8的快取系統不只是快取響應頁面,它還快取許多系統中間資料,比如容器定義資料、配置資料、擴充套件資料等等,你覺得有必要快取的資料它都可以快取,預設的它使用資料庫來存放快取資料(流媒體、二進位制檔案等預設不進行資料庫儲存),在資料庫中以cache_為字首的資料表均存放的是快取資料,此外有一個叫做cachetags的資料表用來儲存快取系統的維護資料,當手動清理快取的時候清空他們即可,包括cachetags

表,如果只清空某一個快取表則不要清理cachetags表,清理cachetags會導致所有快取表裡面的資料失效(僅失效而已,不會導致系統錯誤),當系統執行時間過長時會發現快取系統累積太多資料,從而降低快取效率,可以清空他們一下。

drupal8的快取系統可以進行多種方式快取資料,不僅僅是資料庫,還可以結合配置外部快取記憶體等等,下面介紹它的實現原理:

在理解drupal8快取系統時請記住它實現了兩大塊內容,一是如何儲存與取回資料、二是如何判斷快取資料是否過期及可快取性質。

關於如何儲存與取回資料drupal8有兩個概念:cache_bin和cache.backend

bin可以理解為存放資料的箱子、容器、資料池等等,比如在預設實現中資料庫的一個快取表就是一個bin,這個表就是一個箱子,裡面是一條一條的資料。backend

就是bin的操作者,它是一個物件,負責儲存、刪除、取回、失效bin裡面的資料,為什麼取了backend這麼個名呢,直譯是後端的意思,它相對於前端快取而已(快取代理伺服器、瀏覽器快取等),在系統裡面每個bin對應著一個backend,這種對應關係可以在站點配置檔案settings.php裡面設定(下面講),無配置的情況下采用預設配置,只需要將bin傳給快取工廠就能自動得到backend

關於如何判斷快取資料是否過期及可快取性質有三個概念:CacheMaxAgeCacheTags、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方法,分別對應於上下文、標籤、過期時間,有了這些方法就可以知道需要快取的資料物件如何快取,CacheMaxAgeCacheTags、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就是renderBackendDatabaseBackend。

Dynamic Page Cache模組為任意使用者提供動態的快取頁面,資料儲存於資料庫cache_dynamic_page_cache表,它通過使用事件訂閱機制在在動態響應產生時快取資料,關於事件訂閱敬請期待後續雲遊系列主題。

如果你看到這裡,你應該已經大致瞭解了drupal8的快取運作機制,明天就是國慶假期,祝大家節日快樂,總算趕在節前出了這篇主題,有不明地方歡迎留言

我是雲客,【雲遊天下,做客四方】,微信號:php-world,歡迎轉載,但須註明出處,討論請加qq群203286137