阐述 WordPress `wp_cron()` 源码:它是如何通过 `shutdown` 钩子在页面加载结束时执行定时任务的。

各位观众,各位来宾,欢迎来到今天的“WordPress源码剖析”特别节目!我是你们的老朋友,也是你们的代码向导。今天咱们不聊八卦,只聊代码,而且是WordPress里一个非常神秘,但又非常重要的东西:wp_cron()

别害怕,虽然名字听起来像个魔法咒语,但它其实就是WordPress的定时任务管理系统。想象一下,你设定了一个定时发布文章的任务,或者需要定期清理垃圾数据,这些背后默默工作的就是wp_cron()

今天,咱们就来扒一扒它的源码,看看它是怎么通过shutdown钩子,在页面加载结束时,偷偷地把这些任务给安排上的。准备好了吗? Let’s dive in!

第一幕:wp_cron()是个啥?

首先,我们得搞清楚wp_cron()的定位。它不是一个真正的、像Linux cron那样的系统级定时任务。它更像是一个“伪”定时任务,或者说是一个“事件触发型”的定时任务。

什么意思呢? WordPress本身不是一个一直运行的后台进程。它是在用户访问你的网站时才被激活的。所以,wp_cron()的工作方式是:

  1. 检查: 在每次页面加载时,它会检查是否有需要执行的定时任务。
  2. 触发: 如果有,它会尝试触发这些任务的执行。

但问题来了,WordPress怎么保证在页面加载完毕后,这些任务能够执行呢? 这就轮到我们今天的主角 shutdown 钩子上场了。

第二幕:shutdown钩子:幕后英雄

shutdown钩子是PHP提供的一个特殊钩子。它会在PHP脚本执行即将结束时被触发。 这给了我们一个机会,可以在页面内容输出完毕之后,执行一些清理工作,或者像wp_cron()这样,执行一些定时任务。

那么,wp_cron()是如何利用shutdown钩子的呢? 让我们来看一段简化后的代码:

// wp-includes/cron.php (简化版)

function wp_cron() {
    if ( defined( 'DOING_CRON' ) ) {
        return; // 避免重复执行
    }

    // 检查是否需要运行cron
    if ( wp_doing_cron() ) {
        return;
    }

    // 注册shutdown钩子
    add_action( 'shutdown', 'wp_maybe_run_cron' );
}

function wp_maybe_run_cron() {
    if ( wp_doing_cron() ) {
        return;
    }

    $crons = _get_cron_array();

    if ( empty( $crons ) ) {
        return;
    }

    $time = time();

    foreach ( $crons as $timestamp => $cronhooks ) {
        if ( $timestamp <= $time ) {
            foreach ( $cronhooks as $hook => $args ) {
                spawn_cron(); // 触发cron
                return; // 只触发一次,避免阻塞
            }
        }
    }
}

function spawn_cron() {
    $cron_url = get_site_url() . '/wp-cron.php?doing_wp_cron=' . time();

    // 使用非阻塞的方式请求cron URL
    wp_remote_get( $cron_url, array(
        'timeout'   => 0.01, // 极短的超时时间
        'blocking'  => false, // 非阻塞请求
        'sslverify' => apply_filters( 'https_local_ssl_verify', false )
    ) );
}

function wp_doing_cron() {
    if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
        return true;
    }

    if ( isset( $_GET['doing_wp_cron'] ) ) {
        return true;
    }

    return false;
}

这段代码做了以下几件事:

  1. wp_cron() 这个函数是wp_cron()的入口。它首先检查是否已经在运行cron,避免重复执行。然后,它使用add_action( 'shutdown', 'wp_maybe_run_cron' )注册了shutdown钩子。

  2. wp_maybe_run_cron() 这个函数会在shutdown钩子触发时被调用。它会检查是否有需要执行的定时任务,如果有,则调用spawn_cron()来触发cron。

  3. spawn_cron() 这个函数使用wp_remote_get()函数,以非阻塞的方式向wp-cron.php发送一个HTTP请求。这个请求会触发wp-cron.php的执行,从而真正执行定时任务。

第三幕:wp-cron.php:任务执行者

wp-cron.php是真正执行定时任务的脚本。它会检查$_GET['doing_wp_cron']参数,如果存在,则表示这是一个cron请求。然后,它会加载WordPress核心文件,并执行所有到期的定时任务。

// wp-cron.php (简化版)

if ( ! empty( $_GET['doing_wp_cron'] ) ) {
    define( 'DOING_CRON', true );

    // 加载WordPress核心文件
    require_once( dirname( __FILE__ ) . '/wp-load.php' );

    // 禁用错误显示
    @ini_set( 'display_errors', 0 );

    // 执行cron任务
    if ( defined( 'ABSPATH' ) ) {
        require_once( ABSPATH . 'wp-includes/cron.php' );

        $crons = _get_cron_array();
        if ( ! empty( $crons ) ) {
            foreach ( $crons as $timestamp => $cronhooks ) {
                if ( time() >= $timestamp ) {
                    foreach ( $cronhooks as $hook => $args ) {
                        if ( has_action( $hook ) ) {
                            do_action_ref_array( $hook, $args['args'] );
                            unset( $crons[ $timestamp ][ $hook ] );
                        }
                    }
                }
            }

            _set_cron_array( $crons );
        }
    }
    exit;
}

这段代码做了以下几件事:

  1. 定义DOING_CRON 定义DOING_CRON常量,防止重复执行。

  2. 加载WordPress核心文件: 加载wp-load.php,初始化WordPress环境。

  3. 执行cron任务: 从数据库中读取定时任务列表,并执行所有到期的任务。

第四幕:定时任务的存储与读取

WordPress使用一个名为cron的option来存储定时任务。这个option是一个序列化的数组,包含了所有定时任务的信息。

// 获取定时任务列表
function _get_cron_array() {
    $crons = get_option( 'cron' );

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

    foreach ( $crons as $timestamp => $cronhooks ) {
        foreach ( $cronhooks as $hook => $args ) {
            if ( ! has_action( $hook ) ) {
                unset( $crons[ $timestamp ][ $hook ] );
            }
        }
        if ( empty( $crons[ $timestamp ] ) ) {
            unset( $crons[ $timestamp ] );
        }
    }
    return $crons;
}

// 更新定时任务列表
function _set_cron_array( $crons ) {
    update_option( 'cron', $crons );
}

这两个函数分别用于获取和更新cron option。

第五幕:定时任务的添加与移除

WordPress提供了两个函数来添加和移除定时任务:

  • wp_schedule_event() 添加一个定时任务。
  • wp_unschedule_event() 移除一个定时任务。

例如,要添加一个每天凌晨执行的定时任务,可以这样写:

if ( ! wp_next_scheduled( 'my_daily_event' ) ) {
    wp_schedule_event( strtotime( 'tomorrow 00:00:00' ), 'daily', 'my_daily_event' );
}

add_action( 'my_daily_event', 'my_daily_function' );

function my_daily_function() {
    // 这里写你的定时任务代码
    // 例如:清理垃圾数据,发送邮件等等
}

这段代码做了以下几件事:

  1. 检查: 检查是否已经存在名为my_daily_event的定时任务。

  2. 添加: 如果不存在,则使用wp_schedule_event()添加一个定时任务,每天凌晨执行,使用daily作为重复间隔,并绑定到my_daily_event这个action上。

  3. 绑定: 使用add_action()my_daily_event action绑定到my_daily_function()函数上。

  4. 执行: 当定时任务到期时,my_daily_function()函数会被执行。

第六幕:wp_remote_get():非阻塞请求的关键

spawn_cron()函数使用wp_remote_get()函数来触发wp-cron.php的执行。 关键在于它的参数:

  • timeout 设置为0.01秒,表示极短的超时时间。
  • blocking 设置为false,表示非阻塞请求。

这两个参数的组合,使得wp_remote_get()函数可以快速地发起一个HTTP请求,而不会阻塞当前页面的加载。

第七幕:wp_doing_cron():避免重复执行的卫士

为了避免wp_cron()被重复执行,WordPress使用wp_doing_cron()函数来检查当前是否已经在运行cron。

这个函数会检查两个条件:

  1. DOING_CRON常量: 如果定义了DOING_CRON常量,则表示已经在运行cron。
  2. $_GET['doing_wp_cron']参数: 如果存在$_GET['doing_wp_cron']参数,则表示这是一个cron请求。

如果满足其中一个条件,则wp_doing_cron()函数会返回true,阻止wp_cron()的执行。

第八幕:一些需要注意的地方

  1. 可靠性问题: 由于wp_cron()依赖于用户访问网站才能触发,所以它的可靠性并不高。如果你的网站流量很低,或者用户很少访问,那么定时任务可能会延迟执行,甚至根本不执行。

  2. 性能问题: 每次页面加载都会触发wp_cron()的检查,这可能会对网站性能产生一定的影响。

  3. 替代方案: 如果你需要更可靠的定时任务,可以考虑使用系统级的cron,或者使用一些专门的定时任务服务。

  4. 调试: 可以通过定义WP_DEBUGtrue,并在wp-config.php中设置define('ALTERNATE_WP_CRON', true);来强制使用内置的cron,方便调试。

第九幕:总结与展望

好了,各位观众,今天的“WordPress源码剖析”特别节目到这里就告一段落了。我们一起深入了解了wp_cron()的工作原理,以及它是如何通过shutdown钩子,在页面加载结束时执行定时任务的。

我们学习了以下几个关键点:

  • wp_cron()是一个“伪”定时任务,它依赖于用户访问网站才能触发。
  • shutdown钩子是wp_cron()的关键,它允许我们在页面加载完毕后执行代码。
  • wp_remote_get()函数以非阻塞的方式触发wp-cron.php的执行。
  • wp_doing_cron()函数用于避免wp_cron()被重复执行。

希望今天的节目能帮助大家更好地理解WordPress的定时任务机制,也希望大家能在实际开发中灵活运用这些知识。

最后,感谢大家的收看,我们下期再见!


表格总结:

组件/函数 作用 关键代码
wp_cron() wp_cron的入口,注册shutdown钩子。 add_action( 'shutdown', 'wp_maybe_run_cron' );
wp_maybe_run_cron() 检查是否有需要执行的定时任务,并触发spawn_cron() spawn_cron();
spawn_cron() 以非阻塞方式请求wp-cron.php wp_remote_get( $cron_url, array( 'timeout' => 0.01, 'blocking' => false, 'sslverify' => apply_filters( 'https_local_ssl_verify', false ) ) );
wp-cron.php 实际执行定时任务的脚本。 do_action_ref_array( $hook, $args['args'] );
_get_cron_array() 获取定时任务列表(从cron option)。 get_option( 'cron' );
_set_cron_array() 更新定时任务列表(到cron option)。 update_option( 'cron', $crons );
wp_schedule_event() 添加一个定时任务。 add_action( $hook, $callback ); (实际上,wp_schedule_event 内部会更新 cron option,并让 wp_cron() 在适当时机执行对应的action)
wp_unschedule_event() 移除一个定时任务。 更新 cron option,移除对应的定时任务
wp_doing_cron() 检查当前是否正在运行cron,防止重复执行。 defined( 'DOING_CRON' ) && DOING_CRON 以及 isset( $_GET['doing_wp_cron'] )
shutdown hook 在PHP脚本执行即将结束时被触发。 add_action( 'shutdown', 'function_name' );

希望这个表格能够帮助你更好地理解各个组件的作用和关键代码。

发表回复

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