咳咳,各位同学,晚上好!我是你们今晚的 WordPress 特邀讲师,咱们今天来聊聊 WordPress 里的 “懒人管家” – wp_cron()
。
别看它名字里带个 “cron”,听起来很高大上,但其实它跟真正的 Linux cron 差了十万八千里。它更像是一个“伪”定时任务系统,或者说,是一个“页面请求触发型”的定时任务。
一、wp_cron()
的原理:页面请求触发,任务排队执行
简单来说,wp_cron()
的工作方式是这样的:
- 页面加载触发: 当有人访问你的 WordPress 网站(无论是前台还是后台),WordPress 会顺便检查一下有没有到期的定时任务。
- 任务队列检查: 如果有到期的任务,WordPress 会将这些任务加入到一个队列里。
- 执行任务: WordPress 会尝试执行队列里的任务。
你看,整个过程都依赖于“页面加载”。如果没有人访问你的网站,那么 wp_cron()
就不会被触发,你的定时任务也就不会执行。
二、源码剖析:wp-cron.php
的秘密
我们来看一下 wp-cron.php
这个文件,它就是 wp_cron()
的核心所在。
<?php
/**
* WordPress Cron Page for external requests.
*
* Can be called for a remote cron setup.
*
* @package WordPress
*/
ignore_user_abort( true );
if ( ! defined( 'ABSPATH' ) ) {
/** Set up WordPress environment */
require_once __DIR__ . '/wp-load.php';
}
// Prevent direct execution.
if ( ! defined( 'DOING_CRON' ) ) {
define( 'DOING_CRON', true );
}
if ( WP_DEBUG ) {
error_reporting( E_ALL );
ini_set( 'display_errors', 1 );
}
if ( isset( $_GET['doing_wp_cron'] ) ) {
$doing_wp_cron = sanitize_text_field( wp_unslash( $_GET['doing_wp_cron'] ) );
} else {
$doing_wp_cron = true;
}
if ( ! defined( 'WP_ADMIN' ) ) {
define( 'WP_ADMIN', true );
}
// Load wp-admin functions.
require_once ABSPATH . 'wp-admin/includes/admin.php';
/** Load plugin.php early, as some plugins may use wp_cron() */
require_once ABSPATH . 'wp-includes/plugin.php';
/** Load cron functions. */
require_once ABSPATH . 'wp-includes/cron.php';
if ( is_multisite() && ms_is_switched() ) {
restore_current_blog();
}
if ( ! empty( $_GET['import'] ) ) {
define( 'WP_IMPORTING', true );
}
// Prevent caching of the page.
nocache_headers();
send_origin_headers();
wp();
// Prevent the browser from timing out.
@header( 'Content-Type: text/plain' );
if ( function_exists( 'ignore_user_abort' ) ) {
@ignore_user_abort( true );
}
if ( function_exists( 'set_time_limit' ) && ! ini_get( 'safe_mode' ) ) {
@set_time_limit( 0 );
}
/**
* Fires before the WP Cron spawns.
*
* @since 2.1.0
*
* @param bool $doing_wp_cron Whether WP_Cron is spawning.
*/
do_action( 'spawn_cron', $doing_wp_cron );
$crons = get_option( 'cron' );
if ( is_array( $crons ) ) {
$keys = array_keys( $crons );
natcasesort( $keys );
foreach ( $keys as $timestamp ) {
if ( $timestamp > time() ) {
break;
}
foreach ( $crons[ $timestamp ] as $hook => $args ) {
if ( strpos( $hook, 'shutdown_' ) === 0 ) {
continue;
}
/**
* Fires the WP Cron event.
*
* @since 2.1.0
*
* @param array $args Cron job arguments.
*/
do_action_ref_array( $hook, $args['args'] );
// If the hook ran, clear the schedule.
if ( isset( $crons[ $timestamp ][ $hook ] ) ) {
unset( $crons[ $timestamp ][ $hook ] );
}
}
unset( $crons[ $timestamp ] );
}
update_option( 'cron', $crons );
}
/**
* Fires after the WP Cron spawns.
*
* @since 2.1.0
*
* @param bool $doing_wp_cron Whether WP_Cron is spawning.
*/
do_action( 'after_cron', $doing_wp_cron );
exit();
我们来解读一下这段代码的关键部分:
ignore_user_abort(true);
: 这行代码告诉 PHP,即使客户端断开了连接,脚本也要继续执行。这是为了确保定时任务能够完成,即使访问者离开了页面。define( 'DOING_CRON', true );
: 定义DOING_CRON
常量,这是一个标志,告诉 WordPress 这是一个wp_cron()
进程,可以避免一些不必要的代码执行。$crons = get_option( 'cron' );
: 从数据库中获取cron
选项,这个选项存储了所有的定时任务信息。foreach ( $keys as $timestamp ) { ... }
: 遍历所有的定时任务,按照时间戳排序。if ( $timestamp > time() ) { break; }
: 如果任务的时间戳大于当前时间,说明任务还没有到期,跳出循环。do_action_ref_array( $hook, $args['args'] );
: 关键的一步!执行定时任务,这里使用了do_action_ref_array()
函数,它会触发一个 WordPress 的 action hook,从而执行与该 hook 关联的函数。update_option( 'cron', $crons );
: 更新cron
选项,删除已经执行的任务。
三、手动触发 wp_cron()
:模拟页面请求
虽然 wp_cron()
依赖于页面请求,但我们可以通过一些方法手动触发它,模拟页面请求。
-
直接访问
wp-cron.php
:在浏览器中输入
yourdomain.com/wp-cron.php
。 如果你的网站设置了 URL 重写,你可能需要访问yourdomain.com/wp-cron.php?doing_wp_cron
。// 示例:使用 PHP 的 curl 库 function trigger_wp_cron() { $url = 'https://yourdomain.com/wp-cron.php?doing_wp_cron'; $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $result = curl_exec($ch); curl_close($ch); return $result; } $cron_result = trigger_wp_cron(); echo "WP-Cron 执行结果: " . $cron_result;
-
使用
wp_remote_get()
函数:在你的 WordPress 代码中,可以使用
wp_remote_get()
函数来模拟一个 GET 请求,从而触发wp_cron()
。function trigger_wp_cron() { $url = site_url( '/wp-cron.php?doing_wp_cron' ); $response = wp_remote_get( $url, array( 'blocking' => false, // 设置为非阻塞,避免阻塞当前线程 'timeout' => 0.01, // 设置超短超时时间,进一步避免阻塞 'sslverify' => false // 如果你的服务器 SSL 配置有问题,可以设置为 false ) ); if ( is_wp_error( $response ) ) { $error_message = $response->get_error_message(); error_log( "触发 WP-Cron 失败: " . $error_message ); } else { error_log( "成功触发 WP-Cron" ); } } // 调用这个函数来触发 wp_cron() trigger_wp_cron();
注意:
blocking
设置为false
可以让wp_remote_get
函数异步执行,避免阻塞当前线程。timeout
设置为超短的时间,如果连接失败,则不阻塞当前线程。sslverify
设置为false
,如果在 https 环境下并且 SSL 证书有问题,可以避免错误。 -
使用WP-CLI:
如果已经安装了 WP-CLI,可以使用 wp cron event run
命令。
wp cron event run [hook] [--due-now] [--path=<path>] [--url=<url>]
例如:
wp cron event run my_custom_cron_hook --due-now
四、wp_cron()
的潜在性能问题
wp_cron()
虽然方便,但也存在一些潜在的性能问题:
问题 | 描述 | 解决方案 |
---|---|---|
依赖页面加载 | 如果网站访问量低,wp_cron() 可能无法及时执行,导致定时任务延迟。 |
1. 使用真正的服务器 cron 作业,定期访问 wp-cron.php 。 2. 增加网站访问量(这个…好像不太靠谱)。 |
性能开销 | 每次页面加载都会检查定时任务,这会增加服务器的负担,尤其是在高流量网站上。 | 1. 减少定时任务的数量和频率。 2. 使用缓存插件,减少页面加载的次数。 3. 将耗时的任务放到后台队列中处理(例如使用 WP Background Processing 库)。 |
任务阻塞 | 如果一个定时任务执行时间过长,可能会阻塞后续的任务执行。 | 1. 优化定时任务的代码,减少执行时间。 2. 使用并发执行的机制(例如使用多线程或多进程)。 |
并发执行问题 | 在某些情况下,wp_cron() 可能会并发执行同一个任务,导致数据不一致。 |
1. 使用锁机制,防止并发执行。 2. 确保你的定时任务是幂等的(即多次执行的结果与执行一次的结果相同)。 |
wp-cron.php 频繁调用 |
某些插件或主题可能会不正确地频繁调用 wp-cron.php ,导致服务器资源浪费。 |
1. 检查插件和主题的代码,找出频繁调用 wp-cron.php 的原因。 2. 禁用或替换有问题的插件或主题。 3. 使用代码限制 wp-cron.php 的调用频率(不推荐,可能影响正常任务执行)。 |
五、最佳实践:告别“伪”定时任务,拥抱真正的 cron
为了解决 wp_cron()
的性能问题,最佳实践是使用真正的服务器 cron 作业来触发 wp-cron.php
。
-
配置服务器 cron:
登录你的服务器,编辑 cron 配置文件(通常是
/etc/crontab
或者使用crontab -e
命令)。# 每隔 5 分钟访问一次 wp-cron.php */5 * * * * www-data /usr/bin/php /var/www/yourdomain.com/wp-cron.php >/dev/null 2>&1
*/5 * * * *
:表示每隔 5 分钟执行一次。www-data
:表示执行 cron 作业的用户(根据你的服务器配置修改)。/usr/bin/php
:PHP 解释器的路径(使用which php
命令查找)。/var/www/yourdomain.com/wp-cron.php
:wp-cron.php
文件的路径(根据你的网站目录结构修改)。>/dev/null 2>&1
:将输出和错误信息重定向到/dev/null
,避免 cron 作业发送邮件。
-
禁用
wp_cron()
的自动触发:在你的
wp-config.php
文件中添加以下代码:define('DISABLE_WP_CRON', true);
这会禁用
wp_cron()
的自动触发,只允许通过服务器 cron 作业来执行。
六、进阶技巧:自定义 wp_cron()
调度
如果你需要更灵活的定时任务调度,可以使用 WordPress 的 wp_schedule_event()
函数来添加自定义的定时任务。
// 定义一个自定义的定时任务
function my_custom_cron_function() {
// 这里写你的定时任务代码
error_log( '自定义定时任务执行了!' );
}
// 添加一个自定义的定时任务调度
function my_custom_cron_schedule() {
if ( ! wp_next_scheduled( 'my_custom_cron_hook' ) ) {
wp_schedule_event( time(), 'every_minute', 'my_custom_cron_hook' ); // 每分钟执行一次
}
}
add_action( 'wp', 'my_custom_cron_schedule' );
// 关联定时任务和函数
add_action( 'my_custom_cron_hook', 'my_custom_cron_function' );
// 添加自定义的调度间隔
add_filter( 'cron_schedules', 'my_custom_cron_intervals' );
function my_custom_cron_intervals( $schedules ) {
$schedules['every_minute'] = array(
'interval' => 60, // 每分钟
'display' => __( '每分钟' )
);
return $schedules;
}
wp_schedule_event()
:用于添加一个定时任务调度。time()
:表示任务开始执行的时间。'every_minute'
:表示任务的执行间隔(需要自定义)。'my_custom_cron_hook'
:表示任务的 hook 名称。
add_action( 'my_custom_cron_hook', 'my_custom_cron_function' );
:将 hook 和函数关联起来。cron_schedules
filter:用于添加自定义的调度间隔。
总结:
wp_cron()
是一个方便但有缺陷的定时任务系统。为了提高网站性能和可靠性,建议使用真正的服务器 cron 作业来触发 wp-cron.php
,并禁用 wp_cron()
的自动触发。
好了,今天的讲座就到这里。希望大家对 wp_cron()
有了更深入的了解。如果有什么问题,欢迎提问! 记住,写代码要优雅,姿势要帅!