分析 Laravel 佇列實現原理解決問題

Dr點燃發表於2017-08-19

問題

公司專案使用Laravel的開發的兩個專案在同一個測試伺服器部署,公用同一個redis。在使用laravel中的佇列時,產生衝突干擾。

查詢問題原因

在laravel 佇列的操作類Illuminate\Queue\RedisQueue.php中可以看到pushRaw()方法:

// 將一任務推入佇列中
public function pushRaw($payload, $queue = null, array $options = [])
    {
        $this->getConnection()->rpush($this->getQueue($queue), $payload);

        return Arr::get(json_decode($payload, true), 'id');
    }

從該方法中可以看出Lrarvel佇列的redis實現是通過list結構實現的,rpush(key, value)是將value推入鍵值為key的redis佇列,key的值則是通過$this->getQueue($queue) 獲取到的

protected function getQueue($queue)
    {
        return 'queues:'.($queue ?: $this->default);
    }

所以的redis中list中的key是 'queues:'.($queue ?: $this->default);拼接的,$this->default 的值是 RedisQueue 例項化的時候從config\queue.php配置中載入的 'queue' => 'default'$queue 是新增佇列時$this->dispatch( new jobClass()->onQueue($queue) )傳入的。

// config\queue.php 檔案中的redis配置部分
'redis' => [
            'driver'     => 'redis',
            'connection' => 'default',
            'queue'      => 'default',
            'expire'     => 60,
        ],

至此,兩個專案的佇列衝突原因就找到了。因為redis佇列配置中 'queue' => 'default' 都使用的預設的default,所以當共用redis時,預設的佇列list 都是'queue:default',所以導致了衝突。

因為佇列監聽 監聽的佇列名稱是由 --queue引數決定的,如果不傳就是我們上面設定的預設值,若傳了就會根據傳入的佇列名從前往後優先依次處理,具體見程式碼Illuminate\Queue\Worker.php中:

protected function getNextJob($connection, $queue)
    {
        if (is_null($queue)) {
            return $connection->pop();
        }

        foreach (explode(',', $queue) as $queue) {
            if (! is_null($job = $connection->pop($queue))) {
                return $job;
            }
        }
    }

$queue就是--queue=傳入的引數,當 $queue不存在是直接呼叫$connection->pop()當引數存在時會將引數解析,優先處理排在前面的佇列名稱,將佇列名稱傳入pop($queue), pop()會嘗試從指定佇列或預設佇列中獲取佇列任務

// Illuminate\Queue\RedisQueue.php
public function pop($queue = null)
    {
        $original = $queue ?: $this->default;

        $queue = $this->getQueue($queue);

        if (! is_null($this->expire)) {
            $this->migrateAllExpiredJobs($queue);
        }

        $job = $this->getConnection()->lpop($queue);

        if (! is_null($job)) {
            $this->getConnection()->zadd($queue.':reserved', $this->getTime() + $this->expire, $job);

            return new RedisJob($this->container, $this, $job, $original);
        }
    }

至此搞清了佇列執行的原理。

解決方法

將queue的配置檔案中預設佇列修改為不同的名稱,比如: 'queue' => laravel1','queue' => laravel2'。

佇列監聽 php artisan queue:listen redis --queue=laravel1,syncExpress

最後

歡迎關注我的部落格 ^_^

本作品採用《CC 協議》,轉載必須註明作者和本文連結

Dr點燃

相關文章