各位观众,各位来宾,欢迎来到今天的“WordPress源码剖析”特别节目!我是你们的老朋友,也是你们的代码向导。今天咱们不聊八卦,只聊代码,而且是WordPress里一个非常神秘,但又非常重要的东西:wp_cron()
。
别害怕,虽然名字听起来像个魔法咒语,但它其实就是WordPress的定时任务管理系统。想象一下,你设定了一个定时发布文章的任务,或者需要定期清理垃圾数据,这些背后默默工作的就是wp_cron()
。
今天,咱们就来扒一扒它的源码,看看它是怎么通过shutdown
钩子,在页面加载结束时,偷偷地把这些任务给安排上的。准备好了吗? Let’s dive in!
第一幕:wp_cron()
是个啥?
首先,我们得搞清楚wp_cron()
的定位。它不是一个真正的、像Linux cron那样的系统级定时任务。它更像是一个“伪”定时任务,或者说是一个“事件触发型”的定时任务。
什么意思呢? WordPress本身不是一个一直运行的后台进程。它是在用户访问你的网站时才被激活的。所以,wp_cron()
的工作方式是:
- 检查: 在每次页面加载时,它会检查是否有需要执行的定时任务。
- 触发: 如果有,它会尝试触发这些任务的执行。
但问题来了,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;
}
这段代码做了以下几件事:
-
wp_cron()
: 这个函数是wp_cron()
的入口。它首先检查是否已经在运行cron,避免重复执行。然后,它使用add_action( 'shutdown', 'wp_maybe_run_cron' )
注册了shutdown
钩子。 -
wp_maybe_run_cron()
: 这个函数会在shutdown
钩子触发时被调用。它会检查是否有需要执行的定时任务,如果有,则调用spawn_cron()
来触发cron。 -
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;
}
这段代码做了以下几件事:
-
定义
DOING_CRON
: 定义DOING_CRON
常量,防止重复执行。 -
加载WordPress核心文件: 加载
wp-load.php
,初始化WordPress环境。 -
执行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() {
// 这里写你的定时任务代码
// 例如:清理垃圾数据,发送邮件等等
}
这段代码做了以下几件事:
-
检查: 检查是否已经存在名为
my_daily_event
的定时任务。 -
添加: 如果不存在,则使用
wp_schedule_event()
添加一个定时任务,每天凌晨执行,使用daily
作为重复间隔,并绑定到my_daily_event
这个action上。 -
绑定: 使用
add_action()
将my_daily_event
action绑定到my_daily_function()
函数上。 -
执行: 当定时任务到期时,
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。
这个函数会检查两个条件:
DOING_CRON
常量: 如果定义了DOING_CRON
常量,则表示已经在运行cron。$_GET['doing_wp_cron']
参数: 如果存在$_GET['doing_wp_cron']
参数,则表示这是一个cron请求。
如果满足其中一个条件,则wp_doing_cron()
函数会返回true
,阻止wp_cron()
的执行。
第八幕:一些需要注意的地方
-
可靠性问题: 由于
wp_cron()
依赖于用户访问网站才能触发,所以它的可靠性并不高。如果你的网站流量很低,或者用户很少访问,那么定时任务可能会延迟执行,甚至根本不执行。 -
性能问题: 每次页面加载都会触发
wp_cron()
的检查,这可能会对网站性能产生一定的影响。 -
替代方案: 如果你需要更可靠的定时任务,可以考虑使用系统级的cron,或者使用一些专门的定时任务服务。
-
调试: 可以通过定义
WP_DEBUG
为true
,并在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' ); |
希望这个表格能够帮助你更好地理解各个组件的作用和关键代码。