阐述 `get_transient()` 和 `set_transient()` 函数的源码,它是如何实现短暂数据缓存的?

各位观众,晚上好!今天咱们来聊聊 WordPress 里一对神奇的函数:get_transient()set_transient()。 别看名字高深,其实它们就是 WordPress 的“小抄本”,专门用来缓存那些“一会儿有用,一会儿没用”的数据。想象一下,你辛辛苦苦从数据库里捞出一堆数据,恨不得立刻写在手心,下次用的时候直接看,省得再去数据库里折腾。 这俩函数就扮演了你手心的角色,只不过它们写的是数字化的“小抄”。

为什么要用 Transient?

在深入源码之前,先说说为什么要用这玩意儿。 想象一下,你的网站上有一个功能,需要频繁调用一个外部 API 获取数据。 每次用户访问都去调 API,你的服务器和外部 API 估计都要崩溃。 这时候,Transient 就派上用场了。 它可以把 API 返回的数据缓存起来,在一定时间内直接返回缓存数据,减轻服务器压力,提高网站速度。

举几个例子:

  • 第三方 API 数据: 比如天气预报、汇率信息等,这些数据一般不会实时变化,缓存一段时间是没问题的。
  • 复杂的数据库查询结果: 比如计算热门文章、统计用户行为等,这些查询消耗资源较多,缓存可以减少数据库压力。
  • 页面片段: 缓存一些不常变化的页面片段,比如侧边栏的广告、热门评论等。

Transient 的基本用法

先来个简单的例子:

<?php

// 1. 设置 Transient
$transient_name = 'my_super_data';
$data = array(
    'name' => '张三',
    'age' => 30,
    'city' => '北京'
);
$expiration = 3600; // 缓存 1 小时 (秒)

set_transient( $transient_name, $data, $expiration );

// 2. 获取 Transient
$cached_data = get_transient( $transient_name );

if ( false === $cached_data ) {
    // Transient 不存在或已过期,重新获取数据
    $data = array(
        'name' => '张三',
        'age' => 30,
        'city' => '北京'
    ); // 假设这里是从数据库或者 API 获取数据
    set_transient( $transient_name, $data, $expiration );
    $cached_data = $data;
    echo '从数据库获取数据!';
} else {
    echo '从缓存获取数据!';
}

// 3. 使用缓存数据
echo '<pre>';
print_r( $cached_data );
echo '</pre>';

// 4. 删除 Transient (可选)
// delete_transient( $transient_name );

?>

这个例子演示了 Transient 的基本用法:

  1. set_transient( $transient_name, $value, $expiration ): 设置一个 Transient,参数分别是 Transient 的名称、要缓存的数据、以及过期时间 (秒)。
  2. get_transient( $transient_name ): 获取一个 Transient,如果 Transient 存在且未过期,则返回缓存的数据;否则返回 false
  3. delete_transient( $transient_name ): 删除一个 Transient (可选)。

源码剖析:set_transient()

现在,让我们深入 set_transient() 的源码,看看它是如何实现的。 set_transient() 函数位于 wp-includes/functions.php 文件中。

function set_transient( $transient, $value, $expiration = 0 ) {
    /**
     * Fires before a transient is set.
     *
     * @since 3.0.0
     *
     * @param string $transient Transient name.
     * @param mixed  $value     Transient value.
     * @param int    $expiration Time until expiration in seconds.
     */
    do_action( 'set_transient', $transient, $value, $expiration );

    /**
     * Filters the value of an existing transient.
     *
     * The dynamic portion of the hook name, `$transient`, refers to the transient name.
     *
     * @since 4.4.0
     *
     * @param mixed  $value     The value to be stored, might be pre-filtered.
     * @param string $transient Transient name.
     * @param int    $expiration Time until expiration in seconds.
     */
    $value = apply_filters( 'pre_set_transient_' . $transient, $value, $transient, $expiration );

    if ( is_serialized( $value ) ) {
        $value = @unserialize( $value );
    }

    /**
     * Filters the expiration of an existing transient.
     *
     * The dynamic portion of the hook name, `$transient`, refers to the transient name.
     *
     * @since 4.4.0
     *
     * @param int    $expiration Time until expiration in seconds.
     * @param string $transient  Transient name.
     * @param mixed  $value      Transient value.
     */
    $expiration = apply_filters( 'transient_expiration_' . $transient, $expiration, $transient, $value );

    if ( empty( $expiration ) ) {
        $expiration = 0;
    } else {
        $expiration = time() + (int) $expiration;
    }

    $transient_timeout = '_transient_timeout_' . $transient;
    $transient         = '_transient_' . $transient;

    // If the transient does not already exist, then we need to insert it.
    if ( false === get_option( $transient ) ) {
        if ( add_option( $transient, $value, '', 'no' ) ) {
            add_option( $transient_timeout, $expiration, '', 'no' );
            /**
             * Fires after a transient is set.
             *
             * @since 3.0.0
             *
             * @param string $transient Transient name.
             * @param mixed  $value     Transient value.
             * @param int    $expiration Time until expiration in seconds.
             */
            do_action( 'setted_transient', $transient, $value, $expiration );
            return true;
        } else {
            return false;
        }
    } else {

        // If the transient already exists, then we need to update it.
        if ( update_option( $transient, $value ) ) {
            update_option( $transient_timeout, $expiration );
            /**
             * Fires after an existing transient is updated.
             *
             * @since 3.0.0
             *
             * @param string $transient Transient name.
             * @param mixed  $value     Transient value.
             * @param int    $expiration Time until expiration in seconds.
             */
            do_action( 'updated_transient', $transient, $value, $expiration );
            return true;
        } else {
            return false;
        }
    }
}

代码有点长,咱们一步步分析:

  1. Action 和 Filter: set_transient() 函数开头和结尾都使用了 do_action() 函数,允许开发者在 Transient 设置前后执行自定义操作。中间还使用了 apply_filters() 函数,允许开发者修改 Transient 的值和过期时间。 这体现了 WordPress 强大的扩展性。

  2. is_serialized()unserialize(): 这部分代码看起来有点奇怪。 实际上,WordPress 选项 API 默认会自动序列化和反序列化数据。 为了保持一致性,set_transient() 也做了类似的处理。 如果 $value 已经是序列化的字符串,它会先反序列化,然后再保存。

  3. 处理过期时间: $expiration 参数是秒数,set_transient() 会将其转换为 Unix 时间戳,并保存在 _transient_timeout_{$transient} 选项中。

  4. Transient 名称: set_transient() 会给传入的 $transient 名称加上 _transient_ 前缀,然后作为选项名称存储在数据库中。 过期时间也类似,会加上 _transient_timeout_ 前缀。 这样做是为了避免 Transient 名称与现有的选项名称冲突。

  5. 使用 add_option()update_option(): set_transient() 使用 add_option()update_option() 函数将 Transient 数据和过期时间存储在 wp_options 表中。

    • 如果 Transient 不存在,则使用 add_option() 创建新的选项。
    • 如果 Transient 已经存在,则使用 update_option() 更新选项的值。

关键点总结:set_transient()

步骤 说明
1. Action/Filter 提供钩子,允许开发者在 Transient 设置前后和修改 Transient 的值和过期时间时执行自定义操作。
2. 序列化处理 如果 Transient 的值已经是序列化的字符串,先进行反序列化。
3. 计算过期时间 将传入的秒数 $expiration 转换为 Unix 时间戳。
4. 添加前缀 给 Transient 名称和过期时间名称添加 _transient__transient_timeout_ 前缀,避免与现有选项冲突。
5. 存储到数据库 使用 add_option()update_option() 函数将 Transient 数据和过期时间存储到 wp_options 表中。

源码剖析:get_transient()

接下来,我们看看 get_transient() 的源码,了解它是如何获取 Transient 数据的。 get_transient() 函数也位于 wp-includes/functions.php 文件中。

function get_transient( $transient ) {
    /**
     * Filters the value of an existing transient.
     *
     * The dynamic portion of the hook name, `$transient`, refers to the transient name.
     *
     * @since 4.4.0
     *
     * @param mixed  $pre_transient The default value to return if the transient does not exist.
     *                              Any value other than false will short-circuit the retrieval
     *                              and return the returned value.
     * @param string $transient     Transient name.
     */
    $pre = apply_filters( 'pre_transient_' . $transient, false, $transient );

    if ( false !== $pre ) {
        return $pre;
    }

    $transient = '_transient_' . $transient;

    $value = get_option( $transient );

    if ( false === $value ) {
        return false;
    }

    $transient_timeout = '_transient_timeout_' . $transient;
    $expiration = get_option( $transient_timeout );

    if ( false !== $expiration && time() > (int) $expiration ) {
        delete_transient( substr( $transient, 11 ) );
        return false;
    }

    /**
     * Filters an existing transient's value.
     *
     * The dynamic portion of the hook name, `$transient`, refers to the transient name.
     *
     * @since 3.0.0
     *
     * @param mixed  $value     Value of transient.
     * @param string $transient Transient name.
     */
    return apply_filters( 'transient_' . substr( $transient, 11 ), $value, substr( $transient, 11 ) );
}

代码同样有点长,咱们分解一下:

  1. Filter: get_transient() 首先使用 apply_filters() 函数,允许开发者在获取 Transient 之前自定义返回值。 如果 pre_transient_{$transient} 过滤器返回的值不是 false,则直接返回该值,不再执行后续的代码。 这提供了一种提前返回缓存数据的机制。

  2. Transient 名称: get_transient() 同样会给传入的 $transient 名称加上 _transient_ 前缀,以便从数据库中获取正确的选项。

  3. 使用 get_option(): get_transient() 使用 get_option() 函数从 wp_options 表中获取 Transient 数据。 如果选项不存在,get_option() 会返回 falseget_transient() 也随之返回 false

  4. 检查过期时间: get_transient() 会获取 _transient_timeout_{$transient} 选项,并判断当前时间是否超过了过期时间。 如果已过期,get_transient() 会使用 delete_transient() 函数删除 Transient,并返回 false

  5. Filter: get_transient() 最后使用 apply_filters() 函数,允许开发者在返回 Transient 数据之前对其进行修改。 这里使用 substr( $transient, 11 ) 去掉了 _transient_ 前缀,以便过滤器接收到原始的 Transient 名称。

关键点总结:get_transient()

步骤 说明
1. Filter 提供钩子,允许开发者在获取 Transient 之前自定义返回值,提前返回缓存数据。
2. 添加前缀 给 Transient 名称添加 _transient_ 前缀,从数据库中获取对应的选项。
3. 从数据库获取数据 使用 get_option() 函数从 wp_options 表中获取 Transient 数据。
4. 检查过期时间 获取 _transient_timeout_{$transient} 选项,判断当前时间是否超过了过期时间。如果已过期,则删除 Transient 并返回 false
5. Filter 提供钩子,允许开发者在返回 Transient 数据之前对其进行修改。

delete_transient() 函数

顺便提一下 delete_transient() 函数,它用于删除 Transient。 源码如下:

function delete_transient( $transient ) {

    /**
     * Fires before a transient is deleted.
     *
     * @since 3.0.0
     *
     * @param string $transient Transient name.
     */
    do_action( 'delete_transient', $transient );

    $transient_timeout = '_transient_timeout_' . $transient;
    $transient         = '_transient_' . $transient;

    $deleted = delete_option( $transient );
    if ( $deleted ) {
        delete_option( $transient_timeout );
        /**
         * Fires after a transient is deleted.
         *
         * @since 3.0.0
         *
         * @param string $transient Transient name.
         */
        do_action( 'deleted_transient', $transient );

        return true;
    } else {
        return false;
    }
}

delete_transient() 函数也很简单:

  1. Action: 提供钩子,允许开发者在 Transient 删除前后执行自定义操作。
  2. 添加前缀: 给 Transient 名称添加 _transient_ 前缀。
  3. 使用 delete_option(): 使用 delete_option() 函数从 wp_options 表中删除 Transient 数据和过期时间。

Transient 的存储位置

Transient 数据存储在 wp_options 表中。 wp_options 表是 WordPress 用来存储各种配置选项的表。 Transient 数据以选项的形式存储,选项名称以 _transient_ 开头,过期时间以 _transient_timeout_ 开头。

Transient 的优缺点

优点:

  • 简单易用: get_transient()set_transient() 函数使用起来非常简单,只需要几行代码就可以实现数据缓存。
  • 自动过期: Transient 可以设置过期时间,过期后会自动删除,避免缓存数据过期。
  • 可扩展性: Transient 提供了丰富的钩子,允许开发者自定义缓存行为。

缺点:

  • 存储在数据库: Transient 数据存储在数据库中,如果缓存数据量过大,可能会影响数据库性能。
  • 不适合缓存大型数据: 由于存储在数据库中,Transient 不适合缓存大型数据,比如大型图片或视频。

最佳实践

  • 选择合适的过期时间: 根据数据的变化频率,选择合适的过期时间。 如果数据变化频繁,过期时间可以设置短一些;如果数据变化较慢,过期时间可以设置长一些。
  • 不要缓存敏感数据: Transient 数据存储在数据库中,不适合缓存敏感数据,比如用户密码、信用卡信息等。
  • 避免缓存大型数据: Transient 不适合缓存大型数据,如果需要缓存大型数据,可以考虑使用其他缓存机制,比如 Memcached 或 Redis。
  • 清理过期数据: 虽然 Transient 会自动过期,但如果缓存数据量过大,可能会占用大量数据库空间。 可以定期清理过期的 Transient 数据。 可以使用 WordPress 的 WP-Cron 功能来定期执行清理任务。

更高级的用法:对象缓存 (Object Cache)

Transient 实际上是一种比较基础的缓存机制。 WordPress 还提供了更高级的对象缓存机制,可以将数据存储在内存中,而不是存储在数据库中。 对象缓存可以显著提高网站性能,尤其是在高流量的网站上。

常见的对象缓存方案包括:

  • Memcached: 一个高性能的分布式内存对象缓存系统。
  • Redis: 一个开源的内存数据结构存储系统,可以用作数据库、缓存和消息代理。

使用对象缓存需要安装相应的 WordPress 插件,比如 Memcached Object Cache 或 Redis Object Cache。 安装插件后,WordPress 会自动使用对象缓存来存储 Transient 数据和其他缓存数据。

总结

Transient 是 WordPress 中一种简单易用的缓存机制,可以有效地提高网站性能。 通过 get_transient()set_transient() 函数,你可以轻松地缓存那些“一会儿有用,一会儿没用”的数据。 但是,Transient 也有一些缺点,比如存储在数据库中,不适合缓存大型数据。 在实际开发中,需要根据具体情况选择合适的缓存方案。 如果你的网站流量较大,可以考虑使用更高级的对象缓存机制,比如 Memcached 或 Redis。

希望今天的讲解对大家有所帮助! 感谢大家的收看! 下次有机会再跟大家分享更多 WordPress 的技术知识。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注