记录队列序列化模型导致的内存溢出的解决方案
直接进入主题,当一个Job需要加入队列的时候,经常会用到一个框架自带的序列化TraitsIlluminate\Queue\SerializesModels
,例如以下代码:
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use App\Models\Order;
class CloseCheckOutOrder implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $order;
public function __construct(Order $order)
{
$this->order = $order;
}
public function handle()
{
// TODO XXX
}
}
默认情况下执行dispatch(new CloseCheckOutOrder($order))
是不会报错的
只有当关联了relation
时,且关联的子项也关联了父项,这时候就会有问题了,例如以下代码:
$order = Order::with(['order_goods'])->first();
foreach ( $order->order_goods as $order_goods) {
// 这里如果业务需求,假装把订单商品关联下父订单
$order_goods->order()->associate($order);
}
// 然后我们这里调用Job的话就会内存溢出
dispatch(new CloseCheckOutOrder($order));
查看了下源码,最终定位到了Illuminate\Database\Eloquent\Model
类下面的方法getQueueableRelations
/**
* Get the queueable relationships for the entity.
*
* @return array
*/
public function getQueueableRelations()
{
$relations = [];
foreach ($this->getRelations() as $key => $relation) {
if (! method_exists($this, $key)) {
continue;
}
$relations[] = $key;
if ($relation instanceof QueueableCollection) {
// 就是这里出的问题,子关联项如果也关联了父级的话,会一直循环下去
foreach ($relation->getQueueableRelations() as $collectionValue) {
$relations[] = $key.'.'.$collectionValue;
}
}
if ($relation instanceof QueueableEntity) {
// 就是这里出的问题,子关联项如果也关联了父级的话,会一直循环下去
foreach ($relation->getQueueableRelations() as $entityKey => $entityValue) {
$relations[] = $key.'.'.$entityValue;
}
}
}
return array_unique($relations);
}
不难看出上面的代码,变成无线循环了,所以最终导致内存溢出
简单粗暴的方法就是不设置序列化的关系,在Order
模型里重写getQueueableRelations
方法,直接放回一个空数组既。或者把加入队列需要序列化的模型的关系去掉
本作品采用《CC 协议》,转载必须注明作者和本文链接