exteon / identity-cache
身份缓存实现
README
IdentityCache 是一个内置的数组替换对象缓存,旨在与 ORM 和身份映射模式配合使用,提供基于 Weakref 的唯一身份到对象的解析,以及基于对象流行度的智能缓存未使用对象
摘要
身份映射模式 在 ORM 实现中非常常见。它由持久化层和应用层之间的中间缓存组成,能够通过唯一对象 ID 检索对象实例。它必须满足两个要求
- 只要身份在使用中(应用层持有任何对其的引用),身份映射在查询其 ID 时将检索相同的实例。否则,当应用层突变同一身份的不同实例时,数据一致性将受到影响。
- 对于未使用的身份,缓存通过保留实例以供将来重用,从而提供性能优势。但是,缓存应该有一个机制来丢弃缓存实例,以免消耗过多的资源(例如内存)。
许多实现使用 PHP 本地数组来存储身份 > 实例映射;然而,这有一个缺点,即无限期地存储实例,最终填满内存,即使引用不再使用。
实现
IdentityCache 实现了基于 Weakref 的缓存来满足要求,并使用基于流行度的垃圾回收算法来保持缓存未使用实例的大小在可配置的限制内。
IdentityCache
和 IdentityMap
被设计为原生 PHP 数组的内置替代品。IdentityMap
不提供缓存功能;一旦对象不再使用(其引用计数降至 0),对象就会被释放。
需求
- 对于 PHP > 7.4,使用
Exteon\IdentityCache\Weakreference\IdentityMap
,Exteon\IdentityCache\Weakreference\IdentityMap
,它使用 Weakreference - 对于 PHP < 7.4,需要 Weakref 扩展。然后使用
Exteon\IdentityCache\Weakref\IdentityMap
,Exteon\IdentityCache\Weakref\IdentityMap
- PHP < 7.2 不受支持
用法
使用 composer
安装
composer config repositories.exteon-identity-map vcs https://github.com/exteon/identity-cache composer require exteon/identity-cache
IdentityMap
$map = new \Exteon\IdentityCache\WeakReference\IdentityMap(); $instance = new stdClass(); $map[1] = $instance; // While object is in use via $instance, the map will provide a reference to it // via its id. assert($map[1] === $instance); // Put object out of use by releasing its reference unset($instance); // Object has been freed and its reference unset, because all references to it // were released assert(isset($map[1]) === false);
IdentityCache
$cache = new \Exteon\IdentityCache\WeakReference\IdentityCache([ 'trigger' => 'maxRetainedObjects', 'maxRetainedObjects' => 1, 'purgeStrategy' => 'popularity', 'purgePressure' => 50 ]); // Create 2 object instances $instance1 = new stdClass(); $instance2 = new stdClass(); $cache[1] = $instance1; $cache[2] = $instance2; // While objects are in use via $instance1, $instance2, the map holds references // to them via their ids assert($cache[1] === $instance1); assert($cache[1] === $instance1); // Increase instance 1's popularity by accessing it repeatedly $cache[1]; $cache[1]; $cache[1]; // Put objects out of use by releasing their references unset($instance1); unset($instance2); // maxRetainedObjects was configured to 1; instances will be purged $cache->gc(); // instance 1's popularity was greater so it was preserved in the cache assert(is_object($cache[1])); // instance 2 was of a lesser popularity so it was purged assert(isset($cache[2]) === false);
配置
可以将关联数组传递给 \Exteon\IdentityCache\WeakReference\IdentityCache
的构造函数。以下显示示例用法、默认值和可能值。如果不将配置选项传递给构造函数,则将使用其默认值
use \Exteon\IdentityCache\WeakReference\IdentityCache; $cache = new IdentityCache([ // Specifies the condition that triggers the cache to purge unused // references. // Possible values: // 'maxRetainedObjects' : purging will be initiated when we are // caching a number of references greater // than the maxRetainedObjects parameter // 'maxScriptMemory' : purging will be initiated when the total // memory consumed by the running script is // greater than the value specified in the // maxScriptMemory parameter. // 'none' : purging will not be done. This can be // changed later by using the setConfig() // method. 'trigger' => 'maxRetainedObjects', // Number of objects that can be retained before purging if trigger is // set to 'maxRetainedObjects' 'maxRetainedObjects' => 1000, // Maximum total memory consumed by the running script before purging if // trigger is 'maxScriptMemory'. Format is the same as php.ini's sizes// // format. 'maxScriptMemory' => '64M', // When purging, which percent of the cached objects to purge? 'purgePressure' => 10, // When purging, how to select which instances to keep? // Possible values: // 'popularity' : Keep the most popular objects. See the following // section for more details on how popularity-based // caching works. // 'random' : Randomly purge the number of objects dictated // by purgePressure. 'purgeStrategy' => 'popularity', // See the Popularity-based caching section for an explanation of this// // parameter 'popularityDecay' => IdentityCache::getPopularityDecay(1000, 10000, 2) ]);
基于流行度的缓存
当 purgeStrategy
配置选项为 popularity
时,将为缓存对象保持一个流行度索引。每当从缓存中访问对象时,对象的流行度增加 1。
为了不永久保留曾经非常流行但长时间未使用的对象,每当从缓存中访问另一个对象时,对象的流行度也会指数级衰减。
衰减参数的值可以通过 popularityDecay
配置选项指定。这是一个非常敏感的浮点值,可以使用 getPopularityDecay()
方法计算,如下所示
$popularityDecay = \Exteon\IdentityCache\WeakReference\IdentityCache::getPopularityDecay( 1000, // initialPopularity 10000, // rounds 2 // targetPopularity );
以上参数的含义(显示默认值)如下:如果一个对象当前具有initialPopularity
(1000)的受欢迎程度,那么其他对象将从缓存中被访问rounds
(10000)次,初始对象的受欢迎程度将降至targetPopularity
(2)。通过调整popularityDecay
的值,您可以在保留更多曾经受欢迎的对象或更多最近的对象之间取得平衡。
获取对象
映射对象可以被明确地'获取',这意味着即使它们不再被使用(它们不会被清除),映射/缓存也将无限期地保留它们。这需要用于'脏'对象,这些对象已经被修改,并且需要在写后策略场景中稍后持久化。
示例
$cache = new \Exteon\IdentityCache\WeakReference\IdentityCache([ 'trigger' => 'maxRetainedObjects', 'maxRetainedObjects' => 0 ]); $instance = new stdClass(); $cache[1] = $instance; $instance->foo = 'bar'; $cache->acquire(1); // The acquired identity with id 1 will never be purged, even if the cache's // purge strategy is triggered by number of objects or memory trigger unset($instance); $cache->gc(); assert( is_object($cache[1]) && $cache[1]->foo === 'bar' ); // Acquired identities can be released and then they can be purged if no longer // in use: $cache->release(1); $cache->gc(); assert(isset($cache[1]) === false);
显式调用垃圾回收器
垃圾回收器通常在每次向缓存添加一个身份时被调用;然后它将检查是否满足触发条件,如果是的话,它将开始从缓存中清除多余的对象。也可以通过在IdentityCache
上调用gc()
方法来手动调用垃圾回收器。
本地PHP数组替代
如果Weakref
扩展不可用,或者出于测试目的,或者在Weakref / WeakReference实现失败的情况下紧急使用,我们提供了基于常规数组的Weakref IdentityMap
和IdentityCache
的替代方案。
\Exteon\IdentityCache\NativeArray\IdentityMap
\Exteon\IdentityCache\NativeArray\IdentityCache
这些实现了与它们的WeakRef / WeakReference对应相同的接口,因此可以替换它们,但它们不会实现任何引用释放功能:添加的对象将保留在IdentityMap
或IdentityCache
中,直到明确调用unset()
。