thamtech / yii2-refresh-ahead-cache
Yii2的预刷新缓存策略
Requires
- php: >=5.6.0
- thamtech/yii2-di: ~0.1
- yiisoft/yii2: >=2.0.14 <2.1
Requires (Dev)
- php: >=7.2
- phpunit/phpunit: ^8.1
- yiisoft/yii2-queue: ~2.3.0
Suggests
- yiisoft/yii2-queue: Required in order to use the QueueGenerator
README
Yii2预刷新缓存可以装饰Yii2缓存组件或其他组件以实现预刷新缓存策略。
预刷新缓存策略(也称为预读取)用于在数据过期之前刷新缓存数据。通过在数据过期之前(以异步方式进行)刷新缓存数据,最终用户永远不需要忍受刷新的延迟。此外,它还可以帮助避免缓存踩踏。
有关许可证信息,请参阅LICENSE文件。
安装
安装此扩展的首选方式是通过composer。
php composer.phar require --prefer-dist thamtech/yii2-read-ahead-cache
或者添加
"thamtech/yii2-read-ahead-cache": "*"
到您的composer.json
文件的require
部分。
使用方法
装饰缓存组件
您可以通过附加RefreshAheadCacheBehavior
来将预刷新功能添加到应用程序的缓存组件。例如,在您的应用程序配置中
<?php [ 'components' => [ 'cache' => [ 'class' => 'yii\redis\Cache', 'as refreshAhead' => 'thamtech\caching\refreshahead\RefreshAheadCacheBehavior', ], ], ];
如果将行为声明为配置数组,您可以选择配置以下参数
<?php [ 'components' => [ 'redisCache' => [ 'class' => 'yii\redis\Cache', ], 'appMutex' => [ 'class' => 'yii\redis\Mutex', ], 'cache' => [ 'class' => 'yii\caching\FileCache', 'as refreshAhead' => [ 'class' => 'thamtech\caching\refreshahead\RefreshAheadCacheBehavior', 'refreshTimeoutCache' => 'redisCache', 'refreshAheadFactor' => 0.5, 'refreshTimeoutKeySuffix' => 'refresh-ahead-timeout', 'mutex' => 'appMutex', ], ], ], ];
装饰任何组件
默认情况下,RefreshAheadCacheBehavior
假设其所有者(作为行为附加到的组件)是一个缓存组件,它将用于存储缓存数据以及存储刷新超时密钥。但是,在这种情况下,您可以指定要使用的缓存组件,因此您不需要将RefreshAheadCacheBehavior附加到缓存组件。您可以将它附加到任何Yii 组件,前提是您在行为配置中指定要使用的缓存组件。
<?php $dataManager = Yii::createObject([ 'class' => DataManager::class, 'as refreshAhead' => [ 'class' => 'thamtech\caching\refreshahead\RefreshAheadCacheBehavior', // use application 'cache' component for data storage 'dataCache' => 'cache', // use application 'cache' component for refresh timeout key storage 'refreshTimeoutCache' => 'cache', ], ]);
如果数据值和刷新超时键将存储在同一个缓存组件中,则可以将单个cache
属性设置为设置dataCache
和refreshTimeoutCache
的快捷方式。以下配置与上述配置等价
<?php $dataManager = Yii::createObject([ 'class' => DataManager::class, 'as refreshAhead' => [ 'class' => 'thamtech\caching\refreshahead\RefreshAheadCacheBehavior', // use application 'cache' component for both data storage and refresh // timeout key storage 'cache' => 'cache', ], ]);
getOrSet的嵌入式替换
RefreshAheadCacheBehavior向缓存或它装饰的任何其他组件添加了一个getRefreshOrSet()
方法。该方法具有与getOrSet()相同的签名,因此您可以在当前使用getOrSet()
的地方进行嵌入式替换。例如,
<?php $data = $cache->getOrSet($key, function ($cache) { return $this->calculateSomething(); }); // drop-in replacement: $data = $cache->getRefreshOrSet($key, function ($cache) { return $this->calculateSomething(); }); // with specified $duration and $dependency $data = $cache->getOrSet($key, function ($cache) { return $this->calculateSomething(); }, $duration, $dependency); // drop-in replacement $data = $cache->getRefreshOrSet($key, function ($cache) { return $this->calculateSomething(); }, $duration, $dependency);
预刷新策略尝试在刷新超时缓存组件中添加一个刷新超时键,其持续时间短于请求的$duration
(默认为$duration
的一半)。如果添加成功,则意味着任何以前的刷新超时键已过期,缓存数据需要刷新。
当使用单个可调用参数调用getRefreshOrSet()
时,预刷新策略调用该可调用项,使用指定的$duration
将返回值存储到数据缓存组件中,并返回该值(在上面的示例中为$data
变量)。
另一方面,如果添加刷新超时键尝试失败,这意味着该键已经存在且未过期,因此目前不需要刷新。预刷新策略使用$key
在数据缓存组件中查找缓存的值,如果找到则返回它。如果没有在数据缓存中找到(可能是缓存已刷新或键已被移除),则调用可调用项来计算新值。使用指定的$duration
在新值设置在数据缓存组件中,并返回该值。
典型用法
如果你能支持异步刷新,则可以使用法进一步改进。为了做到这一点,我们必须向预刷新策略提供两个可调用项:一个用于异步触发刷新,另一个用于同步刷新数据和返回结果。
用法与上述示例类似,但getRefreshOrSet()
的第二个参数将是一个GeneratorInterface
对象或配置数组,而不是单个可调用项。例如,
<?php $data = $cache->getRefreshOrSet($key, [ // called by Refresh Ahead strategy if the data is still cached, but it is // time to refresh it 'refresh' => function ($cache) { // queue the refresh task to be run at a later time // return `true` when task is queued, `false` if the task was not queued // (in which case the `refresh` callable will be called again in a // subsequent request) return $this->taskQueue->append('calculateSomething'); }, // called by Refresh Ahead strategy if the data is not in cache (it may // have expired before it could be refreshed, or it could have been // flushed or evicted, etc.) 'generate' => function ($cache) { return $this->calculateSomething(); } ], $duration, $dependency);
如果你在RefreshAheadCacheBehavior
中配置了一个mutex
组件,你可以使用mutexLockTimeout
属性指定获取锁的超时时间
<?php $generator = [ 'refresh' => function ($cache) { return $this->taskQueue->append('calculateSomething'); }, 'generate' => function ($cache) { return $this->calculateSomething(); }, // Attempt to acquire a mutex lock for 12 seconds before invoking // the 'generate' callback. If the lock is acquired, Refresh Ahead will // check to see if the value is in the data cache once more before invoking // 'generate', in case another process was generating and caching the value // already. 'mutexLockTimeout' => 12, ]; $data = $cache->getRefreshOrSet($key, $generator, $duration, $dependency);
通过在行为上配置一个mutex
组件并将mutexLockTimeout
作为生成器上的属性设置,预刷新策略将尝试获取锁以调用generate
可调用项。这样,如果多个请求在值已过期时几乎同时到达(一个缓存竞争),第一个获取锁的过程将计算值并将其存储在缓存中。其他进程等待锁释放。一旦第一个过程释放了锁,值已被计算并存储在缓存中,因此其他进程将在缓存中检查它,找到它,并返回它而无需调用generate
可调用项。
如果你的任务队列可以异步运行,例如在cron任务中,你可以在generateAndSet()
的调用中使用相同的$generator
来完成刷新过程并在后台更新缓存值。例如,
<?php // using the same parameters defined in the previous example: $data = $cache->generateAndSet($key, $generator, $duration, $dependency);
这将调用generate
可调用项(如果项目尚未由同一时间内的generate
的另一个调用缓存),并在返回之前将结果设置在缓存中。