每个人都希望网站更快。网站越快,用户越愿意停留、继续浏览页面或购买商品。缓存是提高网站速度的一种方法:先存储信息,之后读取它,比重新连接数据库更快。打个比方,从冰箱拿零食,比开车去商店、在货架上找商品、结账,再开车回家吃,要快得多。
缓存有很多类型,包括对象缓存、浏览器缓存、页面缓存,甚至数据库缓存。每种都有相应用途,设置方式可能只是安装插件,也可能需要修改服务器配置来启用对象缓存。本文介绍 Transients API。根据使用环境,它可能采用对象缓存,也可能采用数据库缓存。
Transients API 用于在 WordPress 中跨页面加载临时保存信息。默认安装将信息保存在数据库的 options 表中。它与 Options API 很相似,但允许设置过期时间,过期后数据会被删除。
Transient 非常强大,不仅可以避免为页面内容反复请求数据库,还能加快复杂 HTML 标记生成,并缓解第三方 API 请求缓慢带来的影响。
保存 transient
使用 set_transient 保存 transient。它接受三个参数:名称、待存储数据,以及可选的以秒为单位的过期时间。设置成功返回 true。
下面设置一个一周后过期的 transient:
set_transient( 'dev-blog-transient', 'Some data I want to persist', WEEK_IN_SECONDS );
WordPress 提供多个便于计算时间的常量,使用 Transients API 时很实用,也让代码更易读。WEEK_IN_SECONDS 比 604800 更容易理解。
读取 transient
使用 get_transient 获取数据。它只接受一个参数,即 transient 名称。存在时返回数据,不存在或已过期时返回 false。
$persisted_data = get_transient( 'dev-blog-transient' );
最好使用 === 严格比较,确认数据存在并且类型一致。某些缓存值在布尔语境下可能为假,例如零;原文也将字符串“false”列作此类例子,但它们在业务中仍可能是有效数据。严格比较可以避免把有效数据误判为缓存缺失。
下面是常见的检查方式,也为缺失时重新生成 transient 留好了位置:
if ( false === ( $persisted_data = get_transient( 'dev-blog-transient' ) ) ) {
// This will run if there is no transient or it has expired.
}
删除 transient
可以随时通过 delete_transient 删除 transient。它也只接受名称参数,删除成功返回 true。
delete_transient( 'dev-blog-transient' );
对象缓存
要完整理解 Transients API,需要先了解 WordPress 的内存缓存,也就是对象缓存。对象缓存可以把计算成本较高的结果留在内存中,供后续使用。
典型场景是一个开销较大的 WP_Query,其结果会用到多次。缓存结果就不必反复查询数据库,从而缩短处理时间。
原文使用 WP_Object_Class 这个名称描述底层缓存类;实际交互通常通过 wp_cache_* 函数族完成。
默认对象缓存不持久,页面加载结束后,缓存内容就会消失。借助缓存插件可以实现持久化。具体设置超出本文范围,通常需要修改服务器,并添加 object-cache.php drop-in,覆盖默认对象缓存。
这与 Transients API 有什么关系?一旦使用持久化对象缓存,Transient 就不再通过 Options API 存入数据库,而改由对象缓存保存。直接收益是速度:读取内存中的数据通常远快于查询数据库。
更好的是,无需修改业务代码就能获得这些性能收益,切换都发生在底层。
Transient 的过期时间
过期时间经常被误解。Ryan McCue 在官方文档中的解释非常清楚:
大家似乎总是误解 transient 的过期机制。简而言之,过期时间是最长存活时间,没有最短存活时间。Transient 可能设置一秒后就消失,也可能 24 小时后消失,但绝不会在过期时间之后继续存在。
Ryan McCue
数据库更新、对象缓存被清空等外部因素,都可能导致数据提前消失。因此,代码必须始终考虑 transient 不存在的情况。
这也意味着,即使把过期时间设为 100 年以后,也无法保证数据会一直保留到那个时候。
始终设置过期时间
设置 transient 时,可以省略过期时间参数,它默认是 0:
set_transient( 'dev-blog-transient', 'Some data I want to persist' );
原文将这一情况描述为数据不应在 0 秒之后仍可用,并提醒不要因此把它当作很短时间缓存的合适做法。在 transient 保存到数据库时,它有一个潜在的不良副作用。
Transient 底层通过 Options API 写入数据库。查看源代码会发现,没有过期时间的 transient 会将对应选项设为自动加载:
$autoload = 'yes';
if ( $expiration ) {
$autoload = 'no';
add_option( $transient_timeout, time() + $expiration, '', 'no' );
}
$result = add_option( $transient_option, $value, '', $autoload );
这意味着每次页面请求都会自动载入该数据,即使根本没有用到。设想每次加载页面都读取 1000 个包含大型数据集的 transient,网站性能可能受到巨大影响。
除了部署对象缓存,最简单的避免方式就是始终设置过期时间,即使只有一秒:
set_transient( 'dev-blog-transient', 'Persisted but not autoloaded data', 1 );
如果没有持久化对象缓存,只想在一次页面加载期间复用数据,而不需要 transient,可以使用 wp_cache_set() 和 wp_cache_get()。页面加载结束后,这些数据会消失。
什么时候使用 transient
这取决于代码在哪里运行,以及你是否了解运行环境。
有时,代码运行在自己的站点或客户站点上,服务器配置已知,可以确定是否启用了 Redis、Memcached 等持久化对象缓存。此时可以使用 Transients API,也可以使用 wp_cache_*,因为 transient 已经保存在对象缓存中。
如果公开发布插件或主题,就无法知道用户的服务器环境。这种情况下,使用 Transients API 更合适,因为没有持久化对象缓存时,它仍可写入数据库。
简而言之:需要在不同服务器环境中都能跨请求保存数据时,使用 Transients API。
代码示例
读取或重新生成 transient 数据
最常见的用法是检查 transient 是否存在,不存在则生成。
下面读取文章 ID 列表。如果 transient 返回 false,就执行查询,并通过 set_transient 只保存查询返回的 ID 数组。关键在于,读取缓存和最终生成的 ID 列表都使用同一变量 $persisted_data,保证后续代码无论缓存是否命中都能正常工作。
if ( false === ( $persisted_data = get_transient( 'dev-blog-post-id-list' ) ) ) {
// The transient doesn't exist or has expired so you need to regenerate
$query = new WP_Query( array( 'posts_per_page' => 25, 'fields' => 'ids' ) );
if ( $query->have_posts() ) {
$persisted_data = $query->posts;
} else {
$persisted_data = array();
}
set_transient( 'dev-blog-post-id-list', $persisted_data, WEEK_IN_SECONDS );
}
预热缓存
缓存预热有助于让访客获得更好的性能体验。其本质是:网站发生变化时,重新生成相关 transient,使用户访问时数据已经准备好。
下面在文章保存时预热 transient:
add_action(
'save_post',
function () {
// The post was saved.
$query = new WP_Query( array( 'posts_per_page' => 25, 'fields' => 'ids' ) );
if ( $query->have_posts() ) {
$persisted_data = $query->posts;
} else {
$persisted_data = array();
}
set_transient( 'dev-blog-post-id-list', $persisted_data, WEEK_IN_SECONDS );
},
);
存储 HTML 标记
Transient 不一定只保存数据。有时预先生成并缓存 HTML 更高效。基本思路相同:检查 transient,不存在时重新生成标记并保存。
下面读取最近五篇文章,生成包含文章标题和作者名称的列表:
if ( false === ( $markup = get_transient( 'stored_markup' ) ) ) {
$query = new WP_Query( array( 'posts_per_page' => 5 ) );
$markup = '';
if ( $query->have_posts() ) {
$markup .= '<ul>';
foreach ( $query->posts as $post ) {
$markup .= '<li>';
$markup .= get_the_title( $post->ID ) . ' | ';
$markup .= get_the_author_meta( 'nicename', $post->post_author );
$markup .= '</li>';
}
$markup .= '</ul>';
set_transient( 'stored_markup', $markup, WEEK_IN_SECONDS );
}
}
存储 API 响应
第三方 API 响应非常适合存入 transient。通常没有必要让每次页面加载都发起新请求;保存响应还能缓解 API 限流或响应缓慢的问题。
下面请求一个第三方表情包生成服务,并把返回数据保存在 transient 中:
if ( false === ( $api_data = get_transient( 'stored_memes' ) ) ) {
// Make the request to the API.
$response = wp_remote_get( 'https://api.imgflip.com/get_memes', array( 'headers' => array( 'Content-Type' => 'application/json') ) );
// Ensure that there is a response.
if ( ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ) ) {
// Retrieve the body of the request.
$body = wp_remote_retrieve_body( $response );
// Process API data.
$api_data_json = json_decode( $body, true );
$api_data = $api_data_json['data']['memes'];
// Store the results for a maximum of 1 hour.
set_transient( 'stored_memes', $api_data, MINUTE_IN_SECONDS * 60 );
}
}
你是否以有趣或出人意料的方式使用过 transient?欢迎在评论中分享示例。
感谢 @greenshady 和 @milana_cap 对原文的审阅。
原文:An introduction to the Transients API。作者/维护者:Ryan Welcher。本文为原文的中文译文;代码保留原文内容。











暂无评论内容