ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
# 缓存 缓存通过存储数据(一旦几乎不检索)来加速您的应用程序,以备将来使用。 我们将向您展示: 如何使用缓存 如何更改缓存存储 如何正确无效缓存 Nette Framework提供了一个非常直观的API用于缓存操作。 毕竟,你不会期望什么,对吧? ;-)在我们继续第一个例子之前,我们需要考虑地方在哪里存储数据。 我们可以使用数据库,Memcached服务器,或最可用的存储 - 硬盘驱动器: ~~~ // the `temp` directory will be the storage $storage = new Nette\Caching\Storages\FileStorage('temp'); ~~~ Nette \ Caching \ Storages \ FileStorage存储是非常优化的性能,首先,它提供操作的完全原子性。 这意味着什么? 当我们使用缓存时,我们可以确保我们没有读取一个尚未完全写入的文件(由另一个线程),或者该文件被删除“在我们手中”。 因此使用缓存是完全安全的。 有关高速缓存存储部分的更多信息。 对于使用缓存的操作,我们使用Nette \ Caching \ Cache ~~~ use Nette\Caching\Cache; $cache = new Cache($storage); // $storage from the previous example ~~~ 让我们将'$ data'变量的内容保存在'$ key'键下: ~~~ $cache->save($key, $data); ~~~ 这样,我们可以从缓存中读取:(如果缓存中没有这样的项,则返回NULL值) ~~~ $value = $cache->load($key); if ($value === NULL) ... ~~~ 方法load()有第二个参数callable $ fallback,当缓存中没有这样的项时,它被调用。 这个回调通过引用接收数组$ dependencies,您可以使用它来设置过期规则。 ~~~ $value = $cache->load($key, function(& $dependencies) { // some calculation return 15; }); ~~~ 我们可以通过保存NULL或调用remove()方法从缓存中删除项: ~~~ $cache->save($key, NULL); // or $cache->remove($key); ~~~ Web应用程序通常由多个独立部分组成,如果它们都在同一存储(例如同一目录)中缓存数据,迟早会出现名称冲突。 Nette Framework通过将整个存储分割成段来解决这个问题(在使用子目录的FileStorage案例中)。 应用程序的每个部分都使用它自己的具有唯一名称的段,因此不会发生冲突。 该节的名称可以作为第二个参数传递给Cache类构造函数。 (这些部分通常称为缓存命名空间。) ~~~ $cache = new Cache($storage, 'htmlOutput'); ~~~ ## 在模板中缓存 在模板中缓存很容易,只需用{cache} ... {/ cache}包装模板的一部分。 当源模板更改时,缓存自动失效,包括{cache}宏中包含的任何模板。 {cache}块可以嵌套,并且当嵌套块被无效(例如通过标签)时,父块也被无效。 可以定义缓存将要绑定的键(在这种情况下,$ id变量),并设置过期和标签为无效 ~~~ {cache $id, expire => '20 minutes', tags => [tag1, tag2]} ... {/cache} ~~~ 所有参数都是选项,因此您不必指定到期,标签或键。 使用缓存也可以使用“if”条件 - 只有在满足条件时才会缓存内容: ~~~ {cache $id, if => !$form->isSubmitted()} {$form} {/cache} ~~~ 如果我们只从模板中检索数据(按需原则或延迟),缓存将特别有效。 ## 缓存功能结果 缓存函数或方法调用的结果可以使用call()方法实现: ~~~ $name = $cache->call('gethostbyaddr', $ip); ~~~ 因此,gethostbyaddr($ ip)只会被调用一次,下一次只返回缓存中的值。 当然,对于不同的$ ip,不同的结果被缓存。 ## 输出缓存 输出不仅可以缓存在模板中: ~~~ if ($block = $cache->start($key)) [ ... printing some data ... $block->end(); // save the output to the cache } ~~~ 如果输出已经存在于高速缓存中,start()方法将打印它并返回NULL。 否则,它开始缓冲输出并返回$ block对象,我们最终将数据保存到缓存中。 ## 到期和无效 这里有两个问题在缓存中存储数据。 首先,存储空间可能已完全填满,您无法在其中保存更多数据。 并且可能发生的是,先前保存的数据中的一些将随时间变得无效。 因此,Nette Framework提供了一种机制,如何限制数据的有效性以及如何以受控的方式删除它们(使用框架的术语来“使它们无效”)。 使用save()方法的第三个参数保存数据时,设置数据有效性: ~~~ $cache->save($key, $data, [ Cache::EXPIRE => '20 minutes', // accepts also seconds or a timestamp. ]); ~~~ 从代码本身很明显,我们保存了接下来20分钟的数据。 在这段时间之后,缓存将报告在“$ key”键下没有记录(即,将返回NULL)。 事实上,您可以使用任何时间值,这是PHP函数strToTime()或DateTime类的有效值。 如果我们想要在每次阅读时延长有效期,可以这样做: ~~~ $cache->save($key, $data, [ Cache::EXPIRE => '20 minutes', Cache::SLIDING => TRUE, ]); ~~~ 非常方便的是,当特定文件被改变或者几个文件之一时,让数据过期的能力。 这可以用于将由于将这些文件解析到缓存而产生的数据。 为了无故障功能,建议使用绝对路径。 ~~~ $cache->save($key, $data, [ Cache::FILES => 'data.yaml', // an array of files can also be specified ]); ~~~ Cache :: FILES标准,当然可以使用Cache :: EXPIRE等与时间到期结合。 缓存也可以依赖于其他缓存的项目。 这可以在我们将整个HTML页面保存在缓存中和不同的键(其某些片段)时使用。 一旦零件更改,整个页面将失效。 ~~~ $cache->save('page', $html, [ // will expire if frag1 or frag2 expires Cache::ITEMS => ['frag1', 'frag2'], ]); ~~~ 过期可以通过自己的回调来控制: ~~~ function controlExpiration($val) { return $val; } $cache->save($key, $value, [ Cache::CALLBACKS => [['controlExpiration', 1]], ]); ~~~ ## 到期使用标签和优先级 所谓的标签是一个非常有用的无效工具。 我们可以为存储在缓存中的每个项目分配一个标签列表。 例如,假设我们有一个HTML页面,其中包含我们要缓存的文章和注释。 所以我们在保存到缓存时指定标签: ~~~ $cache->save($articleId, $html, [ Cache::TAGS => ["article/$articleId", "comments/$articleId"], ]); ~~~ 现在,让我们转到管理。 这里我们有一个文章编辑的表单。 与将文章保存到数据库一起,我们调用clean()命令,它将通过标签删除缓存的项目: ~~~ $cache->clean([ Cache::TAGS => ["article/$articleId"], ]); ~~~ 在添加新评论(或编辑它们)的地方,不要忘记使适当的标记无效: ~~~ $cache->clean([ Cache::TAGS => ["comments/$articleId"], ]); ~~~ 我们已经实现了什么? HTML缓存将自动失效。 每当有人更改ID为10的文章时,它会强制文章/ 10标记无效,并且缓存中标记的HTML页面被清除。 当某人在文章下面插入新评论时,也会发生同样的情况。 与标签类似,您可以按优先级控制到期日期: ~~~ $cache->save($key, $value, [ Cache::PRIORITY => 50, ]); // all cached items with priority less than or equal to 100 will be removed. $cache->clean([ Cache::PRIORITY => 100, ]); ~~~ ## 缓存存储 除了已经提到的FileStorage之外,Nette Framework还提供了MemcachedStorage,用于将数据存储到Memcached服务器,还提供MemoryStorage用于在请求期间将数据存储在内存中。 ## 存储服务 当然,可以创建自己的存储。 唯一的要求是实现IStorage接口。 我们可以使用依赖注入,所以我们不必在任何地方创建$ storage对象。 Nette框架提供了一种实现IStorage接口的服务。 如果没有在配置中指定具体的实现,默认情况下使用FileStorage,它将数据保存到由bootstrap.php中的$ configurator-> setTempDirectory()指定的目录中。 ~~~ use Nette; class MyPresenter { /** * @inject * @var Nette\Caching\IStorage */ public $storage; public function actionDefault() { $cache = new Cache($this->storage, 'htmlFront'); $cache->save($key, $data); } } ~~~ ## 为测试目的禁用缓存 Nette \ Caching \ IStorage的特殊实现是一个DevNullStorage。 它不保存任何数据。 当我们想要消除缓存的影响时,这通常在测试时很有用。 在config.neon中配置存储: ~~~ services: cacheStorage: class: Nette\Caching\Storages\DevNullStorage ~~~ ## 并发缓存 删除缓存是将新应用程序版本上传到服务器时的常见操作。 然而,在那一刻,服务器变得非常困难,因为它必须构建一个完整的新缓存。 检索一些数据可能相当困难,例如RobotLoader缓存构建。 此外,如果例如30个请求在短时间内来到,则资源消耗甚至更高。 解决方案是修改应用程序行为,使数据只由一个线程创建,而其他线程正在等待。 为此,请将该值指定为回调或使用匿名函数: ~~~ $result = $cache->save($key, function() { return buildData(); // difficult operation }); ~~~ 框架将确保函数的主体将只被一个线程一次调用,而其他线程将等待。 如果线程由于某种原因失败,就找另一个机会。 ## 在整个应用程序中使用缓存 当在服务中使用缓存时,我们面临的决定是将缓存或IStorage对象传递到服务中。 当服务只需要自己的缓存,我们传递IStorage: ~~~ use Nette\Caching; class MyService { /** @var Caching\Cache */ private $cache; public function __construct(Caching\IStorage $storage) { $this->cache = new Caching\Cache($storage, 'my-service'); } } ~~~ 服务从DI容器获取存储: ~~~ services: - MyService ~~~ 另一种情况是,当我们需要更多的相同服务的实例,但是使用分离的缓存时: ~~~ use Nette\Caching; class MyService { /** @var Caching\Cache */ private $cache; public function __construct(Caching\Cache $cache) { $this->cache = $cache; } } ~~~ 我们分别为每个服务创建一个缓存对象: ~~~ services: one: MyService( Nette\Caching\Cache(namespace: 'one') ) two: MyService( Nette\Caching\Cache(namespace: 'two') ) ~~~