“中国要复兴、富强,必须在开源软件领域起到主导作用,为了国家安全和人类发展,责无旁贷,我们须为此而奋斗”——By:云客
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
关于如何判断缓存数据是否过期及可缓存性质有三个概念: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模块为匿名用户提供页面缓存,在我前面发的《HttpKernel堆栈》里面有提到,它实现了HttpKernelInterface接口,属于http中间件,缓存的数据位于数据库表cache_render
里面,使用URI和请求格式作为cid,这个模块的bin就是render
,Backend是DatabaseBackend。
Dynamic Page Cache模块为任意用户提供动态的缓存页面,数据储存于数据库cache_dynamic_page_cache表,它通过使用事件订阅机制在在动态响应产生时缓存数据,关于事件订阅敬请期待后续云游系列主题。
如果你看到这里,你应该已经大致了解了drupal8的缓存运作机制,明天就是国庆假期,祝大家节日快乐,总算赶在节前出了这篇主题,有不明地方欢迎留言
反馈互动