社区新闻

Transients API 简介

查看官方原文 ↗ 发布于

每个人都希望网站速度快。网站越快,用户停留的时间就越长,点击页面或购买产品的可能性就越大。加速网站的方法之一是实施缓存。缓存背后的理念是存储信息,以便访问速度比连接数据库更快。打个比方,去冰箱拿零食比开车去商店、在货架上找到它、付款然后开车回家吃要快得多。

缓存有多种类型:对象缓存、浏览器缓存、页面缓存甚至数据库缓存。每种类型都有其目的和设置步骤,范围从安装插件到更改服务器配置以启用对象缓存。在本文中,您将了解 Transients API,它可以是对象缓存或数据库缓存——具体取决于其使用位置。

Transients API 用于在 WordPress 中跨页面加载临时存储信息。在默认安装中,此信息存储在数据库的 options 表中。这与 Options API 非常相似,但增加了设置过期时间的能力,过期后数据将被删除。

Transients 是一个非常强大的工具,不仅可以避免对页面内容进行不必要的数据库请求,还可以加速生成复杂的标记并缓解缓慢的第三方 API 请求。

保存 Transient

要保存一个 transient,可以使用 <a href="https://developer.wordpress.org/reference/functions/set_transient/">set_transient</a> 函数。它接受三个参数:transient 的名称、要存储的数据以及一个可选的过期时间(以秒为单位)。如果 transient 设置成功,它将返回 true

下面的代码片段设置了一个过期时间为一周的 transient。

set_transient( 'dev-blog-transient', 'Some data I want to persist', WEEK_IN_SECONDS );

WordPress 中有许多用于计算时间的常量,在处理 Transients API 时非常有用,并且使您的代码更具可读性。WEEK_IN_SECONDS604800 更容易理解。

检索 Transient

要检索存储在 transient 中的数据,可以使用 get_transient。此函数接受一个参数——要检索的 transient 的名称——并返回数据,如果 transient 不存在或已过期,则返回 false

$persisted_data = get_transient( 'dev-blog-transient' );

最好使用 === 运算符检查数据,以确认数据存在且类型相同。在某些情况下,transient 数据可能是“假值”,例如数字零或字符串“false”,但这对于数据的用途来说是有意义的。使用严格比较可以防止在这种情况下出现假阴性。

下面的代码片段演示了检查 transient 数据并设置重新生成 transient 能力的常见方法。

if ( false === ( $persisted_data = get_transient( 'dev-blog-transient' ) ) ) {
  // 如果没有 transient 或它已过期,将运行此代码。
} 

删除 Transient

任何 transient 都可以随时使用 delete_transient 函数删除。此函数也接受一个 transient 名称参数,如果 transient 被删除,将返回 true

delete_transient( 'dev-blog-transient' );

对象缓存

在完全理解 Transients API 之前,您需要了解 WordPress 的内存或对象缓存系统是如何工作的。对象缓存允许您将计算成本高的项目存储在内存中供以后使用。

一个很好的用例是,当您有一个昂贵的 WP_Query,其结果需要多次使用时。缓存结果可以避免多次访问数据库检索数据,并加快处理速度。

WordPress 在幕后使用 WP_Object_Class 来驱动此缓存,但您会使用 wp_cache_* 系列的函数与之交互。

默认的对象缓存不是持久性的,这意味着任何已缓存的内容在页面加载完成后都会消失。可以使用许多缓存插件将对象缓存更改为持久性。设置此功能超出了本文的范围,但它通常需要修改服务器并添加 object-cache.php 插件来覆盖默认的对象缓存。

此时,您可能想知道这与 Transients API 有什么关系。嗯,一旦有了持久性缓存,Transients API 将不再通过 Options API 将数据存储在数据库中,而是使用 WP_Object_Class 来持久化其数据。最直接的好处是速度。访问存储在内存中的数据可能比查询数据库获取相同数据快得多。

最棒的是,您无需对代码进行任何更改即可获得性能优势。所有更改都在幕后处理。

Transient 过期

Transient 过期时间的概念常常被误解。Ryan McCue 在官方文档中有一个很好的解释

似乎每个人都误解了 transient 过期的工作原理,简而言之就是:transient 过期时间是一个最大时间。没有最小年龄。Transient 可能在您设置后一秒消失,也可能在 24 小时后消失,但它们绝不会在过期时间之后还存在。

Ryan McCue

这是由于许多外部因素造成的,例如数据库更新或对象缓存被清除。因此,您应该始终假设 transient 不存在,并相应地编写代码。

这也意味着,即使您将 transient 的过期时间设置为从现在起 100 年,也不能保证数据会一直保留到那时。

始终设置过期时间

设置 transient 时,可以省略过期时间参数,使其默认为 0 秒。

set_transient( 'dev-blog-transient', 'Some data I want to persist' ); 

这实际上告诉 API,此数据在 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() 来设置和检索在页面加载完成后会消失的数据。

何时应该使用 Transients?

这将取决于您的代码在哪里运行以及您是否了解环境。

在某些情况下,您会知道您的代码将在哪里运行。无论是客户网站还是您自己的网站。服务器配置对您是已知的,并且可以确定是否存在像 RedisMemcached 这样的持久性对象缓存。在这种情况下,您可以使用 Transients API 或 wp_cache_* 函数,因为如上所述,transient 存储在对象缓存中。

如果您公开发布该插件或主题,则无法知道您的代码将在什么样的服务器环境中运行。在这种情况下,最好使用 Transients API,因为它保证会保存到数据库。

TL;DR: 如果您需要确保数据在任何服务器环境中都能持久化,请使用 Transient API。

代码示例

检索或重新生成 Transient 数据

最常见的例子可能是当您想检查一个 transient 并在它不存在时生成它。

在此示例中,您正在检索一个文章 ID 列表。如果 transient 返回 false,您将生成查询,然后使用 set_transient 仅存储查询返回的 ID 数组。这里重要的部分是,相同的变量名 $persisted_data 用于检索 transient 和最终生成的 ID 列表。这确保了无论 transient 是否存在,您的其余代码都能正常工作。

if ( false === ( $persisted_data = get_transient( 'dev-blog-post-id-list' ) ) ) {
    // Transient 不存在或已过期,因此需要重新生成
    $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 () {
        // 文章已保存。
        $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,如果不存在则重新生成标记并保存。

在这里,您将检索最近五篇文章并生成一个显示文章标题和作者姓名的列表。

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 的绝佳候选者。您很少会希望每次页面加载都发出新的请求,事实上,将结果存储在 transient 中可以缓解 API 的速率限制或响应缓慢等问题。

在此代码片段中,您将向第三方 meme 生成器服务发出请求,并将 meme 数据存储在 transient 中。

if ( false === ( $api_data = get_transient( 'stored_memes' ) ) ) {

	// 向 API 发出请求。
	$response = wp_remote_get( 'https://api.imgflip.com/get_memes', array( 'headers' => array( 'Content-Type'  => 'application/json') ) );

	// 确保有响应。
	if ( ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ) ) {

		// 检索请求的主体。
		$body = wp_remote_retrieve_body( $response );

		// 处理 API 数据。
		$api_data_json = json_decode( $body, true );
		$api_data      = $api_data_json['data']['memes'];

		// 存储结果,最多 1 小时。
		set_transient( 'stored_memes', $api_data, MINUTE_IN_SECONDS * 60 );
	}
}