silverstripe-terraformers / keys-for-cache
Silverstripe 缓存键管理
Requires
- php: ^8.1
- silverstripe/framework: ^5
Requires (Dev)
- phpunit/phpunit: ^9.5
- slevomat/coding-standard: ~8.8.0
- tractorcow/silverstripe-fluent: ^7
README
此模块帮助您创建单个缓存键,这些键可以用作任何缓存策略的一部分,但最常用的是与 Silverstripe 的部分缓存 功能。
此模块的总体目标有两个
- 使开发者更容易为我们的
DataObjects
(尤其是那些有复杂依赖的)创建缓存键。 - 通过减少在最终用户请求时计算缓存键的数量和复杂性来提高我们应用程序的性能。
我们将如何实现这些目标?
我们将使用和增强现有的配置范式,这些范式Silverstripe开发者已经熟悉。
我们将把计算缓存键的成本移至对我们的 DataObjects
进行更改时,而不是在最终用户请求时。
此模块不是什么:
此模块不是为了提供缓存方法,而是为了提供您可以使用您首选的缓存方法(例如:部分缓存)的 键。
安装
composer require silverstripe-terraformers/keys-for-cache
需求
- PHP
^8.1
- Silverstripe 框架
^5
通过我们标记为 ^1
的版本提供对 Silverstripe 4 的支持。
为什么缓存键难以处理
TL;DR:相关信息的任何更改都需要更新缓存键,并且我们通常有包含多个嵌套 DataObjects
的内容结构(例如:轮播图块)。
任何缓存键的目标都是尽可能地降低计算成本(因为这必须在每次请求时发生),同时也要在适当的时间失效(例如:相关内容发生变化时)。
考虑以下情况
- 我们有一个应用了Elemental模块的
Page
。 - 其中一个可用的块是轮播图块。此块本身包含轮播图项,每个项包含一个图像。
- 当更改块本身、其项或分配给其项的任何图像时,应使缓存键失效。
单个轮播图块的缓存键可能看起来像这样
public function getCacheKey(): string { $parts = [ // Parts related to the block itself static::class, $this->ID, $this->LastEdited, // Parts related to the Items within the block $this->Items()->max('LastEdited'), $this->Items()->count(), // Parts related to the Images assigned to our Items Image::get()->filter('ID', $this->Items()->column('ImageID'))->max('LastEdited'), Image::get()->filter('ID', $this->Items()->column('ImageID'))->count(), ]; return implode('-', $parts); }
这可以稍微优化一下,但重点是:每次执行时都需要多个查询,并且需要相当大的认知负荷来理解(以及最初构思)。
简而言之,我们希望实现与上述示例相同的成果,但只需简单的配置,例如这样
App\Blocks\CarouselBlock: has_cache_key: true cares: - Items App\Blocks\CarouselItem: cares: - Image
设置和配置
序言:当我们谈论“记录的更改”时,这包括所有 C.R.U.D. 操作。
关系配置:为了使此模块正常工作,我们可能需要比Silverstripe ORM更了解您的DataObjects
之间的关系。有关更多信息,请参阅此处。
有缓存键
首先,您需要告诉我们您希望为哪些DataObjects
生成缓存键。例如,我们可能希望为所有页面和所有元素生成键。
Page: has_cache_key: true DNADesign\Elemental\Models\BaseElement: has_cache_key: true
通过添加此配置,您将能够访问DataObject
上的getCacheKey()
方法,并在您的模板中访问$CacheKey
变量,当您拥有该DataObject
时。
接下来,为了确保当相关的DataObjects
被更新时您的缓存键失效,您需要定义您的模型可能有的任何依赖关系。
请注意以下三个重要配置:
关心
此配置确定当其他(相关)DataObjects
被操作时,如何影响$this
DataObject
。
例如:我们已要求所有元素都有缓存键。如果您有一个包含CarouselItems
的CarouselBlock
,那么您将希望确保在CarouselItem
更新时,CarouselBlock
的缓存键也失效。
您可以通过告诉我们您的CarouselBlock
关注其Items
来实现这一点。
App\Blocks\CarouselBlock: cares: - Items
或者在您的类中:
class CarouselBlock extends BaseElement { private static array $has_many = [ 'Items' => CarouselItem::class, ]; // $owns is optional, but quite common in this use case. I've added it here simply to illustrate how $cares // follows the same paradigm private static array $owns = [ 'Items', ]; private static array $cares = [ 'Items', ]; }
以原始示例为基础,我们还想让我们的CarouselItem
包括其图像的变化作为其缓存键的一部分。现在我们也可以在我们的CarouselItem
中添加一个cares
配置。
App\Blocks\CarouselItem: cares: - Image
或者在您的类中这样写:
class CarouselItem extends DataObject { private static array $has_one = [ 'Image' => Image::class, ]; // $owns is optional, but quite common in this use case. I've added it here simply to illustrate how $cares // follows the same paradigm private static array $owns = [ 'Image', ]; private static array $cares = [ 'Image', ]; }
现在,每当链接的Image
被更新时,它也会更新CarouselItem
,进而更新链接的Carousel
。更进一步,一直追溯到Page
,我们也可以添加以下内容:
# Our BlockPage cares about changes to its ElementalArea App\Elemental\BlockPage: cares: - ElementalArea # Our ElementalAreas care about any changes made to its Elements DNADesign\Elemental\Models\ElementalArea: cares: Elements: BaseElement::class
我们的BlockPage
现在关注我们的ElementalArea
,而我们的ElementalArea
现在关注其所有块/元素。这意味着,每当我们对任何块进行更改(只要我们像我们对Carousel
那样配置它们),我们将在我们的Page
上获得新的缓存键值。
或者,您也可以通过使用Touches来实现相同的结果。
重要提示:您的DataObject
不需要has_cache_key
才能关注或接触其他DataObjects
。实际上,我们非常依赖您通过cares/touches
提供完整的关联树。我们只为has_cache_key
的DataObjects
生成缓存键,但我们将继续遵循您创建的路径,直到路径用尽。
重要提示:务必考虑在元素更新时使Page
缓存失效的性能影响。它已被添加到上面,纯粹是为了说明技术上可能做到的事情;它不是作为建议添加的。
接触
此配置确定当$this
DataObject
被更新时,如何影响其他对象。例如:当$this
DataObject
被更新时,它应该“接触”一些其他DataObjects
,以便它们也使它们的缓存键失效。
使用上面的例子,如果您有一个具有CarouselItems
的CarouselBlock
,则可以按如下方式配置它(其中key
是字段关系名称,值是它相关的class
)
App\Blocks\CarouselItem: touches: - Parent
或者在您的类中这样写:
class CarouselItem extends DataObject { private static array $has_one = [ 'Parent' => CarouselBlock::class, ]; private static array $touches = [ 'Parent', ]; }
这意味着每当CarouselItem
被更新时,它也会更新父级CarouselBlock
的缓存键。
或者,您也可以通过使用Cares来实现相同的结果。
重要提示:您的DataObject
不需要has_cache_key
才能关注或接触其他DataObjects
。实际上,我们非常依赖您通过cares/touches
提供完整的关联树。我们只为has_cache_key
的DataObjects
生成缓存键,但我们将继续遵循您创建的路径,直到路径用尽。
全局关心
您可能会遇到需要当特定类的任何数据对象发生变化时更新缓存键的情况。例如,我们可以有一个“最近更新块”,列出最近更新的页面。为此,我们希望实现当任何页面更新时,也会使任何关心我们页面全局变化的数据对象的缓存键失效。
App\Blocks\RecentUpdates: global_cares: - SilverStripe\CMS\Model\SiteTree
重要提示:这些全局更新在发生时不会使用touches/cares
。例如,如果Blocks\RecentUpdates
有touches
为Link: SilverStripe\CMS\Model\SiteTree
,网站树就不会更新。这是全局更新机制,以确保我们不遇到性能问题。
使用方法和示例
查看:使用方法和示例
性能影响/考虑事项
这将增加更新DataObjects
时对数据库的查询次数,但到目前为止,这并没有对CMS中页面(例如)的发布时间产生明显的影响,也没有对与DataObjects
操作相关的定时任务的执行时间产生明显的影响。
话虽如此
- 您仍应了解您启用了哪些
cares
和touches
配置。 - 如果您开始注意到(例如)页面发布时的性能问题,那么您可能需要重新考虑您在页面和相关
DataObjects
(例如:块)中设置的cares
或touches
关系的范围。
队列作业
如果您想防止内容作者在CMS中编辑时响应速度略微变慢,可以通过注入CacheKeyExtension
并更新triggerEvent
以创建一个作业,然后在作业中调用CacheRelationService::singleton()->processChange($this->DataObject)
来生成缓存更新作业。
案例研究
查看:案例研究
流畅支持
查看:Fluent支持
单元测试
查看:单元测试
许可
查看 许可协议
维护者
- Adrian Humphreys adrhumphreys@gmail.com
- Chris Penny chris.penny@gmail.com
- Adrian Jimson adrian.jimson@silverstripe.com
开发和贡献
如果您想为此模块做出贡献,请确保提出一个拉取请求,并与模块维护者进行讨论。