以下文章由 Ryan Burnette 客串撰写。正如您将在下面阅读到的那样,Ryan 正在开发一个 WordPress 网站,该网站使用一个插件通过 Instagram API 获取照片。他以一种不太标准的方式使用它,这导致了大量的请求和非常缓慢的网站。在探索不同的解决方案时,他发现了片段缓存。但不幸的是,他发现了一些过时的信息,因此,作为一名优秀的开发者,他对其进行了更新。以下是背景故事和历程。
我们都知道网站性能非常重要。然而,对于构建自定义 WordPress 主题的开发者来说,在实际编写代码时,这在优先级列表中排名很低。渲染页面上元素的代码通常使用可用的函数以最简单、最友好的方式编写。这会导致代码易于创建、阅读和维护。它还会导致元素具有非常低效的渲染过程,其中包含多余的循环和数据库查询。
多出几毫秒真的会累积起来。再加上网站流量的增加,可能会出现严重的性能问题。
许多非常聪明的人已经将他们的智慧应用于这个问题。WordPress 社区已经开发了一些很棒的缓存插件。 W3 Total Cache 就是其中之一。我非常喜欢它们并经常使用它们,但有时我并不需要那么强大的功能。我可能想要避免配置,或者拥有不适合缓存的元素。此外,为了避免将来维护的麻烦,最好将插件数量保持在最低限度。
这促使我采用不同的方法。我想要使用少量代码来缓存页面上一些太笨拙而无法在每次加载时都渲染的元素。
片段缓存
当 WordPress 页面加载时,PHP 会被处理,MySQL 数据库会被查询。有时,一段代码会执行许多查询并需要一段时间才能运行。片段缓存会获取代码块的输出并将其存储,以便在预定的时间内使用。当代码运行时,只要时间限制没有超过,该代码块就会被忽略,并且存储的输出会被返回并打印到页面上。
片段缓存并不是什么新鲜事物。WordPress 核心开发者 Marc Jaquith 曾经写过关于片段缓存的文章。后来,我发现了一个简化了 Jaquith 类为函数的 Gist。我对此进行了分叉并进行了修改。
在 2.5 之前的 WordPress 版本中,WP_Cache 对象可以像 Jaquith 的示例中演示的那样用于持久缓存,或者比一次页面加载时间更长的缓存。 Transients API 可以创建具有方便的过期功能的持久数据库对象。我的片段缓存代码片段使用这种方法来存储片段。
以下几行代码可以包含在 functions.php 文件中,允许将任何输出作为片段缓存。以下是代码。
function fragment_cache($key, $ttl, $function) {
if ( is_user_logged_in() ) {
call_user_func($function);
return;
}
$key = apply_filters('fragment_cache_prefix','fragment_cache_').$key;
$output = get_transient($key);
if ( empty($output) ) {
ob_start();
call_user_func($function);
$output = ob_get_clean();
set_transient($key, $output, $ttl);
}
echo $output;
}
该函数接受三个参数
-
键:一个简单的字符串,用于标识片段。请注意,该函数会添加一个前缀以避免与其他瞬态冲突。您可以通过编辑函数或添加与 'fragmentcacheprefix' 标签匹配的过滤器来更改前缀。
-
生存时间:缓存生存的时间(以秒为单位)。我通常使用 时间常量。例如,DAYINSECONDS 是 86400,表示一天中的秒数。这对于那些懒得进行简单数学运算的人来说非常有用。
-
函数:创建输出的函数。这可以是任何东西,如本帖中的示例所示。
使用示例
使用片段缓存就像将一些 HTML 和 PHP 包含在函数中一样简单。
以下是一些开发者可能在 WordPress 网站或应用程序上编写的代码。
<p>Here's some HTML.</p>
<?php
// Here's some PHP
$args = array(
'post_type' => 'my_data',
'posts_per_page' => -1
);
$posts = get_posts($args);
foreach ( $posts as $p ) {
echo '<pre>';
echo get_post_meta($p,'some_meta',true);
echo '</pre>';
}?>
<p>The PHP in this block runs and executes queries with every page load. :(</p>
以下是使用片段缓存代码片段实现的相同代码。请注意,我们正在使用 HTML 和 PHP,这些会被函数捕获并缓存。
让我们回顾一下该函数的三个参数
- 表示缓存的标签。这里有一个提示。如果此代码因页面而异,请将帖子 ID 连接到标签中,为每个页面创建单独的缓存。如果主循环被片段缓存,这一点非常重要。
- 超时时间。我通常使用 WordPress 时间常量,但可以使用任何以秒为单位的时间。
- 输出代码本身。请注意,它被包含在一个函数中。该函数被传递到片段缓存函数中。没错,您可以在 PHP 中将函数作为参数传递。
<?php
// After
fragment_cache('my_footer', DAY_IN_SECONDS, function() { ?>
<p>Here's some HTML.</p>
<?php
// Here's some PHP
$args = array(
'post_type' => 'my_data',
'posts_per_page' => -1
);
$posts = get_posts($args);
foreach ( $posts as $p ) {
echo '<pre>';
echo get_post_meta($p,'some_meta',true);
echo '</pre>';
}
?>
<p>And everything this block outputs will be fragment cached. :)</p>
<?php }); ?>
示例
以下是一些我使用此函数来避免数据库比实际需要更频繁地渲染元素的示例。
自定义页脚
我实现此函数的最常见位置是自定义页脚。我经常创建一个页脚,其中不仅包含 WordPress 菜单,还包含基于 get_posts() 函数生成的菜单,以及每个迭代帖子的附加 get_post_meta() 函数。我发现很多情况下,渲染一个大的页脚需要 100-200 毫秒。片段缓存使这些元素的加载时间变得无关紧要。
数据表
WordPress 越来越受欢迎,成为了一种应用程序开发平台。现在有很多关于它的热议。无论你是否喜欢,人们都将使用 WordPress 构建应用程序。这通常会导致原本应该是一组数据库对象的情况变成存储在自定义帖子类型的帖子中。每个属性都成为该帖子的元数据的一部分,而不是真实数据库对象的属性。查询和渲染以这种方式存储的数据表需要很长时间。片段缓存它可以解决这个问题。
令人尴尬的冗长循环
有数千个令人尴尬的冗长且复杂的循环。我也写过一些。无论你写了多么低效的代码片段,你都可以将其放入片段缓存中,它将快速加载。
一个测试用例
我是 STUDIOCRIME 的网站管理员,该网站聚合了街头艺术视频。 2019 年 12 月更新:链接已删除,因为该网站已不存在,现在是垃圾邮件网站。
WordPress 为我们的策展人提供了一个很棒的、简单的 CMS,供他们在发布和组织网站的视频内容时使用。视频集页面每次查看时都会加载超过 80 个帖子。每次迭代还会查询数据库以获取帖子元数据。
我们还在侧边栏中显示大量内容,使用一个插件进行身份验证并从 Instagram API 获取数据。该插件并非旨在以我们使用它的方式使用。每个小工具都会单独实例化该插件。这会导致非常长的加载时间。
它确实非常快速且易于构建,但这里和那里的几毫秒加起来,导致页面需要 1500 到 5000 毫秒才能渲染。五秒钟对于等待网页加载来说太久了。
我们选择不使用像 W3 Total Cache 这样的缓存插件,因为关于页面如何加载以及在 PHP 中跟踪用户数据的决定。页面缓存会阻止此 PHP 运行。
这提供了一个绝佳的机会,既可以使用片段缓存,又可以测试缓存加载速度慢的片段所带来的收益。
我使用 Apache Bench 运行了这些测试。Apache Bench 以并发或连续的方式发出一个或多个请求,并报告 Web 服务器提供页面所花费的时间。请注意,在没有缓存的情况下,单个请求的加载时间大约长三倍。如果有多个请求,响应时间会变得很高,达到 3 到 5 秒。对网站的慢速部分进行片段缓存使时间恢复正常,并在更重的负载下为我们提供了所需的性能。
这些测试展示了在 10、100 和 1000 个并发请求的情况下,单个页面的渲染时间。
Apache Bench 测试 | 无缓存 | 有缓存 |
---|---|---|
10 个请求 | 1426 毫秒 | 518 毫秒 |
100 个请求 | 3498 毫秒 | 658 毫秒 |
1000 个请求 | 5116 毫秒 | 895 毫秒 |
祝您缓存愉快!
很棒的文章——我一定会在我的未来构建中使用它,可能还要回到一些旧的项目中去!
早上 8:30 我就学到了新东西。
有没有一种方法可以“清除缓存”,而无需在代码更新/修复后立即应用的情况下编辑函数?
干杯
您需要钩入某些东西来调用 delete_transient() -> http://codex.wordpress.org/Function_Reference/delete_transient
我喜欢设置一个钩子,寻找登录的用户和 URL 上提供的查询字符串。例如 example.com/my-page/?clear-the-fragment-cache
Ryan 几乎已经有了这个功能。如果稍作调整,就可以让它在登录用户加载页面时更新瞬态数据。
要真正清除整个缓存,以便您不必登录并重新加载每个更改的页面,您需要找到并删除所有以
fragment_cache_
或您的前缀开头的瞬态数据。@Julian Mallett:如果您登录后,片段的内容会有所不同,您可能不想为登录用户缓存片段。
可以通过向函数添加另一个参数来轻松解决这个问题,该参数指定是否允许为登录用户缓存。
这就是为什么我在创建 $key 变量的那一行附加了一个过滤器来清除缓存。我在键中附加了一个随机生成的数字,然后在我的网站管理员的管理区域中添加了一个按钮,上面写着“清除缓存”。单击它会重新生成随机生成的数字。旧的瞬态数据会在超时后被清除。删除瞬态数据确实有效,但这需要您跟踪创建的瞬态数据,然后删除这些数据,或者使用其他一些花哨的方法。我只是使用随机数字,因为它既快又简单。
我还添加了几行代码,如果当前用户已登录,则会绕过整个函数。我通常在登录 WordPress 的情况下进行开发。所以,这就是那几行代码存在的原因。如果不需要,可以轻松地删除这些代码。
很棒的文章,我知道我一定会使用它!感谢您提供这些小技巧。
很棒的文章!但请记住……缓存不应该替代优化!在我看来,只有在您完全确定代码、查询和 HTTP 请求已经尽可能精简的情况下,才应该应用缓存。如果这些都做好了,您仍然对整体执行时间不满意,那么您应该应用缓存。否则,这就像把脏衣服藏在毯子下面一样。
Stratos,我理解您的观点,但我认为您不应该在……优化之后应用缓存,我认为在流量非常大的网站上,您应该同时应用两者,并以同样积极的方式应用,在这种情况下,这些额外的毫秒可以节省大量带宽和资源,还要记住,谷歌会根据页面加载速度对页面进行排名,我曾在美国一家最大的杂志出版商的网站上工作,在这些网站上,缩略图的微小变化都会产生影响,因此我们必须尽可能地进行优化和缓存。
很棒的文章,但有一点……get_transient 函数如果瞬态数据已过期或不存在,则返回布尔值 false。
因此应该是
而不是
两者都可以。您甚至可以只使用
另请参阅:PHP 类型比较表
Codeforest 说得对。
想象一下:
set_transient( 'hardlifting', 0, DAY_IN_SECONDS );
。我希望那个
0
也被缓存,这就是为什么需要与false
进行严格比较的原因。您说得太对了。我有 关于这段代码片段的 gist,我已经根据您的建议更新了它。谢谢!
如果
$output === false
那么就对了,如果反过来,您的代码可读性就会很差。
很棒的文章,在使用 W3 Total Cache 等插件时,这仍然相关吗?还是我在比较苹果和忍者?
这完全取决于您的具体情况,但您仍然需要使用 W3 Total Cache 或 WP Super Cache 等插件,因为它们会缓存整个页面,而不仅仅是页面的部分。
我认为是相关的。W3TC 会生成页面的 HTML 版本,当提供 HTML 版本时,您的 PHP 代码不会运行。所以不用担心。
我认为这种片段缓存最适合用在小部件上。
这非常有用。我将来会更加注意使用它。
太棒了,太棒了,太棒了……唯一的问题是……我刚刚才读到它,刚刚完成了 4 个月的项目,用不同的方式/函数来缓存页面片段,这个解决方案可以为我们节省大量时间,感谢您分享它,我爱死 CSS TRICK 社区了,感谢您的存在,让开发人员的生活更加轻松。
如果有人能帮忙,我有一个问题,最好使用“瞬态数据”进行缓存,还是直接使用文件,以便稍后包含?我之所以提到这一点,是因为我们的高流量网站对数据库请求非常疯狂,一旦我们开始将数据缓存到文件,MySQL 的问题就停止了,而瞬态数据仍然使用数据库中的一个表。
绝对的,我认为缓存文件以避免 MySQL 问题,相比使用“瞬态数据”缓存不会有什么优势。
当我使用它来缓存 single.php 页面上 get_posts() 循环的结果时,它会返回页面上主文章的标题和/或特色图片。您知道为什么吗?
发生的事情是,同一块代码根据显示的页面创建不同的内容。在这种情况下,使用片段缓存完全没问题。您只需要一种方法来区分不同的页面。如果我正在处理一个单页,那么有时我会将帖子 ID 附加到片段缓存的键中。以下是一个示例
不,get_posts() 函数不依赖于页面,它在显示的任何页面上都返回相同的结果。还有其他想法吗?
我不明白为什么你要检查
is_user_logged_in()
… 我猜你不想把缓存的块提供给登录用户,因为登录用户可能会有不同的东西?即使对于登录用户,也有很多可以缓存的块,也许这可以作为函数的参数。另外,我一直在想,最好检查我的 IP(开发人员的 IP),不要向我提供缓存的块,因为这会导致疯狂。此外,最好有一个类似于if($_get['recache'])destroyThisChunk($thechunk)
的东西。此外,在save_post时销毁缓存可能也是一个好主意。我非常喜欢这个主意,我将在多个地方实施它。
它检查用户是否已登录,以避免向登录用户显示缓存的内容。我已在此处的代码片段源代码 Gist中删除了它。您关于查找 URL 参数以清除缓存的想法很棒。这将是一个很棒的 fork。
很棒的东西。我在 Amazon Micro EC2 实例上安装的 WP 中遇到过很多问题。缓存是关键。
只需注意诸如缓存雪崩之类的事情,因为如果实际的数据生成过程很慢,则很容易导致问题(尤其是在高流量场景中),您可能需要重新考虑您的方法。
我仍然是 PHP 新手..但我喜欢 WordPress,您在这里写的关于缓存的内容很棒,我以后会非常注意它..另外,我喜欢您的网站评论部分.. 值得模仿.. 非常感谢您的分享。
很棒的文章。这很有帮助。谢谢!
Zack Tollman 也有一篇关于片段缓存的好文章:http://tollmanz.com/partial-page-templating-in-wordpress/
好东西,Ryan,
如果您真的想将 transients API 提升到一个新的水平,我建议您考虑使用 APC 这样的对象缓存后端,以完全绕过选项表数据库调用。
wp-content 中的 object-cache.php 文件是关键。
这非常有帮助。并且 WordPress 片段缓存重新审视给了我一些想法。
我使用 vanan 翻译进行了这种类型的缓存。如果你想了解更多想法,可以查看它。http://www.bengali-transcription.com/About-us.php
这正是我一直在寻找的,谢谢 Ryan。
请问您可以详细说明您是如何使用随机数更新密钥的?我创建了一个“清除缓存”页面,它运行一个函数,但我似乎无法弄清楚如何将它与
fragment_cache()
函数中的密钥关联起来。我像这样将一个函数挂钩到“fragment_cache_prefix”。
这会获取一个之前存储在 WordPress 选项中的数字,并且片段缓存函数中的过滤器会将其添加到用于存储 transient 的密钥中。
在其他地方,您可以将以下函数挂钩到后端的按钮,或者将其挂钩到“save_post”操作,以便在网站管理员保存其内容时清除缓存。
还有其他方法可以实现这一点,但想法是当随机数改变时,任何之前存储的 transient 都会被放弃。它们会过期并随后从选项表中清除,因此无需手动删除它们。
这是示例代码,因此如果您实现它,您需要更改名称以保护无辜者。 ;)
这很有趣。我刚读了这篇文章,它讨论了如何清除 transient 的过期时间,以及过期时间是最大值而不是最小值。对此有何想法?
此外,您提到了 Mark Jaquith 的片段缓存文章,我一直在查看和玩弄它。我想知道您是否可以更多地谈谈片段缓存与使用 transient 的比较。
据我所知:Mark 的方法,它使用 WP_Object_Cache,不会将数据存储在持久缓存中,而 transient 将是持久的。
我最近正在更多地研究这个问题,因为我喜欢使用高级自定义字段来使用任何输入字段来高度自定义我的 WP 网站的管理区域 项目需要。
但是,我有一种感觉,太多的这些自定义字段以及循环和输出其数据的逻辑前端在数据库查询数量方面会变得很昂贵。因此,使用这种缓存来存储输出 ACF 字段数据的代码块似乎会有所帮助。