剖析Laravel隊列系統--準備隊列做業

原文連接https://divinglaravel.com/queue-system/preparing-jobs-for-queuelaravel

Every job we push to queue is stored in some storage space sorted by the order of execution, this storage place could be a MySQL database, Redis store, or a 3rd party service like Amazon SQS.數據庫

咱們推送到隊列的每一個做業都存儲在按執行順序排序的某些存儲空間中,該存儲位置能夠是MySQL數據庫,Redis存儲或像Amazon SQS這樣的第三方服務。數組

Later in this dive we're going to explore how workers fetch these jobs and start executing them, but before that let's see how we store our jobs, here are the attributes we keep for every job we store:安全

在此次深刻探究以後,咱們將探索worker如何獲取這些做業並開始執行這些做業,但在此以前,讓咱們看看咱們如何存儲咱們的做業,這裏是咱們爲每一個咱們存儲的做業所保留的屬性:app

  • What queue it should be running through
  • The number of times this job was attempted (Initially zero)
  • The time when the job was reserved by a worker
  • The time when the job becomes available for a worker to pickup
  • The time when the job was created
  • The payload of the job
  • 應該運行什麼隊列
  • 此做業嘗試次數(最初爲零)
  • 做業由worker保留的時間
  • 做業時間可供worker領取
  • 建立做業的時間
  • 做業的有效載荷

What do you mean by queue?less

你說的排隊是什麼意思?ide

Your application sends several types of jobs to queue, by default all the jobs are put in a single queue; however, you might want to put mail jobs in a different queue and instruct a dedicated worker to run jobs only from this queue, this will ensure that other jobs won't delay sending emails since it has a dedicated worker of its own.函數

您的應用程序發送不少類型的做業到隊列,默認狀況下,全部做業都放在單個隊列中; 可是,您可能但願將郵件做業放在不一樣的隊列中,並指示專用worker僅今後隊列運行做業,這將確保其餘做業不會延遲發送電子郵件,由於它具備本身的專用worker。fetch

The number of attemptsui

嘗試次數

By default the queue manager will keep trying to run a specific job if it fails running, it'll keep doing that forever unless you set a maximum number of attempts, when the job attempts reach that number the job will be marked as failed and workers won't try to run it again. This number starts as zero but we keep incrementing it every time we run the job.

默認狀況下,若是運行失敗,隊列管理器將繼續嘗試運行特定的做業,除非您設置了最大次數,不然將繼續執行此操做,看成業嘗試達到該數量時,該做業將被標記爲失敗,而且worker 不會再嘗試運行它。 這個數字從0開始,在每次運行做業時不斷增長。

The Reservation time

預定時間

Once a worker picks a job we mark it as reserved and store the timestamp of when that happened, next time a worker tries to pick a new job it won't pick a reserved one unless the reservation lock is expired, but more on that later.

一旦worker選擇了一個做業,咱們將其標記爲保留,並存儲發生的時間戳,下一次worker嘗試選擇新的做業時,除非保留鎖定已過時,不然不會選擇保留位。

The availability time

可用時間

By default a job is available once it's pushed to queue and workers can pick it right away, but sometimes you might need to delay running a job for sometime, you can do that by providing a delay while pushing the job to queue using the later() method instead of push():

默認狀況下,一旦做業被推送到隊列,worker能夠當即獲取它,但有時您可能須要延遲一段時間的運行做業,您能夠經過 later() 方法而不是 push()來延遲來推送做業:

Queue::later(60, new SendInvoice())

The later() method uses the availableAt() method to determine the availability time:

後者 later() 方法使用 availableAt() 方法來肯定可用性時間:

protected function availableAt($delay = 0)
{
    return $delay instanceof DateTimeInterface
                        ? $delay->getTimestamp()
                        : Carbon::now()->addSeconds($delay)->getTimestamp();
}

As you can see you can pass an instance of DateTimeInterface to set the exact time, or you can pass the number of seconds and Laravel will calculate the availability time for you under the hood.

您能夠看到,您能夠傳遞一個 DateTimeInterface 的實例來設置確切的時間,或者您能夠傳遞秒數,Laravel會計算出您的可用時間。

The payload

有效載荷

The push() & later() methods use the createPayload() method internally to create the information needed to execute the job when it's picked by a worker. You can pass a job to queue in two formats:

push() & later() 方法在內部使用 createPayload() 方法來建立執行做業所需的信息,當worker選擇它時。 您能夠經過兩種格式將做業傳遞給隊列:

// Pass an object
Queue::push(new SendInvoice($order));

// Pass a string
Queue::push('App\Jobs\SendInvoice@handle', ['order_id' => $orderId])

In the second example while the worker is picking the job, Laravel will use the container to create an instance of the given class, this gives you the chance to require any dependencies in the job's constructor.

在第二個例子中,當worker正在選擇做業時,Laravel將使用該容器來建立給定類的實例,這樣可讓您有機會在做業的構造函數中包含任何依賴。

Creating the payload of a string job

建立字符串做業的有效負荷

createPayload() calls createPayloadArray() internally which calls the createStringPayload() method in case the job type is non-object:

createPayload()在內部調用 createPayloadArray(),調用 createStringPayload() 方法,以防做業類型爲空對象:

protected function createStringPayload($job, $data)
{
    return [
        'displayName' => is_string($job) ? explode('@', $job)[0] : null,
        'job' => $job, 'maxTries' => null,
        'timeout' => null, 'data' => $data,
    ];
}

The displayName of a job is a string you can use to identify the job that's running, in case of non-object job definitions we use the the job class name as the displayName.

Notice also that we store the given data in the job payload.

做業的displayName 是可用於標識正在運行的做業的字符串,在空對象做業定義的狀況下,咱們使用做業類名稱做爲 displayName.

Notice also that we store the given data in the job payload.

還要注意,咱們將給定的數據存儲在做業有效載荷中。

Creating the payload of an object job

建立對象做業的有效負載

Here's how an object-based job payload is created:

如下是建立基於對象的做業有效負載的方式:

protected function createObjectPayload($job)
{
    return [
        'displayName' => $this->getDisplayName($job),
        'job' => 'Illuminate\Queue\CallQueuedHandler@call',
        'maxTries' => isset($job->tries) ? $job->tries : null,
        'timeout' => isset($job->timeout) ? $job->timeout : null,
        'data' => [
            'commandName' => get_class($job),
            'command' => serialize(clone $job),
        ],
    ];
}

Since we already have the instance of the job we can extract some useful information from it, for example the getDisplayName() method looks for a displayName() method inside the job instance and if found it uses the return value as the job name, that means you can add such method in your job class to be in control of the name of your job in queue.

因爲咱們已經有了這個做業的實例,咱們能夠從中提取一些有用的信息,例如, getDisplayName() 方法在做業實例中查找一個 displayName() 方法,若是發現它使用返回值做爲做業名稱,那麼 意味着您能夠在做業類中添加這樣的方法來控制隊列中做業的名稱。

protected function getDisplayName($job)
{
    return method_exists($job, 'displayName')
                    ? $job->displayName() : get_class($job);
}

We can also extract the value of the maximum number a job should be retried and the timeout for the job, if you pass these values as class properties Laravel stores this data into the payload for use by the workers later.

As for the data attribute, Laravel stores the class name of the job as well as a serialized version of that job.

若是將這些值做爲類屬性傳遞,咱們還能夠提取做業應該重試的最大數量和做業的超時值,Laravel將此數據存儲到有用的worker中以供之後使用。

對於數據屬性,Laravel存儲做業的類名稱以及該做業的序列化版本。

Then how can I pass my own data

那麼如何傳遞我本身的數據

In case you chose to pass an object-based job you can't provide a data array, you can store any data you need inside the job instance and it'll be available once un-serialized.

若是您選擇傳遞基於對象的做業,則沒法提供數據數組,您能夠在做業實例中存儲所需的任何數據,而且一旦未序列化,它都是可用的。

Why do we pass a different class as the "job" parameter

爲何咱們經過不一樣的類做爲「做業」的參數

QueueCallQueuedHandler@call is a special class Laravel uses while running object-based jobs, we'll look into it in a later stage.

QueueCallQueuedHandler@call 是Laravel在運行基於對象的做業時使用的一個特殊類,咱們將在稍後的階段進行研究。

Serializing jobs

序列化做業

Serializing a PHP object generates a string that holds information about the class the object is an instance of as well as the state of that object, this string can be used later to re-create the instance.

In our case we serialize the job object in order to be able to easily store it somewhere until a worker is ready to pick it up & run it, while creating the payload for the job we serialize a clone of the job object:

序列化PHP對象會生成一個字符串,該字符串保存有關該對象是該實例的類的信息以及該對象的狀態,此字符串之後可用於從新建立該實例。

通常狀況下,咱們序列化做業對象,以便可以輕鬆地將其存儲在某個位置,直到worker準備好接收並運行它,爲做業建立有效負載的同時咱們將序列化做業對象的克隆:

serialize(clone $job);

But why a clone? why not serialize the object itself?

但爲何要克隆? 爲何不序列化對象自己?

While serializing the job we might need to do some transformation to some of the job properties or properties of any of the instances our job might be using, if we pass the job instance itself transformations will be applied to the original instances while this might not be desired, let's take a look at an example:

在序列化做業時,咱們可能須要對咱們的做業可能使用的任何實例的某些做業屬性或屬性進行一些轉換,若是咱們傳遞做業實例自己,轉換將應用於原始實例,而這可能不是 指望的,讓咱們來看一個例子:

class SendInvoice
{
    public function __construct(Invoice $Invoice)
    {
        $this->Invoice = $Invoice;
    }
}

class Invoice
{
    public function __construct($pdf, $customer)
    {
        $this->pdf = $pdf;
        $this->customer = $customer;
    }
}

While creating the payload for the SendInvoice job we're going to serialize that instance and all its child objects, the Invoice object, but PHP doesn't always work well with serializing files and the Invoice object has a property called $pdf which holds a file, for that we might want to store that file somewhere, keep a reference to it in our instance while serializing and then when we un-serialize we bring back the file using that reference.

在爲 SendInvoice 做業建立載荷時,咱們將序列化該實例及其全部子對象,發票, 但PHP並不老是能很好地處理序列化文件,而且發票對象具備稱爲 $pdf 的屬性,該屬性包含 文件,由於咱們可能想要將該文件存儲在某個地方,在序列化期間保留對咱們實例的引用,而後當咱們進行非序列化時,咱們使用該引用返回文件。

class Invoice
{
    public function __construct($pdf, $customer)
    {
        $this->pdf = $pdf;
        $this->customer = $customer;
    }

    public function __sleep()
    {
        $this->pdf = stream_get_meta_data($this->pdf)['uri'];

        return ['customer', 'pdf'];
    }

    public function __wakeup()
    {
        $this->pdf = fopen($this->pdf, 'a');
    }
}

The __sleep() method is automatically called before PHP starts serializing our object, in our example we transform our pdf property to hold the path to the PDF resource instead of the resource itself, and inside __wakup() we convert that path back to the original value, that way we can safely serialize the object.

Now if we dispatch our job to queue we know that it's going to be serialized under the hood:

在PHP開始序列化咱們的對象以前,__sleep() 方法被自動調用,在咱們的示例中,咱們將咱們的pdf屬性轉換爲保存到PDF資源的路徑而不是資源自己,而在__wakup() 中,咱們將該路徑轉換回原始值,這樣咱們能夠安全地序列化對象。

如今若是咱們分發做業到隊列,將被序列化:

$Invoice = new Invoice($pdf, 'Customer #123');

Queue::push(new SendInvoice($Invoice));

dd($Invoice->pdf);

However, if we try to look at the Invoice pdf property after sending the job to queue we'll find that it holds the path to the resource, it doesn't hold the resource anymore, that's because while serializing the Job PHP serialized the original Invoice instance as well since it was passed by reference to the SendInvoice instance.

然而,若是咱們在發送做業到隊列後嘗試查看發票pdf屬性,咱們會發現它擁有資源的路徑,它再也不佔用資源,這是由於序列化做業時PHP已經序列化了原始發票實例,由於它經過引用傳遞給SendInvoice實例。

Here's when cloning becomes handy, while cloning the SendInvoice object PHP will create a new instance of that object but that new instance will still hold reference to the original Invoice instance, but we can change that:

如下是克隆handy時,克隆 SendInvoice 對象時PHP將建立該對象的新實例,但新的實例仍將保留對原始Invoice實例的引用,可是咱們能夠更改:

class SendInvoice
{
    public function __construct(Invoice $Invoice)
    {
        $this->Invoice = $Invoice;
    }

    public function __clone()
    {
        $this->Invoice = clone $this->Invoice;
    }
}

Here we instruct PHP that whenever it clones an instance of the SendInvoice object it should use a clone of the invoice property in the new instance not the original one, that way the original Invoice object will not be affected while we serialize.

這裏咱們介紹PHP只要克隆 SendInvoice 對象的一個實例,它應該使用新實例中的Invoice屬性的克隆,而不是原始實例,那麼原始Invoice對象在序列化時不會受到影響。

轉載請註明: 轉載自Ryan是菜鳥 | LNMP技術棧筆記

若是以爲本篇文章對您十分有益,何不 打賞一下

謝謝打賞

本文連接地址: 剖析Laravel隊列系統--準備隊列做業

相關文章
相關標籤/搜索