分析 `wp_schedule_event()` 和 `wp_unschedule_event()` 的源码,它们如何管理周期性定时任务?

咳咳,麦克风试音… 喂喂,大家好!今天咱们不聊八卦,就来扒一扒 WordPress 里的“定时炸弹”—— wp_schedule_event()wp_unschedule_event(),看看它们是如何管理那些神出鬼没的周期性定时任务的。

一、什么是 WordPress 定时任务?(又名:WP-Cron)

想象一下,你需要每天凌晨3点自动发布一篇预先写好的文章,或者每周自动清理一次数据库垃圾数据。手动操作?那太麻烦了!这时候,WordPress 的定时任务(WP-Cron)就派上用场了。

WP-Cron 并不是一个真正的系统级别的 cron 任务,而是一个模拟的 cron 系统。它依赖于用户访问你的 WordPress 网站来触发。简单来说,每次有人访问你的网站,WordPress 都会检查是否有需要执行的定时任务。如果有,就执行;没有,就继续服务你的用户。

二、wp_schedule_event():定时任务的“调度员”

wp_schedule_event() 函数的作用就是向 WP-Cron 系统注册一个新的定时任务。 它的原型是这样的:

wp_schedule_event( int $timestamp, string $recurrence, string $hook, array $args = array() )
  • $timestamp: 任务第一次执行的时间戳(Unix 时间戳)。
  • $recurrence: 任务的重复频率,例如:’hourly’(每小时)、’daily’(每天)、’weekly’(每周)等等。 WordPress 预定义了一些常用的频率,你也可以自定义。
  • $hook: 任务触发时要执行的动作(Action Hook)。这是一个字符串,对应着你用 add_action() 注册的函数。
  • $args: 传递给动作函数的参数数组。

源码剖析:wp_schedule_event() 的内部运作

wp_schedule_event() 的源码稍微有点长,我们把它拆解开来看:

function wp_schedule_event( $timestamp, $recurrence, $hook, $args = array() ) {
    $crons = _get_cron_array(); // 获取所有计划任务
    $schedules = wp_get_schedules(); // 获取所有预定义的计划时间表

    if ( ! isset( $schedules[ $recurrence ] ) ) {
        return false; // 如果提供的重复频率无效,返回 false
    }

    $event = (object) array(
        'hook'      => $hook,
        'time'      => $timestamp,
        'args'      => $args,
        'schedule'  => $recurrence
    );

    $key = md5( serialize( $args ) );

    if ( isset( $crons[ $timestamp ][ $hook ][ $key ] ) ) {
        return true; // 如果该任务已经存在,直接返回 true
    }

    $crons[ $timestamp ][ $hook ][ $key ] = $event;
    ksort( $crons ); // 按照时间戳排序

    return _set_cron_array( $crons ); // 更新计划任务数组
}
  1. 获取计划任务和时间表:

    • _get_cron_array():这个函数负责从 WordPress 的 options 表中读取所有已注册的定时任务,并将其存储在一个数组中。这个数组是 WordPress 用来跟踪所有定时任务的核心数据结构。

    • wp_get_schedules():这个函数返回一个包含所有预定义和自定义计划时间表的数组。这些时间表定义了任务的重复频率,例如 ‘hourly’、’daily’、’weekly’ 等等。

  2. 验证重复频率:

    • 函数会检查你提供的 $recurrence 是否在 $schedules 数组中存在。如果不存在,说明你提供的重复频率无效,函数会返回 false
  3. 创建任务对象:

    • 创建一个包含任务信息的对象,包括 hook 名称、执行时间、参数和重复频率。
  4. 检查任务是否已存在:

    • 使用 md5(serialize($args)) 生成一个基于参数的唯一键。然后,检查在 $crons 数组中是否已经存在具有相同时间戳、hook 名称和参数的任务。如果存在,说明该任务已经被注册,函数直接返回 true
  5. 添加任务到计划任务数组:

    • 如果任务不存在,则将其添加到 $crons 数组中。$crons 数组是一个多维数组,其结构如下:

      $crons = array(
          timestamp => array(
              hook_name => array(
                  md5(serialize(args)) => (object) array(
                      'hook' => 'hook_name',
                      'time' => timestamp,
                      'args' => array(),
                      'schedule' => 'recurrence'
                  )
              )
          )
      );
  6. 排序和更新:

    • ksort($crons): 按照时间戳对 $crons 数组进行排序,确保任务按照正确的顺序执行。

    • _set_cron_array($crons):将更新后的 $crons 数组写回 WordPress 的 options 表中,以便下次加载时使用。

三、wp_unschedule_event():定时任务的“终结者”

wp_unschedule_event() 函数的作用是取消一个已经注册的定时任务。它的原型是这样的:

wp_unschedule_event( int $timestamp, string $hook, array $args = array() )
  • $timestamp: 要取消的任务的执行时间戳(Unix 时间戳)。
  • $hook: 要取消的任务的动作 Hook。
  • $args: 要取消的任务的参数数组。

源码剖析:wp_unschedule_event() 的内部运作

function wp_unschedule_event( $timestamp, $hook, $args = array() ) {
    $crons = _get_cron_array(); // 获取所有计划任务
    $key = md5( serialize( $args ) );

    if ( ! isset( $crons[ $timestamp ][ $hook ][ $key ] ) ) {
        return false; // 如果该任务不存在,返回 false
    }

    unset( $crons[ $timestamp ][ $hook ][ $key ] ); // 从数组中移除该任务

    if ( empty( $crons[ $timestamp ][ $hook ] ) ) {
        unset( $crons[ $timestamp ][ $hook ] ); // 如果 hook 下没有任务了,移除 hook
    }

    if ( empty( $crons[ $timestamp ] ) ) {
        unset( $crons[ $timestamp ] ); // 如果 timestamp 下没有任务了,移除 timestamp
    }

    return _set_cron_array( $crons ); // 更新计划任务数组
}
  1. 获取计划任务:

    • wp_schedule_event() 类似,首先使用 _get_cron_array() 函数从 options 表中读取所有已注册的定时任务。
  2. 生成唯一键:

    • 使用 md5(serialize($args)) 生成一个基于参数的唯一键,用于查找要取消的任务。
  3. 检查任务是否存在:

    • 检查 $crons 数组中是否存在具有相同时间戳、hook 名称和参数的任务。如果不存在,说明要取消的任务不存在,函数返回 false
  4. 移除任务:

    • 如果任务存在,则使用 unset() 函数从 $crons 数组中移除该任务。
    • 为了保持数据的整洁性,函数会检查在移除任务后,如果某个 hook 名称下不再有任何任务,则移除该 hook 名称。同样,如果某个时间戳下不再有任何任务,则移除该时间戳。
  5. 更新:

    • _set_cron_array($crons):最后,将更新后的 $crons 数组写回 WordPress 的 options 表中。

四、如何使用 wp_schedule_event()wp_unschedule_event()

例子:每天凌晨3点执行一次清理缓存的操作

// 1. 定义要执行的函数
function my_clear_cache_function() {
    // 这里写清理缓存的代码,例如:
    // wp_cache_flush();
    error_log("Cache cleared at " . date('Y-m-d H:i:s')); // 写入错误日志,方便调试
}

// 2. 注册动作
add_action( 'my_clear_cache_hook', 'my_clear_cache_function' );

// 3. 调度任务(在插件激活时)
function my_plugin_activate() {
    // 计算凌晨3点的时间戳
    $timestamp = strtotime( 'today 3:00' );
    if ( $timestamp < time() ) {
        $timestamp = strtotime( 'tomorrow 3:00' ); // 如果已经过了凌晨3点,则设置为明天凌晨3点
    }

    // 调度任务
    if ( ! wp_next_scheduled( 'my_clear_cache_hook' ) ) {
        wp_schedule_event( $timestamp, 'daily', 'my_clear_cache_hook' );
    }
}
register_activation_hook( __FILE__, 'my_plugin_activate' );

// 4. 取消任务(在插件停用时)
function my_plugin_deactivate() {
    wp_clear_scheduled_hook( 'my_clear_cache_hook' );
}
register_deactivation_hook( __FILE__, 'my_plugin_deactivate' );

代码解释:

  1. my_clear_cache_function(): 这是我们要执行的清理缓存的函数。你可以把实际的清理缓存代码放在这里。
  2. add_action( 'my_clear_cache_hook', 'my_clear_cache_function' ): 将 my_clear_cache_function() 函数绑定到 my_clear_cache_hook 动作上。
  3. my_plugin_activate(): 这个函数在插件激活时执行。它计算出下一个凌晨3点的时间戳,然后使用 wp_schedule_event() 函数注册一个每天凌晨3点执行 my_clear_cache_hook 动作的定时任务。 wp_next_scheduled() 用于检查是否已经存在该定时任务,避免重复注册。
  4. my_plugin_deactivate(): 这个函数在插件停用时执行。它使用 wp_clear_scheduled_hook() 函数取消所有与 my_clear_cache_hook 动作相关的定时任务。 wp_clear_scheduled_hook() 实际上会调用 wp_unschedule_event(),但它更方便,因为它能取消所有与特定 hook 相关的任务,而不需要知道具体的执行时间。

五、自定义计划时间表

WordPress 默认提供了一些计划时间表,例如 hourlydailyweekly 等等。如果你需要更灵活的计划时间表,可以使用 cron_schedules 过滤器来添加自定义的计划时间表。

例子:添加一个每15分钟执行一次的计划时间表

add_filter( 'cron_schedules', 'my_add_custom_cron_schedule' );

function my_add_custom_cron_schedule( $schedules ) {
    $schedules['every_fifteen_minutes'] = array(
        'interval' => 900, // 900 秒 = 15 分钟
        'display'  => __( 'Every 15 Minutes' )
    );
    return $schedules;
}

代码解释:

  • add_filter( 'cron_schedules', 'my_add_custom_cron_schedule' ):将 my_add_custom_cron_schedule() 函数绑定到 cron_schedules 过滤器上。
  • $schedules['every_fifteen_minutes'] = array(...):添加一个新的计划时间表,键名为 every_fifteen_minutes
  • 'interval' => 900:设置重复间隔为 900 秒(15 分钟)。
  • 'display' => __( 'Every 15 Minutes' ):设置在 WordPress 后台显示的名称。

添加自定义计划时间表后,你就可以在 wp_schedule_event() 函数中使用 every_fifteen_minutes 作为 $recurrence 参数了。

六、重要提示 & 常见问题

  1. WP-Cron 的局限性: 前面提到过,WP-Cron 依赖于用户访问网站来触发。如果你的网站访问量很低,定时任务可能不会按时执行。为了解决这个问题,你可以设置一个真正的系统级别的 cron 任务,定期访问 wp-cron.php 文件。例如,在 Linux 系统上,你可以使用 crontab 命令来设置一个每5分钟访问一次 wp-cron.php 的任务。

    */5 * * * * wget -q -O - http://your-domain.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1
  2. 调试定时任务: 如果你的定时任务没有按预期执行,可以使用以下方法进行调试:

    • 检查错误日志: 在你的函数中添加错误日志记录,以便查看是否有任何错误发生。
    • 使用插件: 有很多 WordPress 插件可以帮助你管理和调试定时任务,例如 WP Crontrol。
    • 手动触发: 可以通过访问 wp-cron.php 文件来手动触发定时任务。
    • 检查时间戳: 确保你设置的时间戳是正确的。
    • 检查参数: 确保你传递给动作函数的参数是正确的。
  3. 避免阻塞: 定时任务应该尽可能快速地执行,避免阻塞 WordPress 的正常运行。如果你的定时任务需要执行大量耗时的操作,可以考虑使用队列系统,例如 WP Queue。

  4. 安全问题: 确保你的定时任务是安全的,避免执行恶意代码。

七、总结

函数/概念 作用
wp_schedule_event() 注册一个新的定时任务。它接受任务的执行时间、重复频率、动作 Hook 和参数作为输入,并将任务信息存储在 WordPress 的 options 表中。
wp_unschedule_event() 取消一个已经注册的定时任务。它接受任务的执行时间、动作 Hook 和参数作为输入,并在 WordPress 的 options 表中删除相应的任务信息。
wp_clear_scheduled_hook() 取消所有与特定动作 Hook 相关的定时任务。它比 wp_unschedule_event() 更方便,因为它不需要知道具体的执行时间。
wp_next_scheduled() 检查是否已经存在与特定动作 Hook 相关的定时任务。这可以用来避免重复注册相同的任务。
cron_schedules 过滤器 用于添加自定义的计划时间表。你可以使用这个过滤器来定义自己的任务重复频率,例如每 15 分钟、每 3 小时等等。
WP-Cron WordPress 的模拟 cron 系统。它依赖于用户访问网站来触发定时任务的执行。由于 WP-Cron 的局限性,建议设置一个真正的系统级别的 cron 任务,定期访问 wp-cron.php 文件,以确保定时任务按时执行。

希望今天的讲解能够帮助你更好地理解 WordPress 的定时任务机制。记住,合理地使用定时任务可以让你摆脱很多重复性的工作,让你的 WordPress 网站更加自动化和智能化。

下课! 记得点赞! (虽然这里没有点赞功能…)

发表回复

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