matthiasmullie/scrapbook

Scrapbook 是一个 PHP 缓存库,具有对 Memcached、Redis、Couchbase、APCu、SQL 等的适配器,并在其基础上构建了额外的功能(例如事务、Stampede 保护)。

资助包维护!
matthiasmullie

安装数: 1,450,080

依赖者: 32

建议者: 3

安全: 0

星标: 315

关注者: 12

分支: 28

开放问题: 0

1.5.3 2024-04-02 13:33 UTC

README

Scrapbook PHP cache

Build status Code coverage Latest version Downloads total License

文档: https://www.scrapbook.cash - API 参考: https://docs.scrapbook.cash

目录

安装与使用

如果您使用 Composer 管理项目的依赖关系,只需在 composer.json 文件中添加对 matthiasmullie/scrapbook 的依赖即可。

composer require matthiasmullie/scrapbook

具体的引导过程将取决于您要使用的适配器、功能以及接口,所有这些都在下面有详细说明。

此库采用分层构建,所有层都是 KeyValueStore 实现,您可以相互包裹以添加更多功能。

以下是一个简单的例子:带有 Stampede 保护的基于 Memcached 的 psr/cache。

// create \Memcached object pointing to your Memcached server
$client = new \Memcached();
$client->addServer('localhost', 11211);
// create Scrapbook KeyValueStore object
$cache = new \MatthiasMullie\Scrapbook\Adapters\Memcached($client);

// create stampede protector layer over our real cache
$cache = new \MatthiasMullie\Scrapbook\Scale\StampedeProtector($cache);

// create Pool (psr/cache) object from cache engine
$pool = new \MatthiasMullie\Scrapbook\Psr6\Pool($cache);

// get item from Pool
$item = $pool->getItem('key');

// get item value
$value = $item->get();

// ... or change the value & store it to cache
$item->set('updated-value');
$pool->save($item);

只需查看 "构建您的缓存" 部分,以生成您想使用的确切配置(适配器、接口、功能)和一些示例代码。

适配器

Memcached

Memcached 是一种内存中的键值存储,用于存储来自数据库调用、API 调用或页面渲染的结果中的小块任意数据(字符串、对象)。

使用 PECL Memcached 扩展 与 Memcached 服务器进行接口。只需向 Memcached 适配器提供有效的 \Memcached 对象即可。

// create \Memcached object pointing to your Memcached server
$client = new \Memcached();
$client->addServer('localhost', 11211);
// create Scrapbook KeyValueStore object
$cache = new \MatthiasMullie\Scrapbook\Adapters\Memcached($client);

Redis

Redis 通常被称为数据结构服务器,因为键可以包含字符串、散列、列表、集合、有序集合、位图和 hyperloglogs。

使用 PECL Redis 扩展 与 Redis 服务器进行接口。只需向 Redis 适配器提供有效的 \Redis 对象即可。

// create \Redis object pointing to your Redis server
$client = new \Redis();
$client->connect('127.0.0.1');
// create Scrapbook KeyValueStore object
$cache = new \MatthiasMullie\Scrapbook\Adapters\Redis($client);

Couchbase

专为满足关键任务应用程序的弹性可伸缩性、一致的高性能、始终在线可用性和数据移动性要求而设计。

使用 PECL Couchbase 扩展couchbase/couchbase 包 与 Couchbase 服务器进行接口。只需向 Couchbase 适配器提供有效的 \Couchbase\Collection\Couchbase\Management\BucketManager\Couchbase\Bucket 对象即可。

// create \Couchbase\Bucket object pointing to your Couchbase server
$options = new \Couchbase\ClusterOptions();
$options->credentials('username', 'password');
$cluster = new \Couchbase\Cluster('couchbase://localhost', $options);
$bucket = $cluster->bucket('default');
$collection = $bucket->defaultCollection();
$bucketManager = $cluster->buckets();
// create Scrapbook KeyValueStore object
$cache = new \MatthiasMullie\Scrapbook\Adapters\Couchbase($collection, $bucketManager, $bucket);

APCu

APC 是一个免费的开源 opcode 缓存,用于 PHP。其目标是提供一个免费、开源且健壮的框架,用于缓存和优化 PHP 中间代码。

使用 APC,没有“缓存服务器”,数据仅在执行机器上缓存,并可供该机器上所有 PHP 进程使用。可以使用 PECL APCu 扩展。

// create Scrapbook KeyValueStore object
$cache = new \MatthiasMullie\Scrapbook\Adapters\Apc();

MySQL

MySQL 是全球最受欢迎的开源数据库。MySQL 可以以经济高效的方式帮助您提供高性能、可伸缩的数据库应用程序。

虽然数据库不是真正的缓存,但它也可以作为键值存储使用。只是别期望得到与专用缓存服务器相同类型的性能。

但使用基于数据库的缓存可能有很好的理由:如果你已经使用了数据库,它会很方便,而且可能还有其他好处(如持久存储、复制)。

// create \PDO object pointing to your MySQL server
$client = new PDO('mysql:dbname=cache;host=127.0.0.1', 'root', '');
// create Scrapbook KeyValueStore object
$cache = new \MatthiasMullie\Scrapbook\Adapters\MySQL($client);

PostgreSQL

PostgreSQL拥有经过验证的架构,因此它因可靠性、数据完整性和正确性而享有良好的声誉。

虽然数据库不是真正的缓存,但它也可以作为键值存储使用。只是别期望得到与专用缓存服务器相同类型的性能。

但使用基于数据库的缓存可能有很好的理由:如果你已经使用了数据库,它会很方便,而且可能还有其他好处(如持久存储、复制)。

// create \PDO object pointing to your PostgreSQL server
$client = new PDO('pgsql:user=postgres dbname=cache password=');
// create Scrapbook KeyValueStore object
$cache = new \MatthiasMullie\Scrapbook\Adapters\PostgreSQL($client);

SQLite

SQLite是一个软件库,实现了自包含、无服务器、零配置、事务性的SQL数据库引擎。

虽然数据库不是真正的缓存,但它也可以作为键值存储使用。只是别期望得到与专用缓存服务器相同类型的性能。

// create \PDO object pointing to your SQLite server
$client = new PDO('sqlite:cache.db');
// create Scrapbook KeyValueStore object
$cache = new \MatthiasMullie\Scrapbook\Adapters\SQLite($client);

文件系统

虽然它在I/O访问时间方面不是最快的缓存类型,但打开文件通常仍然比重新执行昂贵的计算要好。

基于文件系统的适配器使用league\flysystem来抽象文件操作,并将与league\filesystem提供的所有类型的存储一起工作。

// create Flysystem object
$adapter = new \League\Flysystem\Local\LocalFilesystemAdapter('/path/to/cache', null, LOCK_EX);
$filesystem = new \League\Flysystem\Filesystem($adapter);
// create Scrapbook KeyValueStore object
$cache = new \MatthiasMullie\Scrapbook\Adapters\Flysystem($filesystem);

内存

PHP也可以在内存中保存数据!PHP数组作为存储特别有用,因为你可以运行测试而无需安装任何其他服务。

将值存储在内存中通常是没用的:它们将在请求结束时消失。除非你在同一个请求中多次使用这些缓存的值,否则缓存总是空的,几乎没有理由使用这种缓存。

然而,在单元测试中非常有用:你可以在这个基于内存的存储上运行整个测试套件,而无需设置缓存服务并确保它们处于原始状态...

// create Scrapbook KeyValueStore object
$cache = new \MatthiasMullie\Scrapbook\Adapters\MemoryStore();

功能

除了默认的缓存功能(如get & set),Scrapbook还提供了一些实用的小功能。这些都是通过实现KeyValueStore的独立小对象实现的,并围绕KeyValueStore进行包装。换句话说,任何功能都可以简单地包装在另一个功能内部或任何适配器之上。

本地缓冲区

BufferedStore有助于减少对真实缓存的请求。如果你需要多次请求相同的值(从代码的各个地方),保持该值可能会很痛苦。再次从缓存中请求它会更简单,但这样你会从缓存服务器连接中获得一些延迟。

BufferedStore会将已知值(你已请求或自行编写的项目)保存在内存中。每次你在同一个请求中需要该值时,它都会从内存中获取,而不是去缓存服务器。

只需将BufferedStore层包装在你的适配器(或其他功能)周围

// create buffered cache layer over our real cache
$cache = new \MatthiasMullie\Scrapbook\Buffered\BufferedStore($cache);

事务

TransactionStore使写入可以在稍后的时间点延迟。类似于数据库中的事务,所有延迟的写入可以一次性回滚或提交,以确保存储的数据是可靠和完整的。所有这些都将被存储,或者什么都不存储。

你可能想在代码库中处理代码,但直到一切成功验证并写入永久存储之前,不提交任何更改。

在事务内部,你不必担心数据一致性。在事务内部,即使它尚未提交,你也会始终得到你打算存储的值。换句话说,当你将新值写入缓存但尚未提交时,你查询它时仍然会得到那个值。如果你回滚或无法提交(因为另一个进程存储的数据导致你的提交失败),那么你会从缓存中获得原始值而不是你打算提交的值。

只需将TransactionStore层包装在你的适配器(或其他功能)周围

// create transactional cache layer over our real cache
$cache = new \MatthiasMullie\Scrapbook\Buffered\TransactionalStore($cache);

现在,你可以使用事务了!

// begin a transaction
$cache->begin();

// set a value
// it won't be stored in real cache until commit() is called
$cache->set('key', 'value'); // returns true

// get a value
// it won't get it from the real cache (where it is not yet set), it'll be read
// from PHP memory
$cache->get('key'); // returns 'value'

// now commit write operations, this will effectively propagate the update to
// 'key' to the real cache
$cache->commit();

// ... or rollback, to discard uncommitted changes!
$cache->rollback();

Stampede 保护

缓存雪崩发生在有很多请求针对当前不在缓存中的数据时,导致大量并发复杂操作。例如

  • 缓存过期,而该数据通常承受着非常高的负载
  • 对可能不在缓存中的某些东西的突然意外高负载

在这些情况下,当时缓存中没有的数据的大量请求会导致昂贵的操作被频繁执行,全部同时进行。

StampedeProtector就是为此设计的。如果一个值在缓存中找不到,将会存储一个占位符来表示已请求但不存在。在短时间内,后续的每次请求都会找到这个指示,知道另一个进程已经在生成该结果,因此这些请求将等待结果可用,而不是压垮服务器。

只需将StampedeProtector层包裹在你的适配器(或其他功能)周围。

// create stampede protector layer over our real cache
$cache = new \MatthiasMullie\Scrapbook\Scale\StampedeProtector($cache);

分片

当你有太多数据(或请求)用于1个小小的服务器时,这将允许你将其分散到多个缓存服务器上。所有数据将自动均匀分布到你的服务器池中,因此每个单独的缓存服务器只会获得一小部分数据和流量。

将组成缓存服务器池的单独KeyValueStore对象传递给这个构造函数,数据将根据传递给此构造函数的缓存服务器的顺序在这些对象上分散(因此请确保始终保持相同的顺序)。

分散是均匀的,所有缓存服务器将大致接收相同数量的缓存键。如果某些服务器比其他服务器大,您可以通过多次添加该缓存服务器的KeyValueStore对象来抵消这一点。

数据甚至可以分散在不同的适配器之间:碎片池中的一个服务器可以是Redis,另一个可以是Memcached。不清楚你为什么想这么做,但你可以这么做!

只需将Shard层包裹在你的适配器(或其他功能)周围。

// boilerplate code example with Redis, but any
// MatthiasMullie\Scrapbook\KeyValueStore adapter will work
$client = new \Redis();
$client->connect('192.168.1.100');
$cache1 = new \MatthiasMullie\Scrapbook\Adapters\Redis($client);

// a second Redis server...
$client2 = new \Redis();
$client2->connect('192.168.1.101');
$cache2 = new \MatthiasMullie\Scrapbook\Adapters\Redis($client);

// create shard layer over our real caches
// now $cache will automatically distribute the data across both servers
$cache = new \MatthiasMullie\Scrapbook\Scale\Shard($cache1, $cache2);

接口

Scrapbook支持3种不同的接口。有Scrapbook特定的KeyValueStore,然后还有PHP FIG提出的2个PSR接口。

KeyValueStore

KeyValueStore是这个项目的基石。它提供的是提供最多缓存操作的接口:get、getMulti、set、setMulti、delete、deleteMulti、add、replace、cas、increment、decrement、touch和flush。

如果你之前使用过Memcached,KeyValueStore看起来非常相似,因为它是受该API启发的/仿照的。

所有适配器和功能都实现了这个接口。如果你有复杂的缓存需求(如能够使用cas),你应该坚持使用这个接口。

KeyValueStore接口及其方法的详细列表可以在文档中找到。

psr/cache

PSR-6(一个PHP-FIG标准)与KeyValueStore和psr/simple-cache相比是一个截然不同的缓存模型:psr/cache基本上是在值对象(Item)上操作以执行更改,然后将更改反馈到缓存(Pool)。

它不允许你执行太多操作。如果get、set、delete(及其多对)和delete是您所需的所有操作,那么您可能更倾向于使用此接口(或下面提到的psr/simple-cache),因为这个接口也受到其他缓存库的支持。

您可以通过将其包裹在任意KeyValueStore对象周围轻松使用psr/cache。

// create Pool object from Scrapbook KeyValueStore object
$pool = new \MatthiasMullie\Scrapbook\Psr6\Pool($cache);

// get item from Pool
$item = $pool->getItem('key');

// get item value
$value = $item->get();

// ... or change the value & store it to cache
$item->set('updated-value');
$pool->save($item);

PSR-6接口及其方法的详细列表可以在文档中找到。

psr/simple-cache

PSR-16(一个PHP-FIG标准)是第二个PHP-FIG缓存标准。它是一个类似于KeyValueStore的驱动模型,并且工作方式非常相似。

它不允许你执行太多操作。如果只需要getsetdelete(及其多态对应)以及delete,那么可能更合适使用这个(或psr/cache,见上文),因为这个接口也被其他缓存库所支持。

你可以通过将任何KeyValueStore对象包装起来轻松地使用psr/simple-cache。

// create Simplecache object from Scrapbook KeyValueStore object
$simplecache = new \MatthiasMullie\Scrapbook\Psr16\SimpleCache($cache);

// get value from cache
$value = $simplecache->get('key');

// ... or store a new value to cache
$simplecache->set('key', 'updated-value');

PSR-16接口及其方法详情可参见文档

集合

集合,或者如果你愿意,是隔离的缓存子集,将只提供对该上下文中值的访问。

无法在集合间设置/获取数据。在两个不同的集合中设置相同的键将存储两个不同的值,这些值只能从各自的集合中检索。

清除集合将只清除那些特定的键,而不会影响其他集合中的键。

然而,清除服务器将清除一切,包括该服务器上任何集合中的数据。

这里有一个简单的例子

// let's create a Memcached cache object
$client = new \Memcached();
$client->addServer('localhost', 11211);
$cache = new \MatthiasMullie\Scrapbook\Adapters\Memcached($client);

$articleCache = $cache->collection('articles');
$sessionCache = $cache->collection('sessions');

// all of these are different keys
$cache->set('key', 'value one');
$articleCache->set('key', 'value two');
$sessionCache->set('key', 'value three');

// this clears our the entire 'articles' subset (thus removing 'value two'),
// while leaving everything else untouched
$articleCache->flush();

// this removes everything from the server, including all of its subsets
// ('value one' and 'value three' will also be deleted)
$cache->flush();

getCollection在所有KeyValueStore实现上都是可用的(因此对于可能围绕它包装的每个适配器和功能)并且返回一个KeyValueStore对象。虽然它不是PSR接口的一部分,但你可以在创建集合之后,然后将所有的集合都包裹在其自己的psr/cache或psr/simple-cache表示中,如下所示

$articleCache = $cache->collection('articles');
$sessionCache = $cache->collection('sessions');

// create Pool objects from both KeyValueStore collections
$articlePool = new \MatthiasMullie\Scrapbook\Psr6\Pool($articleCache);
$sessionPool = new \MatthiasMullie\Scrapbook\Psr6\Pool($sessionCache);

兼容性

Scrapbook支持PHP 8.0版本到当前版本。Scrapbook将在这些版本和平台之间缓解缓存后端的实现差异,以确保统一的行为。

可以使用提供的PHP 8.0及更高版本的docker-compose配置来确认与所有这些版本和平台的兼容性。

与旧软件版本的兼容性不会轻易被破坏。除非有充分的理由,例如安全或性能影响,或与其他软件的兼容性,仅语法糖本身并不是破坏兼容性的理由。

许可证

Scrapbook采用MIT许可。