在这篇文章中,我们将深入探讨 WordPress 中可用的重要缓存类型:瞬时数据。与任何缓存一样,您可以使用瞬时数据存储获取时间较长的任何数据,因此下次需要它时,它将以超快的速度返回。瞬时数据的优点是,只要您注意这里描述的陷阱,它们就会自动清理自己!
网络已经变得非常“API 化”。我的意思是,几乎所有主要的网站都在向其他网站推送和拉取内容。考虑
- 几乎大多数 ESPN.com 实际上都是 Twitter。
- Programmable Web 现在跟踪超过 13,000 个 API。
- 几个月来,围绕 WordPress 的一项最大举措就是 WP-API。
在我自己的工作中,我看到客户和雇主的期望不断增长,包括希望——不,是假设——我的技能组合包括 API 集成。将电子邮件注册表单粘贴为<iframe>
不够。相反,我的机构需要将它们抽象到 [短代码] 中以方便维护。将产品滑块通过一些第三方 JavaScript 粘贴进去是不够的,设计师期望对动画有精确的控制。让开发人员登录 CDN 以查看域是否处于开发模式太耗时,因此我们调用 CDN 并将状态显示在仪表板中。
WordPress 为我们提供了一些用于 API 集成的出色工具。其中之一就是 瞬时数据 API。这是我最喜欢的 WordPress 附带的功能之一,我乐于与您分享一些技巧。如果您不熟悉瞬时数据,请继续阅读。如果您已经了解该概念并希望直接跳到要点,可以随时 这样做。
还有一点:我将假设您熟悉从 WordPress 插件发出 HTTP 请求,因为详细介绍这一点超出了本文的范围。我的示例将全部使用 WordPress HTTP API 发出请求。
瞬时数据与 API 集成有什么关系?
无论何时调用远程服务器,都会有一些额外的延迟,有时延迟相当长。瞬时数据允许您缓存从远程 API 获取的响应,将其存储在 WordPress 数据库中(通常在数据库中;稍后将详细介绍)。此外,许多 API 都有速率限制,这意味着您在给定时间段内只能发出x个请求。瞬时数据允许您从自己请求该 API 响应,从而节省了大量的远程调用。
瞬时数据到底是什么?
它们是缓存信息的一种方法。瞬时数据是一种在服务器上进行的缓存形式,而不是浏览器缓存。可以将瞬时数据想象成一个具有三个组成部分的有机体
- 键。 一段简短的文本字符串。有机体的名称。
- 值。 任何 PHP 变量。有机体的本体——内脏,如果你愿意的话。
- 寿命。 通常表示为 时间常量,例如
DAY_IN_SECONDS
。我们希望该有机体存活的时间。
瞬时数据非常类似于 WordPress 选项,只是瞬时数据有指定的寿命。因此,它们是存储远程调用结果的绝佳选择。
嗯……如果远程 API 更改了其响应,这些瞬时数据会不会弄乱我的数据库?
这就是瞬时数据的妙处:它们会自动过期。如果您尝试在瞬时数据过期后从数据库中检索它,WordPress 会自动将其删除,防止任何混乱。同时,它会重新创建它,确保您拥有来自远程 API 的(合理)新鲜内容。
等等,WordPress 会删除我的宝贵信息?
是的,会。瞬时数据绝对不适合存储无法自动重新创建的数据。因此,您不会使用瞬时数据来存储例如用户在您网站上的表单中输入的数据。
瞬时数据只是用于远程调用吗?
瞬时数据适用于任何生成时间较长的信息块。它们将改善服务器延迟,适用于比检索单个数据库单元更复杂的所有例程。也就是说,它们是更多的代码,而更多的代码意味着更多的错误。因此,我倾向于将它们保留用于远程调用或非常大的查询。
这到底是怎么运作的?
假设您想要一份最近订阅您电子邮件通讯的列表。您使用第三方服务进行电子邮件更新,它通过 API 公开您的订阅者列表。以下是步骤序列
- 现在,您必须调用 API 以请求订阅者列表,因为您没有这些信息。
- API 会以订阅者列表响应您的请求,尽管需要几百毫秒,但您会收到所需的 API 响应。
- 您将这些数据存储在您的服务器上,存储在瞬时数据中,并将其寿命设置为
HOUR_IN_SECONDS
,即一小时。 - 在接下来的一个小时内,每次用户加载最近订阅者列表时,都会非常快,因为所有内容都存储在一个堆中,存储在您数据库中的一个单元格中。
- 一小时过去了,用户尝试加载您的页面。WordPress 会注意到寿命已过期,会删除您本地的值,您将回到此过程的第一步。
我还是不明白。
WROX 插件开发书籍 中有一章关于瞬时数据的精彩章节,手册 中也有介绍。
明白了,给我一段代码。
以下是瞬时数据的最基本形式
<?php
/**
* Get a list of email subscribers.
*
* @return object The HTTP response that comes as a result of a wp_remote_get().
*/
function css_t_subscribers() {
// Do we have this information in our transients already?
$transient = get_transient( 'css_t_subscribers' );
// Yep! Just return it and we're done.
if( ! empty( $transient ) ) {
// The function will return here every time after the first time it is run, until the transient expires.
return $transient;
// Nope! We gotta make a call.
} else {
// We got this url from the documentation for the remote API.
$url = 'https://api.example.com/v4/subscribers';
// We are structuring these args based on the API docs as well.
$args = array(
'headers' => array(
'token' => 'example_token'
),
);
// Call the API.
$out = wp_remote_get( $url, $args );
// Save the API response so we don't have to call again until tomorrow.
set_transient( 'css_t_subscribers', $out, DAY_IN_SECONDS );
// Return the list of subscribers. The function will return here the first time it is run, and then once again, each time the transient expires.
return $out;
}
}
?>
这是基本例程:在本地检查值,如果找到了,很好;如果没有,则从远程获取它并将其存储在本地,以便下次使用。
但是你承诺过一些技巧。
是的!现在您已经了解了基础知识,我有一些技巧要分享。我不会将它们捆绑到最终的示例中,因为您的处理方式可能需要根据您的应用进行调整。这是一个收集袋,我将根据之前解释的瞬时数据的三个组成部分进行组织
- 名称。
- 内容。
- 寿命。
有趣的是,这些也是传递给 set_transient()
的三个值。
瞬时变量命名技巧
这是我在使用瞬时变量时,从我的技巧宝盒里拿出来的最深刻的部分。这有点违反直觉,但 *给你的瞬时变量命名是使用它们最困难的事情*。你给瞬时变量命名的方式可以开启许多强大的可能性,或者完全破坏你的插件。
给你的瞬时变量名称加前缀
能够识别所有与你的插件相关的瞬时变量非常有用。这样做的方法是在它们前面加上你的插件命名空间。这对于防止与其他瞬时变量冲突也至关重要。这就是为什么你看到我在我这里的大多数示例中使用 `css_t_subscribers` 而不是仅仅使用 `subscribers` 的原因,其中 `css_t` 是我为 css-tricks.com 虚构的前缀。
更改名称以打破缓存
计算机科学中有两件难事:缓存失效、命名事物和越界错误。— Leon Bambrick 的,对 Phil Karlton 引用的改编
太棒了!在计算机科学的三大难题中,给你的瞬时变量命名涉及其中两个:命名事物和缓存失效。给你的瞬时变量命名与缓存失效有关,因为如果你*更改*了你的瞬时变量的名称,那么 WordPress 将无法找到它。这实际上可能是一件*好事*,因为它迫使你的插件调用远程 API 获取最新数据。
这种情况可能发生在你发布新版本的插件时。如果你插件代码发生了更改,那么你希望你的代码获取刷新的瞬时变量,而不是继续提供旧的代码,这些代码假设是旧的插件代码。一种方法是在你的瞬时变量名称中包含你的插件版本号。
<?php
// This is the version number for our plugin.
CSS_T_VERSION = '3.1.4';
function css_t_subscribers() {
// Do we have this information in our transients already?
$transient = get_transient( 'css_t_subscribers' . CSS_T_VERSION );
// Do things...
// Save the API response so we don't have to call again until tomorrow.
set_transient( 'css_t_subscribers' . CSS_T_VERSION, $out, DAY_IN_SECONDS );
}
?>
我喜欢使用 常量,因为常量是全局的,所以我不用将它传递到我的函数中 — 它已经在那里。但是,我确保给常量本身加上前缀,以便避免与其他常量发生冲突。
但是,不知从哪儿冒出来一个陷阱!由于你的瞬时变量名称每次更新插件时都会改变,WordPress 再也没有机会用旧的名称调用你的瞬时变量。这意味着它们永远不会被删除!你的瞬时变量会永远留在你的数据库中,达到人们在使用瞬时变量时有时会担心的可怕的“混乱”状态。这就是为什么给所有瞬时变量加前缀很重要的原因之一。有了这个前缀,你就可以自动删除所有与你的插件相关的瞬时变量,甚至可以在你的插件设置页面中提供一个有用的“清除缓存”按钮。这有点微妙,所以我将把详细内容留到文章后面的部分。
让我坦白地说:我从未设置过测试场景来确认瞬时变量可能有一天会像我上面描述的那样堆积起来成为垃圾,但 它似乎确实发生 在某些情况下。此外,阅读核心 `get_transient()` 函数似乎支持这个假设。WP Engine 似乎也同意。
另外需要注意的是:你可以从你的插件顶部的 docblock 中,通过 get_plugin_data()
动态获取你的插件版本。但是,该脚本只在 wp-admin 中加载。我可以想象你也可以在前端包含它,尽管我还没有尝试过。我不能保证它。
使用魔术常量为你的瞬时变量命名
Php 附带一些名为 魔术常量 的有用变量,其中最有用的是 __CLASS__
和 __FUNCTION__
。这些常量分别返回当前 php 类名的字符串和当前函数的名称。在命名很多东西时,这可以简化你的代码,包括瞬时变量。
<?php
class CSS_T {
function subscribers() {
$transient_key = __CLASS__ .'_' . __FUNCTION__;
}
}
?>
你可以轻松地将此技巧与上面提到的版本号技巧结合起来。
在你的瞬时变量名称中包含你的远程 API 密钥
大多数 API 要求你创建一个唯一的 API 密钥,或以其他方式将你的远程调用与你的帐户关联。如果你发现你需要更改那个 API 密钥,那么你的瞬时变量理应指向仅使用新密钥的远程调用。出于这个原因,你可能将你的 API 密钥附加到你的瞬时变量名称。
<?php
function css_t_subscribers( $limit = 50 ) {
$api_key = 'W$3Th&j8Ias76gF%^Fukg3%$Dy3ghd!@';
$transient_name = __FUNCTION__ . '_' . $api_key';
}
?>
你可能担心你的 API 密钥在网络中四处飘荡,因为这会造成安全隐患。你可以通过对 API 密钥进行加密来缓解这种情况,这种方法还有其他好处,我将在后面讨论。
在你的瞬时变量名称中包含远程 URL 和请求参数
远程 API 将根据你查询的确切 URL 提供不同的响应。一些示例可能是
<?php
// Getting all subscribers VS getting just 50 of them.
$subscribers = 'https://api.example.com/subscribers';
$fifty_subscribers = 'https://api.example.com/subscribers?limit=50';
// Getting all campaigns VS getting just the ones that have already sent.
$campaigns = 'https://api.example.com/campaigns';
$sent_campaigns = 'https://api.example.com/campaigns?status=sent';
?>
你的插件很有可能需要发送这些参数的许多组合,因此你将它们作为函数参数公开。鉴于此,你可以使用这些值动态构建每个查询的唯一瞬时变量键。
<?php
function css_t_subscribers( $limit = 50 ) {
// The base url for getting subscribers.
$url = 'https://api.example.com/subscribers';
// Sanitize the limit variable.
$limit = absint( $limit );
// Add the limit variable to the url.
$url = add_query_arg( array( 'limit', $limit ), $url );
// Use the url in the transient name.
$transient_name = __FUNCTION__ . '_' . $url';
}
?>
小心!存在问题!
老天,我们在这里好像醉了,一直在给我们的瞬时变量键添加东西!在你的瞬时变量名称中包含所有这些元素是绝对合理的。
- 插件命名空间。
- PHP 类名。
- PHP 函数名。
- 插件版本号。
- 远程 API URL。
- 远程 API 请求参数。
- 远程 API 密钥。
你最终可能会得到一个超过 100 个字符的瞬时变量名称,这将不起作用。为什么?因为*如果你使你的瞬时变量键超过 40 个字符,WordPress 可能不会存储瞬时变量*。这是因为 WordPress 数据库中的 options 表有一个字符限制。一旦你开始添加前缀,添加版本号,以及添加一些参数,就会很容易超过这个限制。WordPress 很快就会将这个限制增加到 255 个字符,但在此之前,解决这个问题的方法是通过 PHP 的 md5()
函数压缩你的瞬时变量名称。
读者 Jibran B 写信说:“wp_options 表中的瞬时变量名称 (option_name) 现在可以是 172 个字符,而不是 40 个字符。”
md5()
几乎可以接收任何字符串并将其压缩到 32 个字符 — 一个新的字符串,保证对你输入的字符串是唯一的。结果基本上是不可读的(它是哈希值),但你没有理由需要读取瞬时变量键名称,除了前缀部分。
鉴于我们只有 40 个字符可用,而 md5()
使用了其中的 32 个,这意味着我们只剩下 8 个字符用于前缀。为了代码可读性,我对计算机科学的第三个难题表示敬意,越界错误(见上文),并且只给自己留了 7 个字符,以确保安全。
<?php
function css_t_subscribers( $limit = '50' ) {
// The namespace for our plugin.
$namespace = css_t_namespace(); // Let's say this gives us the slug name, 'css_tricks';
// Cut it down to a max of 7 chars.
$namespace = substr($namespace, 0, 7 );
// The base url for getting subscribers.
$url = 'https://api.example.com/subscribers';
// Sanitize the limit variable.
$limit = absint( $limit );
// Add the limit variable to the url.
$url = add_query_arg( array( 'limit', $limit ), $url );
// Build a transient name that is guarenteed to carry all the uniqueness we might want, and also be less than 40 chars.
$transient_name = $namespace . md5( $url );
}
谁知道我们可以在瞬时变量命名的利基话题上谈论这么久。令人惊讶的是,你可以深入到这个话题,而这一切都是因为名称可以以有趣的方式改变,从而打破缓存。但是,关于名称就到此为止吧。
在瞬时变量中存储数据的技巧
在这篇文章的前面,我提到瞬态数据包含三个部分:名称、内容和生命周期。现在让我们来看第二部分,也就是你缓存在瞬态数据中的内容。
它并不一定要是字符串
WordPress核心告诉我们,我们不需要在存储之前序列化我们的瞬态数据内容。换句话说,我们并不局限于存储简单的值,如字符串或数字。相反,我们可以存储整个数组或对象,例如来自wp_remote_request()
的HTTP响应。
也就是说,虽然你可以存储整个响应,但这并不一定意味着你应该这样做。如果你对响应进行一些解析,只存储主体,或者甚至是一些主体子集,这可能会帮助简化你的插件。或者,你可能有一个很好的理由来存储整个响应,也许是因为你希望在插件的其他地方对HTTP状态码做出反应。这取决于你。
并非所有数据都值得存储
说到HTTP状态码,我做API集成时会做的第一件事之一就是阅读文档并整理一个HTTP状态码列表。在许多API中,40x或50x范围内的状态码意味着我在插件代码中犯了错误,发出了API无法满足的请求。可能没有理由将此存储在瞬态数据中,因此在保存之前我会将响应码与我的列表进行比较。
<?php
// Get a list of subscribers from a remote API.
function css_t_subscribers() {
// Transient stuff...
// Call the remote service.
$response = wp_remote_get( $url );
// Check our response to see if it's worth storing.
if ( ! css_t_check_response( $response ) ) {
return FALSE;
}
}
// Given an HTTP response, check it to see if it is worth storing.
function css_t_check_response( $response ) {
// Is the response an array?
if( ! is_array( $response ) ) { return FALSE; }
// Is the response a wp error?
if( is_wp_error( $response ) ) { return FALSE; }
// Is the response weird?
if( ! isset( $response['response'] ) ) { return FALSE; }
// Is there a status code?
if( ! isset( $response['response']['code'] ) ) { return FALSE; }
// Is the status code bad?
if( in_array( $response['response']['code'], css_t_bad_status_codes() ) ) { return FALSE; }
// We made it! Return the status code, just for posterity's sake.
return $response['response']['code'];
}
// A list of HTTP statuses that suggest that we have data that is not worth storing.
function css_t_bad_status_codes() {
return array( 404, 500 );
}
?>
只存储GET请求的结果
我这里指的是RESTful API。在RESTful API中,你可以使用不同的请求类型进行请求。以下是一些最常见的类型:
- GET - 用于获取数据。
- POST - 用于添加一行数据。
- PUT - 用于编辑整行数据。
- PATCH - 用于编辑数据的一部分。
- DELETE - 用于删除整行数据。
我一直提到wp_remote_request()
函数家族,猜猜怎么了?它们允许你指定你要执行的请求类型。只有一种请求类型的响应属于瞬态数据,那就是GET请求。事实上,如果你正在进行任何其他类型的请求,那么你很可能试图更改远程服务器上的数据,这意味着你的一些瞬态数据可能已经过时了。这将是一个清空与你的插件相关的所有瞬态数据的机会。我很快就会深入探讨如何做到这一点。
在我们的电子邮件API集成的例子中,每当有人注册我的邮件列表时,我的插件就会向远程API发送一个POST请求,将他们添加到我的邮件列表中。我的插件中可能有一个专门调用该API的函数。该函数将检测我正在执行的请求类型,如果它不是GET请求,它将清空我的所有插件瞬态数据。
这种态度假设数据准确性比性能更重要,坦白地说,情况并非总是如此。也许你有一个视图提供了数百行数据,并且这些数据频繁变化。在这种情况下,在你的插件执行的每次POST请求中清空瞬态数据将不是一个高效的做法。
为瞬态数据指定生命周期的技巧
我们来到了瞬态数据的第三部分,也是最后一部分:生命周期。这可以通过几种不同的方式来表示:
set_transient( $name, $content, 3600 )
- 存储数据 3600 秒,即一小时。set_transient( $name, $content, 60 * 60 )
- 存储数据 60 分钟,即一小时,只是更易读。set_transient( $name, $content, HOUR_IN_SECONDS )
- 存储数据一小时,非常易读。 这些是WordPress自带的。
用于调试的“蜉蝣”瞬态数据
一个蜉蝣是一种寿命极短的昆虫。考虑以下瞬态数据:
set_transient( $name, $content, 1 )
这是一个只持续一秒钟的瞬态数据!这个瞬态数据几乎不可能从数据库中被调用。它必须在不到一秒钟的时间内生成,然后重新请求。但是,这引入了一种在插件中提供一种调试模式的有用方法。如果你试图调试代码,最常见的步骤之一是echo
你的变量,以查看它们是否反映了你期望的结果。这对于瞬态数据来说非常令人沮丧。你必须进入你的API调用,注释掉瞬态数据逻辑,以确保你得到用于调试的新结果,然后记住在部署之前取消注释。相反,我这样做:
<?php
// If the user is a super admin and debug mode is on, only store transients for a second.
function css_t_transient_lifespan() {
if( is_super_admin() && WP_DEBUG ) {
return 1;
} else {
return DAY_IN_SECONDS;
}
}
// Get subscribers, using a dynamic value for the transient time.
function css_t_subscribers() {
// ...
$lifespan = css_t_transient_lifespan();
set_transient( $name, $content, $lifespan );
// ...
}
?>
也就是说,如果你有一些生命周期比较长的瞬态数据,例如DAY_IN_SECONDS
,那么你仍然会得到那些旧的值,直到明天。不酷。这就是你需要一种方法来轻松地清除所有插件瞬态数据的原因。
清空
我们需要选择所有与我们的插件相关的瞬态数据,然后使用delete_transient()
函数删除每一个。理论上,我们可以通过SQL删除它们,但通常最好在更接近应用程序级别的层面进行操作,这条规则在这里也绝对适用。我将在稍后解释原因。
<?php
// Purge all the transients associated with our plugin.
function purge() {
global $wpdb;
$prefix = esc_sql( $this -> get_transient_prefix() );
$options = $wpdb -> options;
$t = esc_sql( "_transient_timeout_$prefix%" );
$sql = $wpdb -> prepare (
"
SELECT option_name
FROM $options
WHERE option_name LIKE '%s'
",
$t
);
$transients = $wpdb -> get_col( $sql );
// For each transient...
foreach( $transients as $transient ) {
// Strip away the WordPress prefix in order to arrive at the transient key.
$key = str_replace( '_transient_timeout_', '', $transient );
// Now that we have the key, use WordPress core to the delete the transient.
delete_transient( $key );
}
// But guess what? Sometimes transients are not in the DB, so we have to do this too:
wp_cache_flush();
}
?>
当用户点击你的插件设置页面上的按钮时,当发布新帖子时,或者也许是每当保存小部件时,你都可以调用这个函数。这取决于你!
注意这段代码中的最后一行,我调用了wp_cache_flush
。这是因为我们的瞬态数据可能根本不在数据库中。它们实际上可能在对象缓存中!
我们需要谈谈对象缓存
你是否注意到了我在这篇文章中不同地方的谨慎语气,暗示了瞬态数据并不总是在数据库中?这是因为对象缓存。
最近我试图为客户调试一个API集成。我试图使用phpMyAdmin检查数据库中的瞬态数据值,但找不到任何。这是因为客户正在使用对象缓存:这意味着他们的瞬态数据并不存在于数据库中!
为了避免对象缓存带来的问题,你只需要像往常一样操作你的瞬态数据,使用set_transient()
、get_transient()
和delete_transient()
。如果对象缓存可用,它将以自己的方式操作它们。
当我说“像往常一样”时,我的意思是与使用SQL查询来处理瞬态数据形成对比。在我的上述代码片段中,我通过SQL查询来选择我的瞬态数据,这违反了规则,因此我必须付出代价。我选择通过调用wp_cache_flush()
来支付性能和多一行代码的代价,这会清空整个对象缓存。全面、简单,但很笨重。我还可以选择通过其他几种方式来支付这个代价。
首先,也许更明智的做法是只清空与我的插件相关的缓存部分。对象缓存类有用于执行此操作的方法。但是,这是一篇关于瞬态数据的文章,所以我不会深入探讨对象缓存。
另一种方法是在一个数组中注册我的每个瞬态数据键,并将该数组存储在数据库中。这样,我就可以循环遍历该数组,并对每个值调用delete_transient()
。
<?php
// Pass the transient key to this function whenever we save a transient.
function css_t_update_transient_keys( $new_transient_key ) {
// Get the current list of transients.
$transient_keys = get_option( 'css_t_transient_keys' );
// Append our new one.
$transient_keys[]= $new_transient_key;
// Save it to the DB.
update_option( 'css_t_transient_keys', $transient_keys );
}
// Call this function to dump our plugin transients.
function css_t_purge() {
// Get our list of transient keys from the DB.
$transient_keys = get_option( 'css_t_transient_keys' );
// For each key, delete that transient.
foreach( $transient_keys as $t ) {
delete_transient( $t );
}
// Reset our DB value.
update_option( 'css_t_transient_keys', array() );
}
?>
在我看来,每当我们保存一个瞬态数据时,都会进行额外的数据库调用来更新选项,这有点奇怪 - 这就像用两块石头打一只鸟。但这并不需要太多代码,它不需要SQL,并且可以很好地与对象缓存配合使用。
如果你想了解更多关于对象缓存的信息,我建议深入研究WordPress核心。例如,查看delete_transient()
的源代码。你可以看到它在回退到普通的WP选项API之前检查了对象缓存。
下一步
我原本想将讨论范围限定在 Transients API,但现实情况是,它最好与 WordPress 的 HTTP API 结合使用,并略带对对象缓存的意识。如果你正在开发一个进行远程调用的插件,应该考虑使用 WordPress HTTP API。将所有这些远程调用抽象到一个 PHP 类中,该类可以轻松地使用 WordPress Transients API 在调用远程服务之前和之后。通过掌握对远程调用使用瞬态的方法,你可以轻松地掌握整个 API 网络,而性能问题却可以降到最低。
我已经阅读了很多关于瞬态的文章,甚至自己也写过一些。到目前为止,这是我读过的最简洁的文章!感谢您如此深入地讲解,并在同一篇文章中包含所有常见的陷阱,并附带出色的示例代码片段。我将与我的团队分享这篇文章!