怎么优化在WordPress重复Hooks上面运行耗时操作任务

当使用一些 Hooks时,比如 save_post,我们可能会注意到挂载到这些 Hooks 上的函数可能会被触发多次,例如当我们使用过时的元数据盒子而不是古腾堡插件侧边栏时,就可能会发生这种情况。

关于怎么避免在save_post Hook上重复执行的问题,可以参考本站之前关于在WordPress 中使用钩子进行主题开发时避免死循环的文章。另外,如果我们使用区块编辑器的方式来创建元数据盒子,这个问题应该就不存在了。

重复Hooks 带来的性能问题

除此之外,还有一种重复执行Hooks 的情况是值得我们讨论的,那就是WooCommerce中的 woocommerce_update_order Hook,当我们创建或更新 WooCommerce 订单时,woocommerce_update_order 动作 Hook 会被多次触发,这可能会导致性能问题。我们来看一下下面的代码。

add_action( 'woocommerce_update_order', function( $order_id ) {
	$email = ''; // 在这里设置你的测试电子邮件地址
	wp_mail( $email, '测试邮件', sprintf( '订单 %d 已更新', $order_id ) );
} );

添加了这段代码之后,再创建或更新一个订单,很快我们就会发现,电子邮箱被代码中的测试邮件淹没了。这些邮件就是我们更新订单状态时收到的电子邮件,每一封就代表着一次 woocommerce_update_order Hook 被触发。

我们知道,发送电子邮件是一个比较耗时的操作,特别是网站服务器到SMTP服务器之间的网络连接比较慢时。如果主题或插件中包含了类似上面的测试代码而没有做任何优化时,我们会发现,下单或更新订单流程会变得非常慢。下面我们来看一下怎么来优化这个问题。

方法一:在挂载到该Hook上面的函数内部移除该Hook

和之前提到的防止死循环的方法类似,我们直接在挂载到 woocommerce_update_order Hook 的函数中移除该Hook,就可以防止该Hook多次运行。

add_action( 'woocommerce_update_order', 'wprs_order_update', 25, 2 );

function wprs_order_update( $order_id, $order ) {
	remove_action( 'woocommerce_update_order', __FUNCTION__, 25, 2 );
	
	// 这个函数在当前请求中不会再次运行
}

这种方法可以很简单地解决问题,但是会有一个潜在影响,看一下下面的情况,假设woocommerce_update_order运行了5次 :

  1. woocommerce_update_order – 在这里,我们运行挂载的函数然后断开连接
  2. woocommerce_update_order,
  3. woocommerce_update_order,
  4. woocommerce_update_order – 在这里, WooCommerce 可能做了一些需要在我们的函数中跟踪订单的重要操作,但是我们无法做到这一点,因为我们已经在第一个 Hook 中断开了连接!
  5. woocommerce_update_order

如果你觉得这种问题不会对你造成影响,就可以直接使用这种方法来进行优化。如果你想尽量避免这个问题,可以参考第二种方法。

方法二:使用计划任务来运行函数

如果我们阅读过一些 WooCommerce 源代码,就会发现一些插件已经使用了这种方法。所以在WooCommerce 添加类似「woocommerce_update_order_once」 这种只运行一次的Hook之前,这看起来是解决重复执行问题的一种比较好的办法,下面是该办法的实现代码。

add_action( 'woocommerce_update_order', 'wprs_maybe_order_update' );

function wprs_maybe_order_update( $order_id ) {
	
	as_schedule_single_action( 
		time() + 5, // 何时运行?5 秒后
		'wprs_update_order', // Hook 名称
		array( $order_id ), // 传递给 Hook 和函数的参数
		'', // 组名称,可以是任何字符串
		true // 应该是唯一的吗?- 是的
	);

}

add_action( 'wprs_update_order', function( $order_id ) {
	$order = wc_get_order( $order_id );
	// 在这里进行我们的操作
} );

在上面的代码中:

  • 我选择使用 as_schedule_single_action() 创建一个计划任务,也可以考虑使用as_enqueue_async_action()
  • 关键点在as_schedule_single_action第五个参数$unique ,我们将其设为true,即可防止为同一个订单创建多个计划任务。

除了使用WooCommerce的计划任务,我们还可以使用WP CronWP Queue 来实现类似的功能。

Related Posts

Leave a Reply

Your email address will not be published. Required fields are marked *