bbc/ipr-cache

一个简单的缓存包装器,利用 Doctrine Cache 实现模糊和过时重验证缓存。

v1.1.0 2016-12-02 09:38 UTC

This package is not auto-updated.

Last update: 2024-09-14 19:36:40 UTC


README

围绕 Doctrine\Cache 的简单缓存包装器,允许我们进行标准、模糊和过时重验证缓存。

Build Status Latest Stable Version Total Downloads License

要求

  • PHP >= 5.5
  • Doctrine/Cache 理解的缓存后端

使用方法

入门

通过 Composer 安装

$ composer require bbc/ipr-cache

现在你需要构造一个 BBC\iPlayerRadio\Cache 实例,并将一个 Doctrine\Common\Cache\Cache 实例传递给它。以下是一个简单的示例

$cacheAdapter = new Doctrine\Common\Cache\ArrayCache();
$cache = new BBC\iPlayerRadio\Cache\Cache($cacheAdapter);

$cacheAdapter 是实际进行缓存读写的东西,我们的库只是将其包装起来。因此,我们接受任何 Doctrine\Common\Cache\* 类,所以如果你使用 Redis、Memcached,甚至是普通的文件系统缓存,这个库都将与它一起工作。(更具体地说,任何实现了 Doctrine\Common\Cache\Cache 接口的类都被接受)。

读取项目

这个库在读取缓存项的方式上与您可能习惯的不同。Doctrine 使用的是“传统”方式,工作原理大致如下

if (($item = $cache->fetch($cacheKey) === false) {
    $data = '{somekey: somevalue}';
    $cache->save($cacheKey, $data, $lifetime);
}

这对于简单用例来说非常好,但是如果您想干净地执行模糊和过时重验证等操作,这种 API 风格会很快变得笨拙。

因此,这个库使用存储库模式来读取和写入缓存项。这使得我们能够更干净地封装不同缓存模式的逻辑,即使一开始看起来可能有些奇怪!

以下是读取缓存项的方式

<?php

use BBC\iPlayerRadio\Cache\Cache;
use Doctrine\Common\Cache\ArrayCache;

$cacheAdapter = new ArrayCache();
$cache = new Cache($cacheAdapter);

$cacheItem = $cache->get('cache_key');

// $cacheItem is an instance of BBC\iPlayerRadio\Cache\CacheItem and will be an object whether the item
// is present in cache or not. You can now call functions on this object to ascertain its state:

// Check if the item is expired: true if it isn't in the cache, false if it is
var_dump($cacheItem->isExpired());

// Retrieve the data you stored in the cache from the item:
$cacheData = $cacheItem->getData();

如果您真的想,可以自己构造一个 BBC\iPlayerRadio\Cache\CacheItem 实例,但最简单的方法是直接调用 $cache->get('myCacheKey');,因为它会始终返回一个 CacheItem 实例,无论它是否存在于缓存中。

“模糊”缓存

假设您有一个需要执行五个操作来构建自己的页面部分。如果我们将所有这些操作都缓存相同的时间长度,它们将同时过期,可能会在服务尝试一次性重建所有内容时造成过载。一种缓解方法是对缓存生命周期进行“模糊”;从任何生命周期中添加或减去一个随机数,以确保事物以更分散的方式过期。

这是该库的默认操作模式,所有您的缓存时间都将被“模糊”±5%,以防止工程化踩踏。以下是使用方法

use BBC\iPlayerRadio\Cache\Cache;
use Doctrine\Common\Cache\ArrayCache;

$cache = new Cache(new ArrayCache());

// Attempt to read from the cache:
$item = $cache->get('hello_world');
if ($item->isExpired()) {
    // We don't have an item in the cache, let's rebuild!
    $data = 'This could be the result of an expensive call...';

    // Now we update the item we fetched from the cache with the data
    // and give it a new expiry time (in seconds).
    $item->setData($data);
    $item->setLifetime(60);

    // And re-store in the cache:
    $cache->save($item);
}

// Now we can make use of that data:
echo $item->getData();

如您所见,缓存会自动处理模糊。

默认模糊为5%,但您可以使用 setFuzz() 进行更改

$item = $cache->get('hello_world');
$item->setData('I am data!');
$item->setFuzz(0.1); // 10% fuzz
$cache->save($item);

“纯”缓存

如果您不想模糊生命周期,怎么办?很简单,您可以使用“纯”缓存,这实际上就是关闭模糊

use BBC\iPlayerRadio\Cache\Cache;
use Doctrine\Common\Cache\ArrayCache;

$cache = new Cache(new ArrayCache());

// Attempt to read from the cache:
$item = $cache->get('hello_world');
if ($item->isExpired()) {
    $data = 'This could be the result of an expensive call...';

    $item->setData($data);
    $item->setLifetime(60);

    // This is the only difference from Fuzzy caching, we set the fuzz to 0:
    $item->setFuzz(0);

    $cache->save($item);
}

过时重验证缓存

过时重验证缓存(有时也称为“软”缓存)为对象引入了两个不同的生命周期;它的最佳食用期和它的过期时间。

一旦一个项超过了它的最佳食用期(并变为“过时”),客户端应该尝试重建数据。但是,如果重建失败,它们可以使用过时的数据继续操作。

但是过期日期与其他两种模式的工作方式相同,到那时它将从缓存后端清除,并且您必须重建数据或优雅地降级。

以下是使用过时重验证缓存的方法

use BBC\iPlayerRadio\Cache\Cache;
use Doctrine\Common\Cache\ArrayCache;

$cache = new Cache(new ArrayCache());

$item = $cache->get('hello_world');
if ($item->isStale() || $item->isExpired()) {
    $data = someExpensiveOperation();

    if ($data) {
        // We got a good response, let's cache that:
        $item->setData($data);
        $item->setBestBefore(60); // start re-fetching after 1 minute
        $item->setLifetime(300); // flush from cache at 5 minutes

        $cache->save($item);
    }
}

// At this point, we have to re-examine the cache item to see if the data has been updated. The $item could actually
// be in any state at this point:
//
// - The item was in cache and valid, no refetch happened, you're good to go
// - The item was stale, we re-fetched successfully, you're good to go
// - The item was stale, re-fetch failed, go with the stale data
// - The item was expired, re-fetch failed, you need to do something
//
// Luckily, these four complex states can be handled simply by asking if the item is expired or not:

if ($item->isExpired()) {
    // We have no data to work with:
    gracefullyDegrade();
} else {
    // We have data from somewhere; use it!
    echo $item->getData();
}

过时重验证缓存和模糊

如果您选择通过调用setBestBefore()使用软缓存,模糊处理将应用于最佳使用时间,而不是过期时间。这又是为了防止您的应用开始重新请求数据时出现拥挤。

缓存前缀

新增于v1.1.0

Cache类可以无痕地为所有您提供的缓存键添加一个额外的前缀。当您知道有两个应用将以相似的关键字(例如md5字符串)写入缓存后端时,这很有用。

// You can either pass the prefix into the constructor:
$cache = new Cache($adapter, 'myprefix_');

// Or set it explicitly:
$cache->setPrefix('mycache_');

永远不需要再次使用前缀;读取、写入和删除对象都像没有前缀一样发生,Cache类内部处理所有这些。

$cache = new Cache($adapter, 'myprefix_');
$item = $cache->get('todays_weather'); // actually reads: 'myprefix_todays_weather'

$item->setData('Cloudy');
$cache->save($item);

$cache->delete('yesterdays_weather'); // actually removes 'myprefix_yesterdays_weather'

if ($cache->hasKey('todays_weather')) {
    $item = $cache->get('todays_weather');
    echo 'Today it is: '.$item->getData();
}

接口

在传递缓存作为参数时,请针对CacheInterface进行类型提示,而不是显式地针对Cache。

function doSomethingWithCache(BBC\iPlayerRadio\Cache\CacheInterface $cache) {

};

如果您想自己实现缓存项,自然也有BBC\iPlayerRadio\Cache\CacheItemInterface

模拟缓存

您可以通过使用Doctrine的ArrayCache适配器轻松地在单元测试中拥有一个模拟缓存实例。这正是我们测试Cache类本身的方式;

$mockedCache = new Cache(new ArrayCache());