缓存标签
缓存标签 = 数据依赖
缓存标签描述了对 Drupal 管理的数据的依赖
为什么?
缓存标签提供了一种声明式的方式来跟踪哪些缓存项依赖于某些由 Drupal 管理的数据。
这对像 Drupal 这样的内容管理系统/框架非常重要,因为同一内容可能会以不同方式被重复使用。换句话说:我们无法预先知道某个内容会在哪里被使用。在任何使用内容的地方,它都可能被缓存。这意味着相同的内容可能会在几十个地方被缓存。这就引出了那句著名的话:在计算机科学中,只有两个难题:缓存失效和命名。——即,如何让所有使用了该内容的缓存项失效?
注意:Drupal 7 提供了 3 种使缓存项失效的方式:使某个特定 CID 失效、使 CID 前缀失效,或者使整个缓存 bin 失效。但这三种方法都无法使包含已修改实体的缓存项失效,因为根本无法知道!
是什么?
缓存标签是一个字符串。
缓存标签以字符串数组 string[] 的形式传递(顺序无关)。这是一个集合,因为一个缓存项可能依赖于多个缓存标签。
语法
按约定,它们的形式为 thing:identifier —— 当某个 thing 没有多个实例时,它的形式为 thing。唯一的规则是不能包含空格。
没有严格的语法限制。
示例:
- node:5 - 节点 ID=5 的缓存标签(当它被修改时会失效)
- user:3 - 用户 ID=3 的缓存标签(每次该用户修改时会失效)
- node_list - 节点对象列表的缓存标签(当任何节点对象被更新、删除或创建时失效,即可能需要更新节点列表)。适用于任何实体类型,格式为 {entity_type}_list。
- config:system.performance - 配置 system.performance 的缓存标签
- library_info - 资源库的缓存标签
常见缓存标签
Drupal 管理的数据分为三类:
- 实体 (entities) —— 它们有形式为 <entity type ID>:<entity ID> 的缓存标签,以及 <entity type ID>_list 和 <entity type ID>_list:<bundle> 来使实体列表失效。配置实体类型使用基础配置对象的缓存标签。
- 配置 (configuration) —— 它们有形式为 config:<configuration name> 的缓存标签
- 自定义 "custom"(例如 library_info)
Drupal 会自动为实体和配置提供缓存标签 —— 参见 Entity 基类 和 ConfigBase 基类。(所有具体的实体类型和配置对象都继承自它们。)
尽管许多对象类型遵循可预测的缓存标签格式 <entity type ID>:<entity ID>,但第三方代码不应依赖此格式。相反,它应该通过调用 ::getCacheTags() 方法获取某个对象的缓存标签,例如 $node->getCacheTags(), $user->getCacheTags(), $view->getCacheTags() 等。
此外,还可能需要基于实体相关的数据来使缓存失效(例如,当为列表创建新实体时,需要更新渲染后的 HTML 列表):这可以通过 EntityTypeInterface::getListCacheTags() 来完成,该方法返回的标签应与实体对象自身的标签一起失效。从 Drupal 8.9 开始(变更通知),具有 bundle 的实体还会自动带有更具体的 bundle 缓存标签,以实现更有针对性的列表失效。
还可以基于对象的特定字段值定义自定义、更具体的缓存标签,例如,基于分类术语引用字段来给显示某个分类的实体列表设置缓存标签。在对象的 presave/delete 钩子中可以使这些标签失效:
function yourmodule_node_presave(NodeInterface $node) {
$tags = [];
if ($node->hasField('field_category')) {
foreach ($node->get('field_category') as $item) {
$tags[] = 'mysite:node:category:' . $item->target_id;
}
}
if ($tags) {
Cache::invalidateTags($tags);
}
}
这些标签可以在代码和 Views 中使用,借助 Views Custom Cache Tag 模块。
注意:目前还没有 API 用于从对象中获取单独的 bundle 或更具体的缓存标签。这是因为并不是对象本身决定哪些列表缓存标签相关,而是取决于请求本身。未来 Drupal 核心版本可能会改进对每个 bundle 的缓存标签的内置支持,并将其集成到实体查询构建器和 Views 中。
如何使用
设置
任何缓存服务器都必须实现 CacheBackendInterface,因此在使用 ::set() 方法存储缓存项时,可以指定第三个和第四个参数,例如:
$cache_backend->set( $cid, $data, Cache::PERMANENT, ['node:5', 'user:7'] );
这会将缓存项 $cid 永久存储(即无限期存储),但使其受缓存标签 node:5 或 user:7 的失效控制。
使缓存失效
缓存标签项可以通过 cache_tags.invalidator:invalidateTags()(或者在无法注入 cache_tags.invalidator 服务时,使用 Cache::invalidateTags())来失效,它接收一个缓存标签集合(string[])。
注意:这会使所有缓存 bin 中带有这些标签的缓存项失效。这是因为为单个缓存 bin 使缓存标签失效没有意义,因为修改的数据可能被其他缓存 bin 中的缓存项依赖。
调试
以上内容在调试缓存时非常有用。但还有一件事:假设某个缓存项使用缓存标签 ['foo', 'bar']。那么对应的缓存项会在标签列(例如数据库缓存)中存储如下值:
bar foo
换句话说:
- 缓存标签用空格分隔
- 缓存标签按字母顺序排序
这应该有助于分析和调试缓存!
HTTP 响应头(调试)
最后:很容易看到某个响应依赖于哪些缓存标签(因此会因其失效):只需查看响应头 X-Drupal-Cache-Tags!
(这也是为什么 缓存标签不能包含空格:因为 X-Drupal-Cache-Tags 响应头与许多 HTTP 响应头一样,使用空格分隔值。)
注意:如果你没有看到这些响应头,需要 将 Drupal 配置为开发模式。
与反向代理集成
除了在 Drupal 内部缓存响应并通过缓存标签使其失效之外,还可以在反向代理(Varnish、CDN …)中缓存响应,并通过这些响应关联的缓存标签来使其失效。为了让反向代理知道每个响应关联的缓存标签,可以在响应头中一并发送缓存标签。
就像 Drupal 8 可以发送 X-Drupal-Cache-Tags 响应头用于调试一样,它也可以 发送 Surrogate-Keys 响应头(空格分隔)(某些 CDN 需要),或者 发送 Cache-Tag 响应头(逗号分隔)(其他 CDN 需要)。并且这也可以是你自己运行的反向代理,而不是商用 CDN 服务。
通常建议 Web 服务器和反向代理支持响应头值最大 16 KB。
1. HTTP 是文本协议。缓存标签因此也基于文本。反向代理可以自由地在内部使用其他数据结构表示缓存标签。选择 16 KB 响应头值限制有两个原因:A) 确保它在 99% 的情况下能工作;B) 实际上是可行的。常见 Web 服务器(Apache)和 CDN(Fastly)都支持 16 KB 的响应头值。这意味着大约 1000 个缓存标签,足够覆盖 99% 的情况。
2. 缓存标签数量因站点和响应不同而差异很大。如果某个响应依赖很多东西,就会有很多缓存标签。超过 1000 个缓存标签的响应会很罕见。
3. 但当然,这个指导值(大约 1000 个标签/响应)会随着时间演变,因为 A) 我们看到越来越多真实应用的使用情况,B) 系统会专门利用或基于该能力构建。
最后,超过 1000 个缓存标签通常表明一个更深层次的问题:响应过于复杂,应该被拆分。在 Drupal 中并不禁止超过该数量,但这可能需要手动调整。这在极其复杂的使用场景中是可以接受的。甚至在少于 1000 个标签的情况下也可能出现这种情况。
请阅读关于 在 Varnish 中使用缓存标签 的文档。
已知支持基于缓存标签失效/清除的 CDN:
CloudFlare
Fastly
KeyCDN
Akamai
内部页面缓存
Drupal 8 全面使用缓存标签,使其默认启用了 内部页面缓存。它本质上就是一个内置的反向代理。