aol / offload
简化缓存的PHP任务:后台刷新、最后已知良好状态和单写者。
Requires
- php: ^5.4 || ^7.0
Requires (Dev)
- codeclimate/php-test-reporter: dev-master
- paragonie/random_compat: ^2.0
- phpunit/phpunit: ^4.0
- predis/predis: ^1.0
README
简化缓存的PHP任务:后台刷新、最后已知良好状态和单写者。
示例
$offload = new OffloadManager(/* ... */); // Fetch a result and repopulate it if necessary. $data = $offload->fetch('task-key', function () { // Perform a time consuming task... return $data; })->getData();
这将运行后台任务并缓存返回的 $data
到 task-key
下。如果再次请求数据,如果它在缓存中,则立即返回;如果缓存已过时,则进行数据恢复。
这是如何工作的?
Offload 使用两种时间范围来缓存数据:新鲜和过时。这些 TTL 可以使用选项进行控制,请参阅 Offload 选项。
- 新鲜(缓存命中):立即返回数据,无需重新填充。
- 过时(缓存命中,过时):立即返回数据,并排队进行重新填充。
- 无数据(缓存未命中):强制立即运行重新填充,然后将数据缓存并返回。
Offload 使用任务队列来跟踪需要重新填充的过时缓存命中。当调用 $offload->drain()
时,将运行所有任务并重新填充缓存。最好在请求完成后这样做,这样重新填充缓存的开销就不会干扰快速向客户端返回响应。请参阅 清空 Offload 队列。
OffloadManager
可以接受任何实现 OffloadCacheInterface
的缓存存储。
独占任务
默认情况下,任务以 exclusive
方式运行。可以使用选项更改此行为,请参阅 Offload 选项。
独占任务重新填充意味着对于给定的键,将始终只有一个并发过时重新填充。这避免了当缓存项过期时发生的 重新填充缓存的竞争。
OffloadManager
使用锁实现来提供此功能,并且可以接受任何实现 OffloadLockInterface
的锁。
初始化
以下是一个使用 memcached 实例进行缓存和使用 redis 实例进行锁定的示例
// Setup a cache. $cache = new OffloadCacheMemcached($memcached_instance); // Setup a lock. $lock = new OffloadLockRedis($predis_instance); // Default options for offload manager: $default_options = [ 'ttl_fresh' => 5, // Cache time in seconds 'ttl_stale' => 5, // Stale cache time in seconds 'exclusive' => true, // Whether to run tasks exclusively by key (no concurrent repopulates for the same key) 'background' => true, // Whether to run tasks in the background 'background_timeout' => 5, // Timeout for exclusive background repopulates in seconds ]; // Create the offload manager. $offload = new OffloadManager($cache, $lock, $default_options);
清空 Offload 队列
为了正确地清空 Offload 队列,最好是设置 PHP 关闭处理程序。这确保 Offload 任务将在请求结束时始终运行。示例
// ... register_shutdown_function(function () use ($offload) { if ($offload->hasWork()) { // Flush all buffers. while (ob_get_level()) { ob_end_flush(); } flush(); // End the request if possible (under PHP FPM). if (function_exists('fastcgi_finish_request')) { fastcgi_finish_request(); } // Run all tasks in the queue. $offload->drain(); } });
延迟任务
Offload 支持从重新填充可调用的返回延迟任务。这允许在清空 Offload 队列时并行运行多个任务。
例如,使用 Guzzle 异步请求
$data = $offload->fetch('task-key', function () { // ... $promise = $guzzle_client->getAsync('http://www.example.com'); return new OffloadDeferred([$promise, 'wait']); })->getData();
OffloadDeferred
类接受一个将等待结果的单一可调用。在上面的示例中,$promise->wait()
等待 HTTP 请求完成并返回结果。
重新填充可调用可以返回任何实现 OffloadDeferredInterface
的类,因此您可以创建用于自定义异步处理的适配器。
API
OffloadManager
实现 OffloadManagerInterface
并公开以下方法
OffloadManager |
|
---|---|
fetch(...) |
从缓存中获取数据并在必要时进行重新填充。 |
fetchCached(...) |
与带有特定新鲜缓存TTL的fetch(...) 相同。 |
queue(...) |
将任务排队运行。 |
queueCached(...) |
与queue(...) 相同,但带有特定的新鲜缓存TTL。 |
hasWork() |
是否离载管理器有工作。 |
drain() |
排空离载管理器任务队列。 |
getCache() |
用于手动与缓存交互的对象。 |
有关上述方法的更多详细信息,请参阅下文。
有关可以提供的$options
的更多信息,请参阅离载选项。有关返回的OffloadResult
详细信息的更多信息,请参阅离载结果。
获取数据
fetch
fetch($key, callable $repopulate, array $options = []): OffloadResult
检查缓存中给定$key
的数据。如果数据在缓存中,则立即返回它。如果数据已过时,则安排在离载管理器排空时运行重新填充。
$result = $offload->fetch($key, function () { // Perform long running task... return $data; });
fetchCached
fetchCached($key, $ttl_fresh, callable $repopulate, array $options = []): OffloadResult
与fetch
相同。以下内容等效
$result = $offload->fetch($key, $repopulate, ['ttl_fresh' => 5]); // is the same as: $result = $offload->fetchCached($key, 5, $repopulate);
排队任务
queue
queue($key, callable $repopulate, array $options = []): void
将重新填充任务排队运行。不检查缓存。接受与fetch
相似的选项。
queueCached
queueCached($key, $ttl_fresh, callable $repopulate, array $options = []): void
与queue
相同。以下内容等效
$result = $offload->queue($key, $repopulate, ['ttl_fresh' => 5]); // is the same as: $result = $offload->queueCached($key, 5, $repopulate);
Fetch/Queue 参数
选项 | 类型 | |
---|---|---|
$key |
string |
要存储的数据的键。 |
$ttl_fresh |
float |
缓存数据的刷新TTL(以秒为单位)。这仅提供给fetchCached 和queueCached 。 |
$repopulate |
callable |
一个可调用的函数,返回用于重新填充缓存的数据。 |
$options |
array |
离载选项(见离载选项)。 |
将结果标记为“坏”
有时从重新填充返回的结果状态不佳,不应缓存。
离载管理器为重新填充的可调用函数提供了一个OffloadRun
实例,可用于将结果标记为坏,例如
$offload->fetch($key, function (OffloadRun $run) { // Get some data from a service... $object = $this->service->get($arguments); if (!$object->isValid()) { // If the data returned is not valid, mark the result as bad. // This will tell the offload manager *not to cache* the data. $run->setBad(); } });
离载选项
离载选项以数组形式提供,例如
$options = [ 'ttl_fresh' => 5, 'ttl_stale' => 10, 'background' => true, // ... ]; $result = $offload->fetch($key, function () { /* ... */ }, $options);
选项 | |
---|---|
ttl_fresh |
将数据视为新鲜并缓存的时间(以秒为单位)。新鲜数据不会重新填充。默认值为0 。 |
ttl_stale |
将数据视为陈旧并缓存的时间(以秒为单位)。当获取时,陈旧数据将被重新填充。此值是添加到ttl_fresh 的,以获得总缓存时间。默认值为5 。 |
exclusive |
是否单独运行任务(同一键的其他任务不能同时运行)。默认值为true 。 |
background |
是否在后台运行任务。这意味着它将等待离载管理器排空,而不是立即重新填充。默认值为true 。 |
background_timeout |
以秒为单位超时独占后台任务的时长。默认值为5 。 |
background_release_lock |
是否在卸载的任务完成时立即释放重新填充锁。默认为 true 。如果设置为 false ,卸载将等待后台超时完成后再允许新的重新填充。 |
卸载结果
OffloadResult
类提供了以下方法
OffloadResult |
|
---|---|
getData() |
重新填充可调用函数返回的数据。 |
isFromCache() |
数据是否来自缓存。 |
getExpireTime() |
缓存中的数据过期时间(Unix时间秒)。 |
getStaleTime() |
数据在秒内过期的时长。如果值为负数,则表示数据距离过期还有多长时间。 |
isStale() |
结果是否来自缓存,但已过期。 |
编码器
默认情况下,卸载将使用 OffloadEncoderStandard
(它执行简单的PHP序列化)来编码和解码存储在缓存中的数据。您可以通过实现 OffloadEncoderInterface
并在 OffloadManagerCache
实例上设置编码器来更改此设置。
class CustomEncoder implements OffloadEncoderInterface { // ... public function encode($object) { // ... Encode the value ... return $string_value; } public function decode($string) { // ... Decode the value ... return $object_value; } } // ... $offload = new OffloadManager(/* ... */); // Change the encoder. $offload->getCache()->setEncoder(new CustomEncoder(/* ... */));
默认情况下,卸载将使用您设置的编码器来 解码。您可以通过调用 setDecoder
来更改解码以使用单独的实例。
$offload->getCache()->setEncoder(new FooEncoder(/* ... */)); $offload->getCache()->setDecoder(new BarEncoder(/* ... */));
加密
Offload附带了一个利用AES-256加密的加密编码器。它包装了任何实现了 OffloadEncoderInterface
的其他编码器。要使用它,只需将其设置为卸载缓存的编码器即可。
// Get the base encoder. $base_encoder = $offload->getCache()->getEncoder(); // Wrap it with an encrypting encoder. $encrypting_encoder = new OffloadEncoderEncryptedAes256( $base_encoder, // The key ID for the encryption. 'foo', // Secret keys by ID. This enables key cycling. [ 'foo' => 'my_secret_key' ] ) $offload->getCache()->setEncoder($encrypting_encoder);
自定义加密
您可以通过扩展抽象类 OffloadEncoderEncrypted
来实现自定义加密。
class CustomEncryptionEncoder extends OffloadEncoderEncrypted { // ... protected function encrypt($string, $key) { // ... return encrypted string .. } protected function decrypt($string, $key) { // ... return decrypted string .. } }