剖析 WordPress `wp_schedule_event()` 函数的源码:如何调度一个定时任务,并解释其在 `wp_options` 表中的存储方式。

大家好,我是老码,今天咱们来聊聊WordPress里那个神秘的调度员——wp_schedule_event()。别看它名字挺长,干的活儿可简单,就是帮你安排个“定时炸弹”,让WordPress在指定的时间,自动执行你想要的功能。

一、wp_schedule_event():你的定时任务调度员

想象一下,你需要每天凌晨3点自动备份数据库,或者每周一早上9点发送一封营销邮件。这些任务手动操作太麻烦,这时候wp_schedule_event()就派上用场了。

它的基本语法是这样的:

wp_schedule_event( int $timestamp, string $recurrence, string $hook, array $args = array() )
  • $timestamp: 你希望任务开始执行的时间戳(Unix timestamp)。
  • $recurrence: 任务的重复频率,比如’hourly’(每小时)、’daily’(每天)、’weekly’(每周)等等。WordPress内置了一些常用的频率,你也可以自定义。
  • $hook: 这是一个字符串,对应着一个WordPress action hook。当时间到了,WordPress会触发这个hook,而你就可以把你的任务代码挂载到这个hook上。
  • $args: 可选参数,传递给hook的回调函数。

举个例子,假设我们想每天早上8点触发一个名为my_daily_task的hook:

// 计算明天的早上8点的时间戳
$timestamp = strtotime( 'tomorrow 8:00' );

// 每天执行一次
$recurrence = 'daily';

// hook名称
$hook = 'my_daily_task';

// 调度事件
wp_schedule_event( $timestamp, $recurrence, $hook );

// 接下来,你需要定义这个hook对应的回调函数
add_action( 'my_daily_task', 'my_daily_task_callback' );

function my_daily_task_callback() {
  // 这里写你的定时任务代码,比如备份数据库
  // ...
  wp_mail( '[email protected]', 'Daily Task Done!', 'The daily task has been completed successfully.' );
}

这段代码的意思是:从明天早上8点开始,每天早上8点,WordPress都会触发my_daily_task这个hook,然后执行my_daily_task_callback函数。

二、源码剖析:wp_schedule_event()背后的秘密

现在,让我们深入wp-includes/cron.php文件,看看wp_schedule_event()到底做了什么。

function wp_schedule_event( $timestamp, $recurrence, $hook, $args = array() ) {
  $crons = _get_cron_array(); // 获取当前的 cron 数组
  $key = md5( $hook . serialize( $args ) ); // 为这个任务生成一个唯一的 key

  if ( isset( $crons[$timestamp][$key] ) ) {
    return false; // 已经存在相同任务,直接返回
  }

  $crons[$timestamp][$key] = array(
    'schedule' => $recurrence,
    'args' => $args
  );

  return _set_cron_array( $crons ); // 更新 cron 数组
}

简单来说,wp_schedule_event()做了三件事:

  1. 获取当前的任务列表: 它通过_get_cron_array()函数,从WordPress选项表(wp_options)中读取一个名为cron的选项,这个选项存储着所有已调度的定时任务。

  2. 生成任务的唯一标识: 它使用md5( $hook . serialize( $args ) )生成一个唯一的key,这个key基于hook名称和参数,确保每个任务都有唯一的标识。

  3. 更新任务列表: 它将新的任务添加到任务列表中,并使用_set_cron_array()函数,将更新后的任务列表保存回wp_options表。

看起来很简单,对吧?但关键在于_get_cron_array()_set_cron_array()这两个函数,它们负责与wp_options表交互。

三、wp_options表:定时任务的“家”

WordPress使用wp_options表来存储各种配置信息,其中cron选项就是用来存储定时任务的。

让我们来看看cron选项存储的数据结构:

array(
  timestamp1 => array(
    'unique_key1' => array(
      'schedule' => 'recurrence1',
      'args' => array( arg1, arg2, ... )
    ),
    'unique_key2' => array(
      'schedule' => 'recurrence2',
      'args' => array( arg3, arg4, ... )
    ),
    ...
  ),
  timestamp2 => array(
    'unique_key3' => array(
      'schedule' => 'recurrence3',
      'args' => array( arg5, arg6, ... )
    ),
    ...
  ),
  ...
)
  • 外层数组的键: 是任务执行的时间戳。
  • 内层数组的键: 是任务的唯一key(由md5( $hook . serialize( $args ) )生成)。
  • 内层数组的值: 是一个包含schedule(重复频率)和args(参数)的数组。

举个例子,假设我们调度了两个任务:

  • 每天早上8点执行my_daily_task,没有参数。
  • 每周一早上9点执行my_weekly_task,参数为array( 'user_id' => 123 )

那么,cron选项的数据结构可能如下所示:

array(
  1678886400 => array( // 假设这是明天早上8点的时间戳
    'e5e9fa1ba31ecd1ae84f75caaa474f3a' => array( // md5(my_daily_task)
      'schedule' => 'daily',
      'args' => array()
    )
  ),
  1679232000 => array( // 假设这是下周一早上9点的时间戳
    '698d51a19d8a121ce581499d7b701668' => array( // md5(my_weekly_taska:1:{s:7:"user_id";i:123;})
      'schedule' => 'weekly',
      'args' => array(
        'user_id' => 123
      )
    )
  )
)

注意:这里的时间戳只是示例,实际的时间戳会根据你的设置而变化。 另外,md5的值也是示例,实际的值会根据hook名称和参数而变化。 可以看到,cron选项存储了所有已调度的任务信息,WordPress正是通过读取这个选项,来判断何时应该执行哪个任务。

四、_get_cron_array()_set_cron_array():与wp_options表交互的桥梁

现在,让我们来看看_get_cron_array()_set_cron_array()这两个函数的实现。

function _get_cron_array() {
  global $wpdb;

  $options = wp_load_alloptions(); // 加载所有 options 到缓存
  $crons = get_option( 'cron' );

  if ( ! is_array( $crons ) ) {
    $crons = array();
  }

  return $crons;
}

_get_cron_array()函数非常简单,它只是使用get_option( 'cron' )wp_options表中读取cron选项的值,并返回一个数组。 为了提高性能,WordPress会缓存所有的options,所以get_option()函数通常不会直接查询数据库。

function _set_cron_array( $cron ) {
  return update_option( 'cron', $cron );
}

_set_cron_array()函数也很简单,它只是使用update_option( 'cron', $cron )将新的cron数组保存回wp_options表。

五、WordPress如何执行定时任务?wp-cron.php的功劳

现在,我们已经了解了如何调度定时任务,以及任务信息是如何存储在wp_options表中的。但是,还有一个关键问题:WordPress是如何执行这些定时任务的呢?

答案是:wp-cron.php

wp-cron.php是一个PHP脚本,它负责检查是否有需要执行的定时任务,并执行它们。

但是,wp-cron.php并不是一个守护进程,它不会一直运行在服务器上。相反,WordPress使用一种叫做“虚拟cron”的技术来执行定时任务。

所谓“虚拟cron”,是指WordPress会在每次页面加载时,检查是否需要执行wp-cron.php。如果需要,WordPress会通过HTTP请求来触发wp-cron.php

具体来说,WordPress会在wp_footer() action中,添加一段JavaScript代码,这段代码会在页面加载完成后,向wp-cron.php发送一个HTTP请求。

当然,为了避免频繁触发wp-cron.php,WordPress会使用一个锁机制。只有当上次执行wp-cron.php的时间超过一定间隔(默认为300秒)时,才会触发新的wp-cron.php请求。

你可以通过以下方式来禁用虚拟cron,并设置真正的服务器cron:

  1. wp-config.php文件中添加以下代码:

    define('DISABLE_WP_CRON', true);
  2. 在服务器上设置一个cron job,每隔一段时间(比如5分钟)执行wp-cron.php

    例如,在Linux服务器上,你可以使用crontab -e命令来编辑cron表,并添加以下一行:

    */5 * * * * php /path/to/your/wordpress/wp-cron.php >/dev/null 2>&1

    /path/to/your/wordpress替换为你的WordPress安装目录。

六、自定义重复频率:让你的任务更灵活

WordPress内置了一些常用的重复频率,比如hourlydailyweekly等等。但是,如果你需要更灵活的重复频率,你可以自定义。

你可以使用cron_schedules filter来添加自定义的重复频率。

例如,假设你想添加一个每两分钟执行一次的重复频率:

add_filter( 'cron_schedules', 'add_two_minute_interval' );

function add_two_minute_interval( $schedules ) {
  $schedules['two_minutes'] = array(
    'interval' => 120, // 每隔120秒
    'display' => __( 'Every Two Minutes' )
  );
  return $schedules;
}

这段代码会将一个名为two_minutes的重复频率添加到WordPress的cron schedules中。这个重复频率的interval为120秒,displayEvery Two Minutes

然后,你就可以在wp_schedule_event()函数中使用这个自定义的重复频率了:

$timestamp = time(); // 从现在开始
$recurrence = 'two_minutes'; // 使用自定义的重复频率
$hook = 'my_two_minute_task';

wp_schedule_event( $timestamp, $recurrence, $hook );

add_action( 'my_two_minute_task', 'my_two_minute_task_callback' );

function my_two_minute_task_callback() {
  // 这里写你的任务代码
  // ...
  error_log('Two minute task run');
}

七、取消定时任务:再见,定时炸弹

如果你想取消一个已经调度的定时任务,可以使用wp_unschedule_event()函数。

它的语法是这样的:

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

注意:wp_unschedule_event()函数需要你提供与wp_schedule_event()函数相同的$timestamp$hook$args参数,才能正确取消任务。

如果你不确定任务的执行时间戳,可以使用wp_next_scheduled()函数来获取。

$timestamp = wp_next_scheduled( 'my_daily_task' );

if ( $timestamp ) {
  wp_unschedule_event( $timestamp, 'my_daily_task' );
}

这段代码会获取my_daily_task这个hook的下一次执行时间戳,然后取消这个任务。

八、常见问题及注意事项

  • 定时任务不执行? 首先,确保你的WordPress网站能够正常访问wp-cron.php。你可以尝试手动访问wp-cron.php,看看是否能够正常执行。其次,检查你的服务器是否正确配置了cron job。
  • 大量定时任务导致网站变慢? 尽量减少定时任务的数量,并优化任务代码,避免执行时间过长。
  • 时间戳问题: 确保你使用的时间戳是正确的Unix timestamp,并且与服务器的时区一致。
  • 参数序列化: wp_schedule_event()函数使用serialize()函数来序列化参数,因此你的参数必须是可序列化的。
  • 插件冲突: 某些插件可能会干扰WordPress的cron机制,导致定时任务无法正常执行。你可以尝试禁用所有插件,然后逐个启用,来找出冲突的插件。
  • 调试技巧: 在开发过程中,可以使用error_log()函数来记录任务的执行情况,方便调试。

九、总结

wp_schedule_event()是WordPress提供的一个强大的定时任务调度工具。它允许你轻松地安排各种定时任务,从而实现自动化管理和维护。

通过深入了解wp_schedule_event()的源码,以及它在wp_options表中的存储方式,我们可以更好地理解WordPress的cron机制,并更加灵活地使用它。

记住,合理使用定时任务可以提高你的网站效率,但过度使用可能会导致性能问题。因此,请谨慎使用,并定期检查你的定时任务列表。

希望今天的讲座对你有所帮助! 感谢大家的聆听,我们下次再见!

发表回复

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