7. 缓存系统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

关于如何判断缓存数据是否过期及可缓存性质有三个概念:CacheMaxAgeCacheTagsCacheContexts
CacheMaxAge:代表缓存最大有效时长
CacheTags:缓存标签代表缓存的数据是什么
CacheContexts:缓存上下文代表同一数据在不同情况下的状态,上下文大多来自请求对象(但不是全部),比如请求的语言、数据格式、用户代理、用户、ip、cookie数据等等,同一数据在这些上下文下可以表现出不同的状态。
系统定义了一个可缓存依赖接口如果一个数据是可缓存的,那么可以获得实现该接口的可缓存元数据,里面定义了以上三个概念的值。


下面看一看具体代码实现:


drupal8核心Cache系统在 \core\lib\Drupal\Core\Cache ,它提供最核心的功能,供其他缓存模块和需要使用缓存的地方使用。
所有的backend都需要实现 CacheBackendInterface 接口,此接口里面定义了 backend 可用的公用方法(在drupal8里面所有的命名都有约定规则,接口以Interface结尾,特征以Trait结尾,工厂方法以Factory结尾,一个文件一个类,文件名与类名一致,所以你可以根据文件名大致推断文件的用途,更多约定规则请看社区文档),基于接口的软件设计是大型软件设计的精华之一,用户不需要知道具体实现,针对接口提供的方法使用即可。

drupal8默认提供了以下Backend:

Apcu4BackendApcuBackendDatabaseBackendMemoryBackendMemoryCounterBackendNullBackendPhpBackend

此外有两个特殊的Backend:

BackendChain 将多个Backend结合起来使用,形成一个Backend链

ChainedFastBackend 为了改善性能将一个快速Backend和一个一般Backend结合起来使用

以上Backend都由Backend工厂负责实例化(BackendChain除外),默认定义的Backend工厂有:

ApcuBackendFactoryChainedFastBackendFactoryDatabaseBackendFactoryMemoryBackendFactoryNullBackendFactoryPhpBackendFactory

这些工厂都实现了CacheFactoryInterface接口,该接口很简单,仅仅定义了一个get方法接受bin参数,它返回Backend对象,默认的bin和Backend有一套对应关系,如何自定义这种关系呢?在站点配置文件settings.php里面定义即可,格式如下:

$settings['cache']['bins']['你定义的bin']="缓存工厂服务的服务ID";

默认的对应关系定义在CacheFactory类的get方法里面,源代码见辅助内容区:

这里看一看使用最多的DatabaseBackend,它是默认Backend,负责数据库缓存操作,在DatabaseBackend类里面定义了数据库缓存bin的数据结构,先看一看默认的数据库缓存表,它们都以cache_为前缀,表字段如下:

 

cid:缓存id,一个255以内的ascii字符串

data:缓存的数据内容

expire:缓存到期时间戳,永久为-1

created:缓存创建的时间

serialized:缓存的数据是否经过序列化

tags:缓存标签

checksum:缓存数据有效性校验值

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);  
  }  

 

在前面我们说到了关于如何判断缓存数据是否过期有三个概念,那么系统在这个数据表结构里面如何反映出来呢?对应关系如下:

cid对应着上下文依赖,不同的上下文组合得到的缓存cid不一样,使用中的cid和缓存bin里面存储的cid不一样,存储的cid是经过哈希等方法转换得到的一个255以内的ascii字符串,转换过程见辅助内容区:

使用中的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方法,分别对应于上下文、标签、过期时间,有了这些方法就可以知道需要缓存的数据对象如何缓存,CacheMaxAgeCacheTagsCacheContexts 也称之为可缓存属性,CacheTags CacheContexts 都是字符串表示。在任何网站系统中可缓存数据均有这三方面的依赖。
\core\lib\Drupal\Core\Cache\Context 中定义了常用的上下文依赖。

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;  
 }  

 

以上就是drupal8缓存系统的核心,下面介绍两个应用核心缓存功能的模块,他们是 Page Cache Dynamic Page Cache ,它们都是系统默认提供的模块,且不可关闭,位于 core\modules ,在系统管理后台-扩展:模块列表中能找到。


Page Cache模块为匿名用户提供页面缓存,在我前面发的《HttpKernel堆栈》里面有提到,它实现了 HttpKernelInterface 接口,属于http中间件,缓存的数据位于数据库表 cache_render 里面,使用URI和请求格式作为cid,这个模块的bin就是render,Backend是DatabaseBackend。


Dynamic Page Cache模块为任意用户提供动态的缓存页面,数据储存于数据库 cache_dynamic_page_cache 表,它通过使用事件订阅机制在在动态响应产生时缓存数据,关于事件订阅敬请期待后续云游系列主题。


如果你看到这里,你应该已经大致了解了drupal8的缓存运作机制,明天就是国庆假期,祝大家节日快乐,总算赶在节前出了这篇主题,有不明地方欢迎留言。

本书共161小节。


目前全部收费内容共295.00元。购买全部

评论 (2)

  • 29
    • 2023-04-11

    我看drupal很多配置、路由什么的都是存在数据表里的,这个目的是为什么呢?为什么不直接在配置文件实现呢,数据库还有网络请求资源的消耗?

    第二个问题就是:什么时候会去更新表里的数据呢?每个request过来都要编译一次,存储到数据库么?

  • 回复你的第一个问题:
    配置和路由储存到数据库主要是高负载架构问题,在大型系统中,会有多台WEB服务器对一个数据库,数据库还会做主从分离,这种情况下,如果放在某台服务器的文件系统中,其他服务器怎么访问呢?
    第二个问题,缓存三要素引起缓存失效时,会更新缓存系统